Compare commits

...

46 Commits

Author SHA1 Message Date
xgopilot
034b05c53c cl: remove Underlying() call to reject string type aliases in rewrites
Type aliases like `type T string` are no longer supported for
-ldflags -X rewrites. Only direct *string types are now allowed.

- Removed Underlying() call from isStringPtrType
- Added TestRewriteIgnoresStringAlias test case

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-14 14:49:30 +00:00
xgopilot
1ba7d1e561 fix: change to isStringPtrType for global string var
- Replace isStringType with isStringPtrType to properly validate that only *string type variables can be rewritten with -ldflags -X
- Remove maxStringTypeDepth constant as it's no longer needed
- Update tests to reflect the new function name and add test case for valid *string type
- Fix compileGlobal to use gbl.Type() for accurate type checking

This addresses the review feedback that Go only allows -X rewrites for the basic string type, not derived types like "type T string".

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-14 14:38:46 +00:00
xgopilot
d17ff2592a build: improve error handling and code quality
- Fix missing error handling in exportObject function
- Add explicit warning for non-string variable rewrites
- Improve documentation for maxRewriteValueLength constant

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-14 11:37:09 +00:00
Li Jie
4b26cccc90 cl: cover rewrite guards 2025-11-14 18:29:00 +08:00
Li Jie
3a1d8693e9 rewrite: address review feedback 2025-11-14 17:57:05 +08:00
Li Jie
2a52d422c5 cl: broaden rewrite coverage 2025-11-14 17:48:16 +08:00
Li Jie
b0f5d34b39 cl: add rewrite coverage test 2025-11-14 17:32:52 +08:00
Li Jie
8ba8ec71b5 build: write exports into temp files 2025-11-14 16:58:33 +08:00
Li Jie
1e4616a758 build: don't replace ExportFile 2025-11-14 16:58:33 +08:00
Li Jie
22a43622a0 cl: fix global var rewrite in alt pkg 2025-11-14 16:58:30 +08:00
Li Jie
e2bb68489d build: override vars in alt pkg 2025-11-14 14:37:39 +08:00
Li Jie
9b76be9e9e support ldflags rewrites for initialized globals 2025-11-14 11:59:13 +08:00
xushiwei
2f65c98eb4 Merge pull request #1348 from cpunion/feature/defer-loop
defer: enable loop lowering
2025-11-13 17:59:26 +08:00
Li Jie
aeb5d82d3e ssa: remove unreachable switch case 2025-11-13 16:15:36 +08:00
Li Jie
317de80b42 test: add nested defer/loop/branch/recover tests and fix mixed branch recover mapping 2025-11-13 16:15:33 +08:00
Li Jie
0455ad4443 ssa: Add test for >64 conditional defers; cover panic at eh.go:252-254 2025-11-13 15:48:50 +08:00
Li Jie
e459ca928b runtime: remove overlays for defer workaround 2025-11-13 15:48:50 +08:00
Li Jie
21fef123d2 di: clean unreachable code 2025-11-13 15:48:50 +08:00
Li Jie
983a189c18 test: cover defer recover and nested loops 2025-11-13 15:48:49 +08:00
Li Jie
cb173f91d0 ssa: rely on runtime thread defer TLS 2025-11-13 15:48:49 +08:00
Li Jie
30bde9f6b5 tls: stub handle for go test 2025-11-13 15:48:49 +08:00
Li Jie
3307860d33 ssa: avoid redundant thread defer update 2025-11-13 15:48:49 +08:00
Li Jie
16709411a0 defer: enable loop lowering 2025-11-13 15:48:46 +08:00
xushiwei
3f8c95cf87 Merge pull request #1334 from goplus/dependabot/go_modules/github.com/goplus/gogen-1.19.5
chore(deps): bump github.com/goplus/gogen from 1.19.3 to 1.19.5
2025-11-13 07:49:29 +08:00
xushiwei
0dbe528e6d Merge pull request #1304 from MeteorsLiu/impl-stacksave
feat: implement `llgo.stackSave`
2025-11-13 07:48:01 +08:00
dependabot[bot]
91f6ad9bfa chore(deps): bump github.com/goplus/gogen from 1.19.3 to 1.19.5
Bumps [github.com/goplus/gogen](https://github.com/goplus/gogen) from 1.19.3 to 1.19.5.
- [Release notes](https://github.com/goplus/gogen/releases)
- [Commits](https://github.com/goplus/gogen/compare/v1.19.3...v1.19.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-11 23:40:43 +00:00
xushiwei
4d1906d722 Merge pull request #1391 from goplus/dependabot/go_modules/github.com/goplus/lib-0.3.1
chore(deps): bump github.com/goplus/lib from 0.3.0 to 0.3.1
2025-11-12 07:38:32 +08:00
dependabot[bot]
ff4a180860 chore(deps): bump github.com/goplus/lib from 0.3.0 to 0.3.1
Bumps [github.com/goplus/lib](https://github.com/goplus/lib) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/goplus/lib/releases)
- [Commits](https://github.com/goplus/lib/compare/v0.3.0...v0.3.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-11 23:19:50 +00:00
xushiwei
7ce2733edf Merge pull request #1383 from luoliwoshang/feat/nil/recover
fix(runtime): restore SIGSEGV signal handler for non-wasm platforms
2025-11-12 06:43:02 +08:00
luoliwoshang
2363d28d57 feat(runtime): add SIGSEGV signal handler to convert nil pointer dereference to recoverable panic
- Add z_rt_default.go with signal handler for SIGSEGV on non-wasm platforms
- Convert segmentation faults from nil pointer access to Go panic
- Enable recover() to catch nil pointer dereference errors
- Use build tag (!wasm) to maintain wasm platform compatibility
- Remove commented-out signal handling code from z_rt.go

This aligns llgo's behavior with standard Go, where accessing nil pointer
fields triggers a recoverable panic instead of immediate program crash.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 15:15:51 +08:00
xushiwei
830e8e7058 Merge pull request #1381 from goplus/xgopilot/claude/document-outll-process-1730340128
docs(CLAUDE.md): document out.ll update process after IR changes
2025-11-10 13:55:01 +08:00
xushiwei
34caf518a1 Merge pull request #1386 from goplus/xgopilot/claude/issue-1385-1762256384
chore: remove deprecated //export LLGoVersion comment
2025-11-10 13:53:02 +08:00
xushiwei
a676ba29db Merge pull request #1387 from luoliwoshang/runtime/iface/func
fix(runtime): segmentation fault when calling interface private methods cross-package
2025-11-10 13:51:42 +08:00
xushiwei
d368cade1c Merge pull request #1388 from luoliwoshang/ci/no-auto-update
ci:avoid auto upgrade before brew install
2025-11-10 13:47:43 +08:00
luoliwoshang
4e374a99ff test:refression test for https://github.com/goplus/llgo/issues/1370
Co-authored-by: xgopilot <noreply@goplus.org>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-06 21:02:36 +08:00
luoliwoshang
41b403aef7 fix(runtime): segmentation fault when calling interface private methods cross-package
Co-authored-by: xgopilot <noreply@goplus.org>
Co-authored-by: Claude <noreply@anthropic.com>
2025-11-06 21:00:37 +08:00
luoliwoshang
cda9d682f2 ci:avoid brew auto upgrade dependant 2025-11-06 19:13:11 +08:00
xgopilot
742bfd95a2 chore: remove deprecated //export LLGoVersion comment
Removes the deprecated //export LLGoVersion comment from internal/env/version.go
as it is no longer needed.

Fixes #1385

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com>
2025-11-04 11:45:10 +00:00
xgopilot
01af858a2e docs(CLAUDE.md): remove redundant test failure mention
Removed the sentence about test failures as requested.
The "Why this matters" section now focuses on the benefits
of the process rather than the negative consequences.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com>
2025-10-31 03:43:06 +00:00
xgopilot
c557aa2af1 docs(CLAUDE.md): remove pro tip line
Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com>
2025-10-31 03:34:08 +00:00
xgopilot
c15c7a05b7 docs(CLAUDE.md): add gentests for batch out.ll updates
Updated the out.ll update workflow to document both tools:
- gentests: batch regeneration of all test files (recommended)
- llgen: individual test directory regeneration (for inspection)

Verified gentests works correctly by testing the regeneration
of all out.ll files. The tool successfully processes all
non-Python test directories.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com>
2025-10-31 03:27:49 +00:00
xgopilot
8f5f36e447 docs(CLAUDE.md): document out.ll update process after IR changes
Add comprehensive documentation explaining:
- What out.ll files are (IR comparison test files)
- When to update them (after modifying IR generation logic)
- How to update them (reinstall llgen, regenerate files)
- Why this matters (test consistency and regression detection)

This documentation will help avoid wasting time on repeatedly updating
test files, as it clarifies the required workflow when making compiler
changes that affect IR generation.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com>
2025-10-31 02:43:06 +00:00
Haolan
bf6f785988 Merge branch 'main' of https://github.com/goplus/llgo into impl-stacksave 2025-10-30 13:48:13 +08:00
Haolan
c3dbc580aa chore: update stacksave signature commets 2025-09-28 09:20:50 +08:00
Haolan
93e660a2b0 fix: amd64 getsp demo 2025-09-28 09:20:50 +08:00
Haolan
2ec5653f5e feat: implement llgo.stackSave 2025-09-28 09:20:50 +08:00
53 changed files with 2731 additions and 5792 deletions

View File

@@ -16,6 +16,8 @@ runs:
- name: Install macOS dependencies
if: runner.os == 'macOS'
shell: bash
env:
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
run: |
brew update

View File

@@ -51,6 +51,56 @@ go test ./...
**Important:** The `LLGO_ROOT` environment variable must be set to the repository root when running llgo commands during development.
### Update out.ll files after modifying compiler IR generation
**CRITICAL:** When you modify the compiler's IR generation logic (especially in `ssa/` or `cl/` packages), you MUST update all out.ll test files under the `cl/` directory.
#### Understanding out.ll files
The `out.ll` files under the `cl/` directory are comparison IR files that serve as reference outputs for the test suite:
- They are generated by `llgen` from the corresponding `in.go` files in the same directory
- They reflect the current compiler's LLVM IR representation of the Go source code
- They are used by tests to verify that the compiler generates correct and consistent IR output
#### Required steps after modifying IR generation logic
1. **Reinstall the tools** to apply your compiler changes:
```bash
go install -v ./chore/gentests
go install -v ./chore/llgen
```
2. **Regenerate out.ll files**:
**For batch updates (recommended)** - Use `gentests` to regenerate all test files:
```bash
gentests
```
This will automatically regenerate all out.ll files in these directories:
- `cl/_testlibc`
- `cl/_testlibgo`
- `cl/_testrt`
- `cl/_testgo`
- `cl/_testpy`
- `cl/_testdata`
**For individual test inspection** - Use `llgen` to regenerate specific test directories:
```bash
llgen cl/_testgo/interface
llgen cl/_testrt/tpmethod
```
3. **Verify the changes** make sense by reviewing the diff in the out.ll files
4. **Commit the updated out.ll files** along with your compiler changes
#### Why this matters
This process ensures that:
- The test suite reflects the current compiler behavior
- Changes to IR generation are properly documented and reviewed
- Future regressions can be detected by comparing against the reference output
## Code Quality
Before submitting any code updates, you must run the following formatting and validation commands:

View File

@@ -261,7 +261,7 @@ All Go syntax (including `cgo`) is already supported. Here are some examples:
### Defer
LLGo `defer` does not support usage in loops. This is not a bug but a feature, because we think that using `defer` in a loop is a very unrecommended practice.
LLGo now supports `defer` within loops, matching Go's semantics of executing defers in LIFO order for every iteration. The usual caveat from Go still applies: be mindful of loop-heavy defer usage because it allocates per iteration.
### Garbage Collection (GC)

View File

@@ -0,0 +1,22 @@
//go:build amd64
package main
import (
"unsafe"
_ "unsafe"
)
//go:linkname getsp llgo.stackSave
func getsp() unsafe.Pointer
//go:linkname asmFull llgo.asm
func asmFull(instruction string, regs map[string]any) uintptr { return 0 }
func main() {
sp := asmFull("movq %rsp, {}", nil)
if sp != uintptr(getsp()) {
panic("invalid stack pointer")
}
}

View File

@@ -0,0 +1,22 @@
//go:build arm64
package main
import (
"unsafe"
_ "unsafe"
)
//go:linkname getsp llgo.stackSave
func getsp() unsafe.Pointer
//go:linkname asmFull llgo.asm
func asmFull(instruction string, regs map[string]any) uintptr { return 0 }
func main() {
sp := asmFull("mov {}, sp", nil)
if sp != uintptr(getsp()) {
panic("invalid stack pointer")
}
}

View File

@@ -297,6 +297,12 @@ github_com_goplus_llgo_runtime_internal_clite_pthread_init(void) GO_SYMBOL_RENAM
void
github_com_goplus_llgo_runtime_internal_clite_pthread_sync_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/pthread/sync.init")
void
github_com_goplus_llgo_runtime_internal_clite_signal_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/signal.init")
void
github_com_goplus_llgo_runtime_internal_clite_tls_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/tls.init")
void
github_com_goplus_llgo_runtime_internal_runtime_goarch_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime/goarch.init")

356
_demo/go/gotoken/main.go Normal file
View File

@@ -0,0 +1,356 @@
package main
import (
"fmt"
"go/token"
)
func main() {
testPos()
testToken()
testFileSet()
testFile()
testPosition()
testTokenPrecedence()
testTokenKeywords()
testUtilityFunctions()
}
func testPos() {
fmt.Println("=== Test Pos ===")
pos1 := token.Pos(100)
pos2 := token.Pos(200)
if pos1 != 100 {
panic(fmt.Sprintf("Expected pos1 to be 100, got %d", pos1))
}
if pos2 != 200 {
panic(fmt.Sprintf("Expected pos2 to be 200, got %d", pos2))
}
fmt.Printf("Pos1: %d, Pos2: %d\n", pos1, pos2)
if !pos1.IsValid() {
panic("Expected pos1.IsValid() to be true")
}
fmt.Printf("Pos1.IsValid(): %v\n", pos1.IsValid())
noPos := token.NoPos
if noPos != 0 {
panic(fmt.Sprintf("Expected NoPos to be 0, got %d", noPos))
}
if noPos.IsValid() {
panic("Expected NoPos.IsValid() to be false")
}
fmt.Printf("NoPos: %d, IsValid: %v\n", noPos, noPos.IsValid())
fmt.Println("SUCCESS: Pos operations work correctly\n")
}
func testToken() {
fmt.Println("\n=== Test Token Types ===")
expectedStrings := map[token.Token]string{
token.ADD: "+",
token.SUB: "-",
token.MUL: "*",
token.QUO: "/",
token.LPAREN: "(",
token.RPAREN: ")",
token.EQL: "==",
token.NEQ: "!=",
}
for tok, expected := range expectedStrings {
if tok.String() != expected {
panic(fmt.Sprintf("Expected %v.String() to be %q, got %q", tok, expected, tok.String()))
}
}
tokens := []token.Token{
token.ILLEGAL,
token.EOF,
token.COMMENT,
token.IDENT,
token.INT,
token.FLOAT,
token.IMAG,
token.CHAR,
token.STRING,
token.ADD,
token.SUB,
token.MUL,
token.QUO,
token.REM,
token.AND,
token.OR,
token.XOR,
token.SHL,
token.SHR,
token.AND_NOT,
token.ADD_ASSIGN,
token.SUB_ASSIGN,
token.MUL_ASSIGN,
token.QUO_ASSIGN,
token.REM_ASSIGN,
token.AND_ASSIGN,
token.OR_ASSIGN,
token.XOR_ASSIGN,
token.SHL_ASSIGN,
token.SHR_ASSIGN,
token.AND_NOT_ASSIGN,
token.LAND,
token.LOR,
token.ARROW,
token.INC,
token.DEC,
token.EQL,
token.LSS,
token.GTR,
token.ASSIGN,
token.NOT,
token.NEQ,
token.LEQ,
token.GEQ,
token.DEFINE,
token.ELLIPSIS,
token.LPAREN,
token.LBRACK,
token.LBRACE,
token.COMMA,
token.PERIOD,
token.RPAREN,
token.RBRACK,
token.RBRACE,
token.SEMICOLON,
token.COLON,
}
for _, tok := range tokens {
fmt.Printf("Token: %s (String: %q)\n", tok, tok.String())
}
fmt.Println("SUCCESS: Token types work correctly\n")
}
func testTokenKeywords() {
fmt.Println("\n=== Test Keywords ===")
keywords := []token.Token{
token.BREAK,
token.CASE,
token.CHAN,
token.CONST,
token.CONTINUE,
token.DEFAULT,
token.DEFER,
token.ELSE,
token.FALLTHROUGH,
token.FOR,
token.FUNC,
token.GO,
token.GOTO,
token.IF,
token.IMPORT,
token.INTERFACE,
token.MAP,
token.PACKAGE,
token.RANGE,
token.RETURN,
token.SELECT,
token.STRUCT,
token.SWITCH,
token.TYPE,
token.VAR,
}
for _, kw := range keywords {
if !kw.IsKeyword() {
panic(fmt.Sprintf("Expected %s to be a keyword", kw))
}
fmt.Printf("Keyword: %s, IsKeyword: %v\n", kw, kw.IsKeyword())
}
if token.ADD.IsKeyword() {
panic("Expected ADD operator to not be a keyword")
}
if token.IDENT.IsKeyword() {
panic("Expected IDENT token to not be a keyword")
}
fmt.Println("SUCCESS: Keyword checks work correctly\n")
}
func testTokenPrecedence() {
fmt.Println("\n=== Test Token Precedence ===")
if token.MUL.Precedence() <= token.ADD.Precedence() {
panic("Expected MUL to have higher precedence than ADD")
}
if token.LAND.Precedence() <= token.LOR.Precedence() {
panic("Expected LAND to have higher precedence than LOR")
}
if token.MUL.Precedence() != token.QUO.Precedence() {
panic("Expected MUL and QUO to have same precedence")
}
if token.ADD.Precedence() != token.SUB.Precedence() {
panic("Expected ADD and SUB to have same precedence")
}
operators := []token.Token{
token.ADD,
token.SUB,
token.MUL,
token.QUO,
token.REM,
token.LAND,
token.LOR,
token.EQL,
token.LSS,
token.GTR,
}
for _, op := range operators {
fmt.Printf("Operator: %s, Precedence: %d\n", op, op.Precedence())
}
fmt.Println("SUCCESS: Precedence operations work correctly\n")
}
func testFileSet() {
fmt.Println("\n=== Test FileSet ===")
fset := token.NewFileSet()
file1 := fset.AddFile("file1.go", -1, 1000)
file2 := fset.AddFile("file2.go", -1, 2000)
fmt.Printf("Added file1: %s, Base: %d, Size: %d\n", file1.Name(), file1.Base(), file1.Size())
fmt.Printf("Added file2: %s, Base: %d, Size: %d\n", file2.Name(), file2.Base(), file2.Size())
pos := file1.Pos(100)
retrievedFile := fset.File(pos)
if retrievedFile != file1 {
panic("FileSet.File failed to retrieve correct file")
}
position := fset.Position(pos)
fmt.Printf("Position at offset 100: %s\n", position)
fmt.Println("SUCCESS: FileSet operations work correctly\n")
}
func testFile() {
fmt.Println("\n=== Test File ===")
fset := token.NewFileSet()
file := fset.AddFile("test.go", -1, 1000)
if file.Name() != "test.go" {
panic(fmt.Sprintf("Expected file name to be 'test.go', got %q", file.Name()))
}
if file.Size() != 1000 {
panic(fmt.Sprintf("Expected file size to be 1000, got %d", file.Size()))
}
file.AddLine(0)
file.AddLine(50)
file.AddLine(100)
if file.LineCount() != 3 {
panic(fmt.Sprintf("Expected line count to be 3, got %d", file.LineCount()))
}
fmt.Printf("File name: %s\n", file.Name())
fmt.Printf("File base: %d\n", file.Base())
fmt.Printf("File size: %d\n", file.Size())
fmt.Printf("File line count: %d\n", file.LineCount())
pos := file.Pos(50)
fmt.Printf("Pos at offset 50: %d\n", pos)
offset := file.Offset(pos)
if offset != 50 {
panic(fmt.Sprintf("Expected offset to be 50, got %d", offset))
}
fmt.Printf("Offset of pos: %d\n", offset)
line := file.Line(pos)
if line != 2 {
panic(fmt.Sprintf("Expected line to be 2, got %d", line))
}
fmt.Printf("Line number at pos: %d\n", line)
lineStart := file.LineStart(2)
fmt.Printf("Line 2 starts at pos: %d\n", lineStart)
position := file.Position(pos)
fmt.Printf("Position: %s\n", position)
fmt.Println("SUCCESS: File operations work correctly\n")
}
func testPosition() {
fmt.Println("\n=== Test Position ===" )
pos := token.Position{
Filename: "test.go",
Offset: 100,
Line: 5,
Column: 10,
}
if !pos.IsValid() {
panic("Expected valid position to be valid")
}
if pos.Filename != "test.go" {
panic(fmt.Sprintf("Expected filename to be 'test.go', got %q", pos.Filename))
}
if pos.Line != 5 {
panic(fmt.Sprintf("Expected line to be 5, got %d", pos.Line))
}
if pos.Column != 10 {
panic(fmt.Sprintf("Expected column to be 10, got %d", pos.Column))
}
fmt.Printf("Position: %s\n", pos.String())
fmt.Printf("Filename: %s, Line: %d, Column: %d, Offset: %d\n",
pos.Filename, pos.Line, pos.Column, pos.Offset)
fmt.Printf("IsValid: %v\n", pos.IsValid())
invalidPos := token.Position{}
if invalidPos.IsValid() {
panic("Expected empty position to be invalid")
}
fmt.Printf("Invalid position IsValid: %v\n", invalidPos.IsValid())
fmt.Println("SUCCESS: Position operations work correctly\n")
}
func testUtilityFunctions() {
fmt.Println("\n=== Test Utility Functions ===")
fmt.Printf("IsExported(\"Foo\"): %v\n", token.IsExported("Foo"))
fmt.Printf("IsExported(\"foo\"): %v\n", token.IsExported("foo"))
fmt.Printf("IsExported(\"_foo\"): %v\n", token.IsExported("_foo"))
fmt.Printf("IsIdentifier(\"foo\"): %v\n", token.IsIdentifier("foo"))
fmt.Printf("IsIdentifier(\"foo123\"): %v\n", token.IsIdentifier("foo123"))
fmt.Printf("IsIdentifier(\"123foo\"): %v\n", token.IsIdentifier("123foo"))
fmt.Printf("IsIdentifier(\"foo-bar\"): %v\n", token.IsIdentifier("foo-bar"))
fmt.Printf("IsKeyword(\"func\"): %v\n", token.IsKeyword("func"))
fmt.Printf("IsKeyword(\"if\"): %v\n", token.IsKeyword("if"))
fmt.Printf("IsKeyword(\"foo\"): %v\n", token.IsKeyword("foo"))
lookupFunc := token.Lookup("func")
fmt.Printf("Lookup(\"func\"): %s\n", lookupFunc)
lookupIdent := token.Lookup("myVar")
fmt.Printf("Lookup(\"myVar\"): %s\n", lookupIdent)
lookupFor := token.Lookup("for")
fmt.Printf("Lookup(\"for\"): %s\n", lookupFor)
fmt.Println("SUCCESS: Utility functions work correctly\n")
}

488
_demo/go/gotypes/main.go Normal file
View File

@@ -0,0 +1,488 @@
package main
import (
"fmt"
"go/token"
"go/types"
)
func main() {
testBasicTypes()
testObjects()
testScope()
testPackage()
testNamed()
testInterface()
testStruct()
testSignature()
testTuple()
testArray()
testSlice()
testPointer()
testMap()
testChan()
testTypeComparison()
testTypeChecking()
testStringFunctions()
testLookupFunctions()
testUtilityFunctions()
}
func testBasicTypes() {
fmt.Println("=== Test Basic Types ===")
intType := types.Typ[types.Int]
fmt.Printf("Int type: %v, Kind: %v\n", intType, intType.Kind())
if intType.Kind() != types.Int {
panic(fmt.Sprintf("Int type kind mismatch: expected %v, got %v", types.Int, intType.Kind()))
}
stringType := types.Typ[types.String]
fmt.Printf("String type: %v, Kind: %v\n", stringType, stringType.Kind())
if stringType.Kind() != types.String {
panic(fmt.Sprintf("String type kind mismatch: expected %v, got %v", types.String, stringType.Kind()))
}
boolType := types.Typ[types.Bool]
fmt.Printf("Bool type: %v, Kind: %v\n", boolType, boolType.Kind())
if boolType.Kind() != types.Bool {
panic(fmt.Sprintf("Bool type kind mismatch: expected %v, got %v", types.Bool, boolType.Kind()))
}
float64Type := types.Typ[types.Float64]
fmt.Printf("Float64 type: %v, Kind: %v\n", float64Type, float64Type.Kind())
if float64Type.Kind() != types.Float64 {
panic(fmt.Sprintf("Float64 type kind mismatch: expected %v, got %v", types.Float64, float64Type.Kind()))
}
fmt.Println("SUCCESS: Basic types work correctly\n")
}
func testObjects() {
fmt.Println("\n=== Test Objects (Var, Const, Func, TypeName) ===")
varObj := types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int])
fmt.Printf("Var: Name=%s, Type=%v\n", varObj.Name(), varObj.Type())
if varObj.Name() != "x" {
panic(fmt.Sprintf("Var name mismatch: expected x, got %s", varObj.Name()))
}
if varObj.Type() != types.Typ[types.Int] {
panic(fmt.Sprintf("Var type mismatch: expected int, got %v", varObj.Type()))
}
constObj := types.NewConst(token.NoPos, nil, "pi", types.Typ[types.Float64], nil)
fmt.Printf("Const: Name=%s, Type=%v\n", constObj.Name(), constObj.Type())
if constObj.Name() != "pi" {
panic(fmt.Sprintf("Const name mismatch: expected pi, got %s", constObj.Name()))
}
if constObj.Type() != types.Typ[types.Float64] {
panic(fmt.Sprintf("Const type mismatch: expected float64, got %v", constObj.Type()))
}
sig := types.NewSignatureType(nil, nil, nil, nil, nil, false)
funcObj := types.NewFunc(token.NoPos, nil, "foo", sig)
fmt.Printf("Func: Name=%s, Type=%v\n", funcObj.Name(), funcObj.Type())
if funcObj.Name() != "foo" {
panic(fmt.Sprintf("Func name mismatch: expected foo, got %s", funcObj.Name()))
}
typeObj := types.NewTypeName(token.NoPos, nil, "MyInt", types.Typ[types.Int])
fmt.Printf("TypeName: Name=%s, Type=%v\n", typeObj.Name(), typeObj.Type())
if typeObj.Name() != "MyInt" {
panic(fmt.Sprintf("TypeName name mismatch: expected MyInt, got %s", typeObj.Name()))
}
var obj types.Object = varObj
if obj.Name() != "x" {
panic("Object interface conversion failed")
}
fmt.Println("SUCCESS: Object interface works correctly\n")
}
func testScope() {
fmt.Println("\n=== Test Scope ===")
scope := types.NewScope(nil, 0, 0, "test")
obj := types.NewVar(0, nil, "x", types.Typ[types.Int])
scope.Insert(obj)
result := scope.Lookup("x")
if result != obj {
panic("Scope.Lookup failed")
}
names := scope.Names()
if len(names) != 1 || names[0] != "x" {
panic("Scope.Names failed")
}
num := scope.Len()
if num != 1 {
panic("Scope.Len failed")
}
fmt.Printf("Scope contains %d object(s): %v\n", num, names)
fmt.Println("SUCCESS: Scope operations work correctly\n")
}
func testPackage() {
fmt.Println("\n=== Test Package ===")
pkg := types.NewPackage("example.com/test", "test")
fmt.Printf("Package: Path=%s, Name=%s\n", pkg.Path(), pkg.Name())
if pkg.Path() != "example.com/test" {
panic(fmt.Sprintf("Package path mismatch: expected example.com/test, got %s", pkg.Path()))
}
if pkg.Name() != "test" {
panic(fmt.Sprintf("Package name mismatch: expected test, got %s", pkg.Name()))
}
scope := pkg.Scope()
if scope == nil {
panic("Package.Scope returned nil")
}
varObj := types.NewVar(token.NoPos, pkg, "x", types.Typ[types.Int])
scope.Insert(varObj)
result := pkg.Scope().Lookup("x")
if result != varObj {
panic("Package scope lookup failed")
}
fmt.Println("SUCCESS: Package operations work correctly\n")
}
func testNamed() {
fmt.Println("\n=== Test Named Types ===")
pkg := types.NewPackage("example.com/test", "test")
typeName := types.NewTypeName(token.NoPos, pkg, "MyInt", nil)
named := types.NewNamed(typeName, types.Typ[types.Int], nil)
fmt.Printf("Named type: %v, Underlying: %v\n", named, named.Underlying())
if named.Obj() != typeName {
panic("Named.Obj failed")
}
fmt.Println("SUCCESS: Named type operations work correctly\n")
}
func testInterface() {
fmt.Println("\n=== Test Interface ===")
pkg := types.NewPackage("example.com/test", "test")
posMethod := types.NewFunc(token.NoPos, pkg, "Pos", types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewVar(0, pkg, "", types.Typ[types.Int])), false))
endMethod := types.NewFunc(token.NoPos, pkg, "End", types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(types.NewVar(0, pkg, "", types.Typ[types.Int])), false))
methods := []*types.Func{posMethod, endMethod}
iface := types.NewInterfaceType(methods, nil)
iface.Complete()
fmt.Printf("Interface with %d methods\n", iface.NumMethods())
if iface.NumMethods() != 2 {
panic(fmt.Sprintf("Interface method count mismatch: expected 2, got %d", iface.NumMethods()))
}
method := iface.Method(0)
fmt.Printf("Method 0: %s\n", method.Name())
if method.Name() != "End" && method.Name() != "Pos" {
panic(fmt.Sprintf("Unexpected method name: %s", method.Name()))
}
fmt.Println("SUCCESS: Interface operations work correctly\n")
}
func testStruct() {
fmt.Println("\n=== Test Struct ===")
fields := []*types.Var{
types.NewField(token.NoPos, nil, "X", types.Typ[types.Int], false),
types.NewField(token.NoPos, nil, "Y", types.Typ[types.String], false),
}
structType := types.NewStruct(fields, nil)
fmt.Printf("Struct with %d fields\n", structType.NumFields())
if structType.NumFields() != 2 {
panic(fmt.Sprintf("Struct field count mismatch: expected 2, got %d", structType.NumFields()))
}
field0 := structType.Field(0)
fmt.Printf("Field 0: Name=%s, Type=%v\n", field0.Name(), field0.Type())
if field0.Name() != "X" {
panic(fmt.Sprintf("Field 0 name mismatch: expected X, got %s", field0.Name()))
}
if field0.Type() != types.Typ[types.Int] {
panic(fmt.Sprintf("Field 0 type mismatch: expected int, got %v", field0.Type()))
}
fmt.Println("SUCCESS: Struct operations work correctly\n")
}
func testSignature() {
fmt.Println("\n=== Test Signature ===")
params := types.NewTuple(
types.NewVar(token.NoPos, nil, "x", types.Typ[types.Int]),
types.NewVar(token.NoPos, nil, "y", types.Typ[types.String]),
)
results := types.NewTuple(
types.NewVar(token.NoPos, nil, "", types.Typ[types.Bool]),
)
sig := types.NewSignatureType(nil, nil, nil, params, results, false)
fmt.Printf("Signature: %d params, %d results\n", sig.Params().Len(), sig.Results().Len())
if sig.Params().Len() != 2 {
panic(fmt.Sprintf("Signature param count mismatch: expected 2, got %d", sig.Params().Len()))
}
if sig.Results().Len() != 1 {
panic(fmt.Sprintf("Signature result count mismatch: expected 1, got %d", sig.Results().Len()))
}
param0 := sig.Params().At(0)
fmt.Printf("Param 0: Name=%s, Type=%v\n", param0.Name(), param0.Type())
if param0.Name() != "x" {
panic(fmt.Sprintf("Param 0 name mismatch: expected x, got %s", param0.Name()))
}
if param0.Type() != types.Typ[types.Int] {
panic(fmt.Sprintf("Param 0 type mismatch: expected int, got %v", param0.Type()))
}
fmt.Println("SUCCESS: Signature operations work correctly\n")
}
func testTuple() {
fmt.Println("\n=== Test Tuple ===")
tuple := types.NewTuple(
types.NewVar(token.NoPos, nil, "a", types.Typ[types.Int]),
types.NewVar(token.NoPos, nil, "b", types.Typ[types.String]),
)
fmt.Printf("Tuple length: %d\n", tuple.Len())
if tuple.Len() != 2 {
panic(fmt.Sprintf("Tuple length mismatch: expected 2, got %d", tuple.Len()))
}
var0 := tuple.At(0)
fmt.Printf("Element 0: Name=%s, Type=%v\n", var0.Name(), var0.Type())
if var0.Name() != "a" {
panic(fmt.Sprintf("Tuple element 0 name mismatch: expected a, got %s", var0.Name()))
}
if var0.Type() != types.Typ[types.Int] {
panic(fmt.Sprintf("Tuple element 0 type mismatch: expected int, got %v", var0.Type()))
}
fmt.Println("SUCCESS: Tuple operations work correctly\n")
}
func testArray() {
fmt.Println("\n=== Test Array ===")
arrayType := types.NewArray(types.Typ[types.Int], 10)
fmt.Printf("Array type: %v, Elem: %v, Len: %d\n", arrayType, arrayType.Elem(), arrayType.Len())
if arrayType.Len() != 10 {
panic(fmt.Sprintf("Array length mismatch: expected 10, got %d", arrayType.Len()))
}
if arrayType.Elem() != types.Typ[types.Int] {
panic(fmt.Sprintf("Array element type mismatch: expected int, got %v", arrayType.Elem()))
}
fmt.Println("SUCCESS: Array operations work correctly\n")
}
func testSlice() {
fmt.Println("\n=== Test Slice ===")
sliceType := types.NewSlice(types.Typ[types.String])
fmt.Printf("Slice type: %v, Elem: %v\n", sliceType, sliceType.Elem())
if sliceType.Elem() != types.Typ[types.String] {
panic(fmt.Sprintf("Slice element type mismatch: expected string, got %v", sliceType.Elem()))
}
fmt.Println("SUCCESS: Slice operations work correctly\n")
}
func testPointer() {
fmt.Println("\n=== Test Pointer ===")
ptrType := types.NewPointer(types.Typ[types.Int])
fmt.Printf("Pointer type: %v, Elem: %v\n", ptrType, ptrType.Elem())
if ptrType.Elem() != types.Typ[types.Int] {
panic(fmt.Sprintf("Pointer element type mismatch: expected int, got %v", ptrType.Elem()))
}
fmt.Println("SUCCESS: Pointer operations work correctly\n")
}
func testMap() {
fmt.Println("\n=== Test Map ===")
mapType := types.NewMap(types.Typ[types.String], types.Typ[types.Int])
fmt.Printf("Map type: %v, Key: %v, Elem: %v\n", mapType, mapType.Key(), mapType.Elem())
if mapType.Key() != types.Typ[types.String] {
panic(fmt.Sprintf("Map key type mismatch: expected string, got %v", mapType.Key()))
}
if mapType.Elem() != types.Typ[types.Int] {
panic(fmt.Sprintf("Map element type mismatch: expected int, got %v", mapType.Elem()))
}
fmt.Println("SUCCESS: Map operations work correctly\n")
}
func testChan() {
fmt.Println("\n=== Test Chan ===" )
chanType := types.NewChan(types.SendRecv, types.Typ[types.Int])
fmt.Printf("Chan type: %v, Dir: %v, Elem: %v\n", chanType, chanType.Dir(), chanType.Elem())
if chanType.Dir() != types.SendRecv {
panic(fmt.Sprintf("Chan direction mismatch: expected SendRecv, got %v", chanType.Dir()))
}
if chanType.Elem() != types.Typ[types.Int] {
panic(fmt.Sprintf("Chan element type mismatch: expected int, got %v", chanType.Elem()))
}
sendChan := types.NewChan(types.SendOnly, types.Typ[types.String])
fmt.Printf("SendOnly chan: %v, Dir: %v\n", sendChan, sendChan.Dir())
if sendChan.Dir() != types.SendOnly {
panic(fmt.Sprintf("SendOnly chan direction mismatch: expected SendOnly, got %v", sendChan.Dir()))
}
recvChan := types.NewChan(types.RecvOnly, types.Typ[types.Bool])
fmt.Printf("RecvOnly chan: %v, Dir: %v\n", recvChan, recvChan.Dir())
if recvChan.Dir() != types.RecvOnly {
panic(fmt.Sprintf("RecvOnly chan direction mismatch: expected RecvOnly, got %v", recvChan.Dir()))
}
fmt.Println("SUCCESS: Chan operations work correctly\n")
}
func testTypeComparison() {
fmt.Println("\n=== Test Type Comparison Functions ===")
t1 := types.Typ[types.Int]
t2 := types.Typ[types.Int]
t3 := types.Typ[types.String]
if !types.Identical(t1, t2) {
panic("Identical failed: int should be identical to int")
}
fmt.Printf("Identical(int, int): %v\n", types.Identical(t1, t2))
fmt.Printf("Identical(int, string): %v\n", types.Identical(t1, t3))
if !types.AssignableTo(t1, t2) {
panic("AssignableTo failed")
}
fmt.Printf("AssignableTo(int, int): %v\n", types.AssignableTo(t1, t2))
fmt.Printf("AssignableTo(int, string): %v\n", types.AssignableTo(t1, t3))
fmt.Printf("Comparable(int): %v\n", types.Comparable(t1))
fmt.Printf("Comparable(string): %v\n", types.Comparable(t3))
fmt.Printf("ConvertibleTo(int, int): %v\n", types.ConvertibleTo(t1, t2))
fmt.Println("SUCCESS: Type comparison functions work correctly\n")
}
func testTypeChecking() {
fmt.Println("\n=== Test Type Checking Functions ===")
pkg := types.NewPackage("example.com/test", "test")
m1 := types.NewFunc(token.NoPos, pkg, "Method1", types.NewSignatureType(nil, nil, nil, nil, nil, false))
m2 := types.NewFunc(token.NoPos, pkg, "Method2", types.NewSignatureType(nil, nil, nil, nil, nil, false))
iface := types.NewInterfaceType([]*types.Func{m1, m2}, nil)
iface.Complete()
fields := []*types.Var{
types.NewField(token.NoPos, nil, "x", types.Typ[types.Int], false),
}
structType := types.NewStruct(fields, nil)
fmt.Printf("Implements(struct, interface): %v\n", types.Implements(structType, iface))
fmt.Printf("Implements(int, interface): %v\n", types.Implements(types.Typ[types.Int], iface))
emptyIface := types.NewInterfaceType(nil, nil)
emptyIface.Complete()
fmt.Printf("Implements(int, empty interface): %v\n", types.Implements(types.Typ[types.Int], emptyIface))
fmt.Printf("AssertableTo(interface, int): %v\n", types.AssertableTo(iface, types.Typ[types.Int]))
fmt.Println("SUCCESS: Type checking functions work correctly\n")
}
func testStringFunctions() {
fmt.Println("\n=== Test String Functions ===")
pkg := types.NewPackage("example.com/test", "test")
varObj := types.NewVar(token.NoPos, pkg, "myVar", types.Typ[types.Int])
objStr := types.ObjectString(varObj, nil)
fmt.Printf("ObjectString: %s\n", objStr)
objStrQual := types.ObjectString(varObj, types.RelativeTo(pkg))
fmt.Printf("ObjectString (qualified): %s\n", objStrQual)
typeStr := types.TypeString(types.Typ[types.Int], nil)
fmt.Printf("TypeString(int): %s\n", typeStr)
sliceType := types.NewSlice(types.Typ[types.String])
sliceStr := types.TypeString(sliceType, nil)
fmt.Printf("TypeString([]string): %s\n", sliceStr)
fmt.Println("SUCCESS: String functions work correctly\n")
}
func testLookupFunctions() {
fmt.Println("\n=== Test Lookup Functions ===")
pkg := types.NewPackage("example.com/test", "test")
fields := []*types.Var{
types.NewField(token.NoPos, pkg, "X", types.Typ[types.Int], false),
types.NewField(token.NoPos, pkg, "Y", types.Typ[types.String], false),
}
structType := types.NewStruct(fields, nil)
obj, index, indirect := types.LookupFieldOrMethod(structType, false, pkg, "X")
if obj == nil {
panic("LookupFieldOrMethod failed to find X")
}
fmt.Printf("LookupFieldOrMethod found: %s, index: %v, indirect: %v\n", obj.Name(), index, indirect)
obj2, index2, indirect2 := types.LookupFieldOrMethod(structType, false, pkg, "NonExistent")
fmt.Printf("LookupFieldOrMethod (non-existent): found=%v, index=%v, indirect=%v\n", obj2 != nil, index2, indirect2)
mset := types.NewMethodSet(structType)
fmt.Printf("NewMethodSet: %d methods\n", mset.Len())
fmt.Println("SUCCESS: Lookup functions work correctly\n")
}
func testUtilityFunctions() {
fmt.Println("\n=== Test Utility Functions ===")
pkg := types.NewPackage("example.com/test", "test")
iface := types.NewInterfaceType(nil, nil)
iface.Complete()
fmt.Printf("IsInterface(interface): %v\n", types.IsInterface(iface))
fmt.Printf("IsInterface(int): %v\n", types.IsInterface(types.Typ[types.Int]))
typedNil := types.Typ[types.UntypedNil]
defaultType := types.Default(typedNil)
fmt.Printf("Default(UntypedNil): %v\n", defaultType)
intDefault := types.Default(types.Typ[types.Int])
fmt.Printf("Default(int): %v\n", intDefault)
idStr := types.Id(pkg, "MyType")
fmt.Printf("Id(pkg, \"MyType\"): %s\n", idStr)
fmt.Println("SUCCESS: Utility functions work correctly\n")
}

View File

@@ -0,0 +1,25 @@
package geometry1370
type Shape interface {
Area() float64
validate() bool
setID(int)
}
type Rectangle struct {
Width, Height float64
id int
}
func (r *Rectangle) Area() float64 { return r.Width * r.Height }
func (r *Rectangle) validate() bool { return r.Width > 0 && r.Height > 0 }
func (r *Rectangle) setID(id int) { r.id = id }
func (r *Rectangle) GetID() int { return r.id }
func NewRectangle(width, height float64) *Rectangle {
return &Rectangle{Width: width, Height: height}
}
func RegisterShape(s Shape, id int) {
s.setID(id)
}

View File

@@ -0,0 +1,90 @@
; ModuleID = 'github.com/goplus/llgo/cl/_testdata/geometry1370'
source_filename = "github.com/goplus/llgo/cl/_testdata/geometry1370"
%"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle" = type { double, double, i64 }
%"github.com/goplus/llgo/runtime/internal/runtime.iface" = type { ptr, ptr }
@"github.com/goplus/llgo/cl/_testdata/geometry1370.init$guard" = global i1 false, align 1
define ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.NewRectangle"(double %0, double %1) {
_llgo_0:
%2 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocZ"(i64 24)
%3 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %2, i32 0, i32 0
%4 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %2, i32 0, i32 1
store double %0, ptr %3, align 8
store double %1, ptr %4, align 8
ret ptr %2
}
define double @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).Area"(ptr %0) {
_llgo_0:
%1 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %0, i32 0, i32 0
%2 = load double, ptr %1, align 8
%3 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %0, i32 0, i32 1
%4 = load double, ptr %3, align 8
%5 = fmul double %2, %4
ret double %5
}
define i64 @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).GetID"(ptr %0) {
_llgo_0:
%1 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %0, i32 0, i32 2
%2 = load i64, ptr %1, align 4
ret i64 %2
}
define void @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).setID"(ptr %0, i64 %1) {
_llgo_0:
%2 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %0, i32 0, i32 2
store i64 %1, ptr %2, align 4
ret void
}
define i1 @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).validate"(ptr %0) {
_llgo_0:
%1 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %0, i32 0, i32 0
%2 = load double, ptr %1, align 8
%3 = fcmp ogt double %2, 0.000000e+00
br i1 %3, label %_llgo_1, label %_llgo_2
_llgo_1: ; preds = %_llgo_0
%4 = getelementptr inbounds %"github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", ptr %0, i32 0, i32 1
%5 = load double, ptr %4, align 8
%6 = fcmp ogt double %5, 0.000000e+00
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
%7 = phi i1 [ false, %_llgo_0 ], [ %6, %_llgo_1 ]
ret i1 %7
}
define void @"github.com/goplus/llgo/cl/_testdata/geometry1370.RegisterShape"(%"github.com/goplus/llgo/runtime/internal/runtime.iface" %0, i64 %1) {
_llgo_0:
%2 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData"(%"github.com/goplus/llgo/runtime/internal/runtime.iface" %0)
%3 = extractvalue %"github.com/goplus/llgo/runtime/internal/runtime.iface" %0, 0
%4 = getelementptr ptr, ptr %3, i64 4
%5 = load ptr, ptr %4, align 8
%6 = insertvalue { ptr, ptr } undef, ptr %5, 0
%7 = insertvalue { ptr, ptr } %6, ptr %2, 1
%8 = extractvalue { ptr, ptr } %7, 1
%9 = extractvalue { ptr, ptr } %7, 0
call void %9(ptr %8, i64 %1)
ret void
}
define void @"github.com/goplus/llgo/cl/_testdata/geometry1370.init"() {
_llgo_0:
%0 = load i1, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocZ"(i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData"(%"github.com/goplus/llgo/runtime/internal/runtime.iface")

View File

@@ -0,0 +1,51 @@
package main
func main() {
for _, label := range complexOrder() {
println(label)
}
}
func complexOrder() (res []string) {
record := func(label string) { res = append(res, label) }
defer record(label1("cleanup-final", 0))
defer record(label1("cleanup-before-loop", 0))
for i := 0; i < 2; i++ {
defer record(label1("exit-outer", i))
for j := 0; j < 2; j++ {
if j == 0 {
defer record(label2("branch-even", i, j))
} else {
defer record(label2("branch-odd", i, j))
}
for k := 0; k < 2; k++ {
nested := label3("nested", i, j, k)
defer record(nested)
if k == 1 {
defer record(label3("nested-tail", i, j, k))
}
}
}
}
defer record(label1("post-loop", 0))
return
}
func label1(prefix string, a int) string {
return prefix + "-" + digit(a)
}
func label2(prefix string, a, b int) string {
return prefix + "-" + digit(a) + "-" + digit(b)
}
func label3(prefix string, a, b, c int) string {
return prefix + "-" + digit(a) + "-" + digit(b) + "-" + digit(c)
}
func digit(n int) string {
return string(rune('0' + n))
}

View File

@@ -0,0 +1 @@
;

View File

@@ -0,0 +1,7 @@
package main
func main() {
for i := 0; i < 3; i++ {
defer println("loop", i)
}
}

View File

@@ -0,0 +1 @@
;

View File

@@ -0,0 +1,11 @@
package main
import (
"github.com/goplus/llgo/cl/_testdata/geometry1370"
)
func main() {
rect := geometry1370.NewRectangle(5.0, 3.0)
geometry1370.RegisterShape(rect, 42)
println("ID:", rect.GetID())
}

View File

@@ -0,0 +1,333 @@
; ModuleID = 'github.com/goplus/llgo/cl/_testgo/interface1370'
source_filename = "github.com/goplus/llgo/cl/_testgo/interface1370"
%"github.com/goplus/llgo/runtime/internal/runtime.iface" = type { ptr, ptr }
%"github.com/goplus/llgo/runtime/internal/runtime.String" = type { ptr, i64 }
%"github.com/goplus/llgo/runtime/abi.StructField" = type { %"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, i64, %"github.com/goplus/llgo/runtime/internal/runtime.String", i1 }
%"github.com/goplus/llgo/runtime/internal/runtime.Slice" = type { ptr, i64, i64 }
%"github.com/goplus/llgo/runtime/abi.Method" = type { %"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, ptr, ptr }
%"github.com/goplus/llgo/runtime/abi.Imethod" = type { %"github.com/goplus/llgo/runtime/internal/runtime.String", ptr }
@"github.com/goplus/llgo/cl/_testgo/interface1370.init$guard" = global i1 false, align 1
@"_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle" = linkonce global ptr null, align 8
@0 = private unnamed_addr constant [48 x i8] c"github.com/goplus/llgo/cl/_testdata/geometry1370", align 1
@1 = private unnamed_addr constant [9 x i8] c"Rectangle", align 1
@_llgo_float64 = linkonce global ptr null, align 8
@_llgo_int = linkonce global ptr null, align 8
@"github.com/goplus/llgo/cl/_testgo/interface1370.struct$EuRbjzGGO7GwkW6RxZGl-8lEjTdEMzAFD8LnY_SpVoQ" = linkonce global ptr null, align 8
@2 = private unnamed_addr constant [5 x i8] c"Width", align 1
@3 = private unnamed_addr constant [6 x i8] c"Height", align 1
@4 = private unnamed_addr constant [2 x i8] c"id", align 1
@5 = private unnamed_addr constant [47 x i8] c"github.com/goplus/llgo/cl/_testgo/interface1370", align 1
@6 = private unnamed_addr constant [4 x i8] c"Area", align 1
@"_llgo_func$UYiLlmcWxoOKZPPzvR4LByitNeKoVGoTrB_5ubdOWW8" = linkonce global ptr null, align 8
@7 = private unnamed_addr constant [5 x i8] c"GetID", align 1
@"_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA" = linkonce global ptr null, align 8
@8 = private unnamed_addr constant [5 x i8] c"setID", align 1
@9 = private unnamed_addr constant [54 x i8] c"github.com/goplus/llgo/cl/_testdata/geometry1370.setID", align 1
@"_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA" = linkonce global ptr null, align 8
@10 = private unnamed_addr constant [8 x i8] c"validate", align 1
@11 = private unnamed_addr constant [57 x i8] c"github.com/goplus/llgo/cl/_testdata/geometry1370.validate", align 1
@_llgo_bool = linkonce global ptr null, align 8
@"_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk" = linkonce global ptr null, align 8
@"*_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle" = linkonce global ptr null, align 8
@"github.com/goplus/llgo/cl/_testgo/interface1370.iface$OopIVfjRcxQr1gmJyGi5G7hHt__vH05AREEM7PthH9o" = linkonce global ptr null, align 8
@12 = private unnamed_addr constant [3 x i8] c"ID:", align 1
define void @"github.com/goplus/llgo/cl/_testgo/interface1370.init"() {
_llgo_0:
%0 = load i1, ptr @"github.com/goplus/llgo/cl/_testgo/interface1370.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"github.com/goplus/llgo/cl/_testgo/interface1370.init$guard", align 1
call void @"github.com/goplus/llgo/cl/_testdata/geometry1370.init"()
call void @"github.com/goplus/llgo/cl/_testgo/interface1370.init$after"()
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define void @"github.com/goplus/llgo/cl/_testgo/interface1370.main"() {
_llgo_0:
%0 = call ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.NewRectangle"(double 5.000000e+00, double 3.000000e+00)
%1 = load ptr, ptr @"_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", align 8
%2 = load ptr, ptr @"*_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", align 8
%3 = load ptr, ptr @"_llgo_func$UYiLlmcWxoOKZPPzvR4LByitNeKoVGoTrB_5ubdOWW8", align 8
%4 = load ptr, ptr @"_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA", align 8
%5 = load ptr, ptr @"_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk", align 8
%6 = load ptr, ptr @"github.com/goplus/llgo/cl/_testgo/interface1370.iface$OopIVfjRcxQr1gmJyGi5G7hHt__vH05AREEM7PthH9o", align 8
%7 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.NewItab"(ptr %6, ptr %2)
%8 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.iface" undef, ptr %7, 0
%9 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.iface" %8, ptr %0, 1
call void @"github.com/goplus/llgo/cl/_testdata/geometry1370.RegisterShape"(%"github.com/goplus/llgo/runtime/internal/runtime.iface" %9, i64 42)
%10 = call i64 @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).GetID"(ptr %0)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @12, i64 3 })
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 32)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintInt"(i64 %10)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10)
ret void
}
declare void @"github.com/goplus/llgo/cl/_testdata/geometry1370.init"()
declare ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.NewRectangle"(double, double)
declare void @"github.com/goplus/llgo/cl/_testdata/geometry1370.RegisterShape"(%"github.com/goplus/llgo/runtime/internal/runtime.iface", i64)
define void @"github.com/goplus/llgo/cl/_testgo/interface1370.init$after"() {
_llgo_0:
%0 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.NewNamed"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 48 }, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 9 }, i64 25, i64 24, i64 0, i64 4)
%1 = load ptr, ptr @"_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", align 8
%2 = icmp eq ptr %1, null
br i1 %2, label %_llgo_1, label %_llgo_2
_llgo_1: ; preds = %_llgo_0
store ptr %0, ptr @"_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", align 8
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
%3 = load ptr, ptr @_llgo_float64, align 8
%4 = icmp eq ptr %3, null
br i1 %4, label %_llgo_3, label %_llgo_4
_llgo_3: ; preds = %_llgo_2
%5 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 46)
store ptr %5, ptr @_llgo_float64, align 8
br label %_llgo_4
_llgo_4: ; preds = %_llgo_3, %_llgo_2
%6 = load ptr, ptr @_llgo_float64, align 8
%7 = load ptr, ptr @_llgo_int, align 8
%8 = icmp eq ptr %7, null
br i1 %8, label %_llgo_5, label %_llgo_6
_llgo_5: ; preds = %_llgo_4
%9 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 34)
store ptr %9, ptr @_llgo_int, align 8
br label %_llgo_6
_llgo_6: ; preds = %_llgo_5, %_llgo_4
%10 = load ptr, ptr @_llgo_int, align 8
%11 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 46)
%12 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @2, i64 5 }, ptr %11, i64 0, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%13 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 46)
%14 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @3, i64 6 }, ptr %13, i64 8, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%15 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 34)
%16 = call %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @4, i64 2 }, ptr %15, i64 16, %"github.com/goplus/llgo/runtime/internal/runtime.String" zeroinitializer, i1 false)
%17 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 168)
%18 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %17, i64 0
store %"github.com/goplus/llgo/runtime/abi.StructField" %12, ptr %18, align 8
%19 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %17, i64 1
store %"github.com/goplus/llgo/runtime/abi.StructField" %14, ptr %19, align 8
%20 = getelementptr %"github.com/goplus/llgo/runtime/abi.StructField", ptr %17, i64 2
store %"github.com/goplus/llgo/runtime/abi.StructField" %16, ptr %20, align 8
%21 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %17, 0
%22 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %21, i64 3, 1
%23 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %22, i64 3, 2
%24 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Struct"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @5, i64 47 }, i64 24, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %23)
store ptr %24, ptr @"github.com/goplus/llgo/cl/_testgo/interface1370.struct$EuRbjzGGO7GwkW6RxZGl-8lEjTdEMzAFD8LnY_SpVoQ", align 8
%25 = load ptr, ptr @"github.com/goplus/llgo/cl/_testgo/interface1370.struct$EuRbjzGGO7GwkW6RxZGl-8lEjTdEMzAFD8LnY_SpVoQ", align 8
br i1 %2, label %_llgo_7, label %_llgo_8
_llgo_7: ; preds = %_llgo_6
%26 = load ptr, ptr @"_llgo_func$UYiLlmcWxoOKZPPzvR4LByitNeKoVGoTrB_5ubdOWW8", align 8
%27 = icmp eq ptr %26, null
br i1 %27, label %_llgo_9, label %_llgo_10
_llgo_8: ; preds = %_llgo_18, %_llgo_6
%28 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.NewNamed"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @0, i64 48 }, %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @1, i64 9 }, i64 25, i64 24, i64 0, i64 4)
%29 = load ptr, ptr @"*_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", align 8
%30 = icmp eq ptr %29, null
br i1 %30, label %_llgo_19, label %_llgo_20
_llgo_9: ; preds = %_llgo_7
%31 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%32 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %31, 0
%33 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %32, i64 0, 1
%34 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %33, i64 0, 2
%35 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 46)
%36 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8)
%37 = getelementptr ptr, ptr %36, i64 0
store ptr %35, ptr %37, align 8
%38 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %36, 0
%39 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %38, i64 1, 1
%40 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %39, i64 1, 2
%41 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Func"(%"github.com/goplus/llgo/runtime/internal/runtime.Slice" %34, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %40, i1 false)
call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %41)
store ptr %41, ptr @"_llgo_func$UYiLlmcWxoOKZPPzvR4LByitNeKoVGoTrB_5ubdOWW8", align 8
br label %_llgo_10
_llgo_10: ; preds = %_llgo_9, %_llgo_7
%42 = load ptr, ptr @"_llgo_func$UYiLlmcWxoOKZPPzvR4LByitNeKoVGoTrB_5ubdOWW8", align 8
%43 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @6, i64 4 }, ptr undef, ptr undef, ptr undef }, ptr %42, 1
%44 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %43, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).Area", 2
%45 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %44, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).Area", 3
%46 = load ptr, ptr @"_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA", align 8
%47 = icmp eq ptr %46, null
br i1 %47, label %_llgo_11, label %_llgo_12
_llgo_11: ; preds = %_llgo_10
%48 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%49 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %48, 0
%50 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %49, i64 0, 1
%51 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %50, i64 0, 2
%52 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 34)
%53 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8)
%54 = getelementptr ptr, ptr %53, i64 0
store ptr %52, ptr %54, align 8
%55 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %53, 0
%56 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %55, i64 1, 1
%57 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %56, i64 1, 2
%58 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Func"(%"github.com/goplus/llgo/runtime/internal/runtime.Slice" %51, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %57, i1 false)
call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %58)
store ptr %58, ptr @"_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA", align 8
br label %_llgo_12
_llgo_12: ; preds = %_llgo_11, %_llgo_10
%59 = load ptr, ptr @"_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA", align 8
%60 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @7, i64 5 }, ptr undef, ptr undef, ptr undef }, ptr %59, 1
%61 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %60, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).GetID", 2
%62 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %61, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).GetID", 3
%63 = load ptr, ptr @"_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA", align 8
%64 = icmp eq ptr %63, null
br i1 %64, label %_llgo_13, label %_llgo_14
_llgo_13: ; preds = %_llgo_12
%65 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 34)
%66 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8)
%67 = getelementptr ptr, ptr %66, i64 0
store ptr %65, ptr %67, align 8
%68 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %66, 0
%69 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %68, i64 1, 1
%70 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %69, i64 1, 2
%71 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%72 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %71, 0
%73 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %72, i64 0, 1
%74 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %73, i64 0, 2
%75 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Func"(%"github.com/goplus/llgo/runtime/internal/runtime.Slice" %70, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %74, i1 false)
call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %75)
store ptr %75, ptr @"_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA", align 8
br label %_llgo_14
_llgo_14: ; preds = %_llgo_13, %_llgo_12
%76 = load ptr, ptr @"_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA", align 8
%77 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @9, i64 54 }, ptr undef, ptr undef, ptr undef }, ptr %76, 1
%78 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %77, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).setID", 2
%79 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %78, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).setID", 3
%80 = load ptr, ptr @_llgo_bool, align 8
%81 = icmp eq ptr %80, null
br i1 %81, label %_llgo_15, label %_llgo_16
_llgo_15: ; preds = %_llgo_14
%82 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 33)
store ptr %82, ptr @_llgo_bool, align 8
br label %_llgo_16
_llgo_16: ; preds = %_llgo_15, %_llgo_14
%83 = load ptr, ptr @_llgo_bool, align 8
%84 = load ptr, ptr @"_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk", align 8
%85 = icmp eq ptr %84, null
br i1 %85, label %_llgo_17, label %_llgo_18
_llgo_17: ; preds = %_llgo_16
%86 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 0)
%87 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %86, 0
%88 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %87, i64 0, 1
%89 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %88, i64 0, 2
%90 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64 33)
%91 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 8)
%92 = getelementptr ptr, ptr %91, i64 0
store ptr %90, ptr %92, align 8
%93 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %91, 0
%94 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %93, i64 1, 1
%95 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %94, i64 1, 2
%96 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Func"(%"github.com/goplus/llgo/runtime/internal/runtime.Slice" %89, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %95, i1 false)
call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %96)
store ptr %96, ptr @"_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk", align 8
br label %_llgo_18
_llgo_18: ; preds = %_llgo_17, %_llgo_16
%97 = load ptr, ptr @"_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk", align 8
%98 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @11, i64 57 }, ptr undef, ptr undef, ptr undef }, ptr %97, 1
%99 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %98, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).validate", 2
%100 = insertvalue %"github.com/goplus/llgo/runtime/abi.Method" %99, ptr @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).validate", 3
%101 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 160)
%102 = getelementptr %"github.com/goplus/llgo/runtime/abi.Method", ptr %101, i64 0
store %"github.com/goplus/llgo/runtime/abi.Method" %45, ptr %102, align 8
%103 = getelementptr %"github.com/goplus/llgo/runtime/abi.Method", ptr %101, i64 1
store %"github.com/goplus/llgo/runtime/abi.Method" %62, ptr %103, align 8
%104 = getelementptr %"github.com/goplus/llgo/runtime/abi.Method", ptr %101, i64 2
store %"github.com/goplus/llgo/runtime/abi.Method" %79, ptr %104, align 8
%105 = getelementptr %"github.com/goplus/llgo/runtime/abi.Method", ptr %101, i64 3
store %"github.com/goplus/llgo/runtime/abi.Method" %100, ptr %105, align 8
%106 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %101, 0
%107 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %106, i64 4, 1
%108 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %107, i64 4, 2
call void @"github.com/goplus/llgo/runtime/internal/runtime.InitNamed"(ptr %0, ptr %25, { ptr, i64, i64 } zeroinitializer, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %108)
br label %_llgo_8
_llgo_19: ; preds = %_llgo_8
%109 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.PointerTo"(ptr %28)
call void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr %109)
store ptr %109, ptr @"*_llgo_github.com/goplus/llgo/cl/_testdata/geometry1370.Rectangle", align 8
br label %_llgo_20
_llgo_20: ; preds = %_llgo_19, %_llgo_8
%110 = load ptr, ptr @"_llgo_func$UYiLlmcWxoOKZPPzvR4LByitNeKoVGoTrB_5ubdOWW8", align 8
%111 = load ptr, ptr @"_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA", align 8
%112 = load ptr, ptr @"_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk", align 8
%113 = insertvalue %"github.com/goplus/llgo/runtime/abi.Imethod" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @6, i64 4 }, ptr undef }, ptr %110, 1
%114 = insertvalue %"github.com/goplus/llgo/runtime/abi.Imethod" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @9, i64 54 }, ptr undef }, ptr %111, 1
%115 = insertvalue %"github.com/goplus/llgo/runtime/abi.Imethod" { %"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @11, i64 57 }, ptr undef }, ptr %112, 1
%116 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64 72)
%117 = getelementptr %"github.com/goplus/llgo/runtime/abi.Imethod", ptr %116, i64 0
store %"github.com/goplus/llgo/runtime/abi.Imethod" %113, ptr %117, align 8
%118 = getelementptr %"github.com/goplus/llgo/runtime/abi.Imethod", ptr %116, i64 1
store %"github.com/goplus/llgo/runtime/abi.Imethod" %114, ptr %118, align 8
%119 = getelementptr %"github.com/goplus/llgo/runtime/abi.Imethod", ptr %116, i64 2
store %"github.com/goplus/llgo/runtime/abi.Imethod" %115, ptr %119, align 8
%120 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" undef, ptr %116, 0
%121 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %120, i64 3, 1
%122 = insertvalue %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %121, i64 3, 2
%123 = call ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String" { ptr @5, i64 47 }, %"github.com/goplus/llgo/runtime/internal/runtime.Slice" %122)
store ptr %123, ptr @"github.com/goplus/llgo/cl/_testgo/interface1370.iface$OopIVfjRcxQr1gmJyGi5G7hHt__vH05AREEM7PthH9o", align 8
ret void
}
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.NewNamed"(%"github.com/goplus/llgo/runtime/internal/runtime.String", %"github.com/goplus/llgo/runtime/internal/runtime.String", i64, i64, i64, i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Basic"(i64)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Struct"(%"github.com/goplus/llgo/runtime/internal/runtime.String", i64, %"github.com/goplus/llgo/runtime/internal/runtime.Slice")
declare %"github.com/goplus/llgo/runtime/abi.StructField" @"github.com/goplus/llgo/runtime/internal/runtime.StructField"(%"github.com/goplus/llgo/runtime/internal/runtime.String", ptr, i64, %"github.com/goplus/llgo/runtime/internal/runtime.String", i1)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.AllocU"(i64)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.InitNamed"(ptr, ptr, %"github.com/goplus/llgo/runtime/internal/runtime.Slice", %"github.com/goplus/llgo/runtime/internal/runtime.Slice")
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Func"(%"github.com/goplus/llgo/runtime/internal/runtime.Slice", %"github.com/goplus/llgo/runtime/internal/runtime.Slice", i1)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.SetDirectIface"(ptr)
declare double @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).Area"(ptr)
declare i64 @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).GetID"(ptr)
declare void @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).setID"(ptr, i64)
declare i1 @"github.com/goplus/llgo/cl/_testdata/geometry1370.(*Rectangle).validate"(ptr)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.PointerTo"(ptr)
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.Interface"(%"github.com/goplus/llgo/runtime/internal/runtime.String", %"github.com/goplus/llgo/runtime/internal/runtime.Slice")
declare ptr @"github.com/goplus/llgo/runtime/internal/runtime.NewItab"(ptr, ptr)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintString"(%"github.com/goplus/llgo/runtime/internal/runtime.String")
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintInt"(i64)

View File

@@ -0,0 +1,11 @@
package dep
import "fmt"
var VarName = "dep-default"
var VarPlain string
func PrintVar() {
fmt.Printf("dep.VarName: %s\n", VarName)
fmt.Printf("dep.VarPlain: %s\n", VarPlain)
}

View File

@@ -0,0 +1,23 @@
package main
import (
"fmt"
"runtime"
dep "github.com/goplus/llgo/cl/_testgo/rewrite/dep"
)
var VarName = "main-default"
var VarPlain string
func printLine(label, value string) {
fmt.Printf("%s: %s\n", label, value)
}
func main() {
printLine("main.VarName", VarName)
printLine("main.VarPlain", VarPlain)
dep.PrintVar()
printLine("runtime.GOROOT()", runtime.GOROOT())
printLine("runtime.Version()", runtime.Version())
}

View File

@@ -0,0 +1 @@
;

View File

@@ -0,0 +1,14 @@
package main
import (
"unsafe"
_ "unsafe"
)
//go:linkname getsp llgo.stackSave
func getsp() unsafe.Pointer
func main() {
sp := getsp()
println(sp)
}

View File

@@ -0,0 +1,34 @@
; ModuleID = 'github.com/goplus/llgo/cl/_testrt/stacksave'
source_filename = "github.com/goplus/llgo/cl/_testrt/stacksave"
@"github.com/goplus/llgo/cl/_testrt/stacksave.init$guard" = global i1 false, align 1
define void @"github.com/goplus/llgo/cl/_testrt/stacksave.init"() {
_llgo_0:
%0 = load i1, ptr @"github.com/goplus/llgo/cl/_testrt/stacksave.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"github.com/goplus/llgo/cl/_testrt/stacksave.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define void @"github.com/goplus/llgo/cl/_testrt/stacksave.main"() {
_llgo_0:
%0 = call ptr @llvm.stacksave()
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintPointer"(ptr %0)
call void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8 10)
ret void
}
; Function Attrs: nocallback nofree nosync nounwind willreturn
declare ptr @llvm.stacksave() #0
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintPointer"(ptr)
declare void @"github.com/goplus/llgo/runtime/internal/runtime.PrintByte"(i8)
attributes #0 = { nocallback nofree nosync nounwind willreturn }

View File

@@ -110,6 +110,7 @@ type context struct {
loaded map[*types.Package]*pkgInfo // loaded packages
bvals map[ssa.Value]llssa.Expr // block values
vargs map[*ssa.Alloc][]llssa.Expr // varargs
paramDIVars map[*types.Var]llssa.DIVar
patches Patches
blkInfos []blocks.Info
@@ -126,6 +127,58 @@ type context struct {
cgoArgs []llssa.Expr
cgoRet llssa.Expr
cgoSymbols []string
rewrites map[string]string
}
func (p *context) rewriteValue(name string) (string, bool) {
if p.rewrites == nil {
return "", false
}
dot := strings.LastIndex(name, ".")
if dot < 0 || dot == len(name)-1 {
return "", false
}
varName := name[dot+1:]
val, ok := p.rewrites[varName]
return val, ok
}
// isStringPtrType checks if typ is a pointer to the basic string type (*string).
// This is used to validate that -ldflags -X can only rewrite variables of type *string,
// not derived string types like "type T string".
func (p *context) isStringPtrType(typ types.Type) bool {
ptr, ok := typ.(*types.Pointer)
if !ok {
return false
}
basic, ok := ptr.Elem().(*types.Basic)
return ok && basic.Kind() == types.String
}
func (p *context) globalFullName(g *ssa.Global) string {
name, _, _ := p.varName(g.Pkg.Pkg, g)
return name
}
func (p *context) rewriteInitStore(store *ssa.Store, g *ssa.Global) (string, bool) {
if p.rewrites == nil {
return "", false
}
fn := store.Block().Parent()
if fn == nil || fn.Synthetic != "package initializer" {
return "", false
}
if _, ok := store.Val.(*ssa.Const); !ok {
return "", false
}
if !p.isStringPtrType(g.Type()) {
return "", false
}
value, ok := p.rewriteValue(p.globalFullName(g))
if !ok {
return "", false
}
return value, true
}
type pkgState byte
@@ -175,9 +228,18 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
log.Println("==> NewVar", name, typ)
}
g := pkg.NewVar(name, typ, llssa.Background(vtype))
if value, ok := p.rewriteValue(name); ok {
if p.isStringPtrType(gbl.Type()) {
g.Init(pkg.ConstString(value))
} else {
log.Printf("warning: ignoring rewrite for non-string variable %s (type: %v)", name, gbl.Type())
if define {
g.InitNil()
}
}
} else if define {
g.InitNil()
}
}
func makeClosureCtx(pkg *types.Package, vars []*ssa.FreeVar) *types.Var {
@@ -263,6 +325,8 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
if f.Recover != nil { // set recover block
fn.SetRecover(fn.Block(f.Recover.Index))
}
dbgEnabled := enableDbg && (f == nil || f.Origin() == nil)
dbgSymsEnabled := enableDbgSyms && (f == nil || f.Origin() == nil)
p.inits = append(p.inits, func() {
p.fn = fn
p.state = state // restore pkgState when compiling funcBody
@@ -270,6 +334,11 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
p.fn = nil
}()
p.phis = nil
if dbgSymsEnabled {
p.paramDIVars = make(map[*types.Var]llssa.DIVar)
} else {
p.paramDIVars = nil
}
if debugGoSSA {
f.WriteTo(os.Stderr)
}
@@ -277,7 +346,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
log.Println("==> FuncBody", name)
}
b := fn.NewBuilder()
if enableDbg {
if dbgEnabled {
pos := p.goProg.Fset.Position(f.Pos())
bodyPos := p.getFuncBodyPos(f)
b.DebugFunction(fn, pos, bodyPos)
@@ -371,6 +440,9 @@ func (p *context) debugParams(b llssa.Builder, f *ssa.Function) {
ty := param.Type()
argNo := i + 1
div := b.DIVarParam(p.fn, pos, param.Name(), p.type_(ty, llssa.InGo), argNo)
if p.paramDIVars != nil {
p.paramDIVars[variable] = div
}
b.DIParam(variable, v, div, p.fn, pos, p.fn.Block(0))
}
}
@@ -388,7 +460,7 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do
b.Printf("call " + fn.Name() + "\n\x00")
}
// place here to avoid wrong current-block
if enableDbgSyms && block.Index == 0 {
if enableDbgSyms && block.Parent().Origin() == nil && block.Index == 0 {
p.debugParams(b, block.Parent())
}
if doModInit {
@@ -783,7 +855,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
p.compileInstrOrValue(b, iv, false)
return
}
if enableDbg {
if enableDbg && instr.Parent().Origin() == nil {
scope := p.getDebugLocScope(instr.Parent(), instr.Pos())
if scope != nil {
diScope := b.DIScope(p.fn, scope)
@@ -805,6 +877,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
return
}
}
if p.rewrites != nil {
if g, ok := va.(*ssa.Global); ok {
if _, ok := p.rewriteInitStore(v, g); ok {
return
}
}
}
ptr := p.compileValue(b, va)
val := p.compileValue(b, v.Val)
b.Store(ptr, val)
@@ -846,7 +925,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
x := p.compileValue(b, v.X)
b.Send(ch, x)
case *ssa.DebugRef:
if enableDbgSyms {
if enableDbgSyms && v.Parent().Origin() == nil {
p.debugRef(b, v)
}
default:
@@ -855,14 +934,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) {
}
func (p *context) getLocalVariable(b llssa.Builder, fn *ssa.Function, v *types.Var) llssa.DIVar {
if p.paramDIVars != nil {
if div, ok := p.paramDIVars[v]; ok {
return div
}
}
pos := p.fset.Position(v.Pos())
t := p.type_(v.Type(), llssa.InGo)
for i, param := range fn.Params {
if param.Object().(*types.Var) == v {
argNo := i + 1
return b.DIVarParam(p.fn, pos, v.Name(), t, argNo)
}
}
scope := b.DIScope(p.fn, v.Parent())
return b.DIVarAuto(scope, pos, v.Name(), t)
}
@@ -970,12 +1048,22 @@ type Patches = map[string]Patch
// NewPackage compiles a Go package to LLVM IR package.
func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) {
ret, _, err = NewPackageEx(prog, nil, pkg, files)
ret, _, err = NewPackageEx(prog, nil, nil, pkg, files)
return
}
// NewPackageEx compiles a Go package to LLVM IR package.
func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
//
// Parameters:
// - prog: target LLVM SSA program context
// - patches: optional package patches applied during compilation
// - rewrites: per-package string initializers rewritten at compile time
// - pkg: SSA package to compile
// - files: parsed AST files that belong to the package
//
// The rewrites map uses short variable names (without package qualifier) and
// only affects string-typed globals defined in the current package.
func NewPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) {
pkgProg := pkg.Prog
pkgTypes := pkg.Pkg
oldTypes := pkgTypes
@@ -1008,6 +1096,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly?
},
cgoSymbols: make([]string, 0, 128),
rewrites: rewrites,
}
ctx.initPyModule()
ctx.initFiles(pkgPath, files, pkgName == "C")

View File

@@ -504,6 +504,7 @@ const (
llgoCgoCgocall = llgoCgoBase + 0x7
llgoAsm = llgoInstrBase + 0x40
llgoStackSave = llgoInstrBase + 0x41
llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin)
)

View File

@@ -426,6 +426,7 @@ var llgoInstrs = map[string]int{
"_cgo_runtime_cgocall": llgoCgoCgocall,
"asm": llgoAsm,
"stackSave": llgoStackSave,
}
// funcOf returns a function by name and set ftype = goFunc, cFunc, etc.
@@ -601,6 +602,8 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon
ret = p.sigsetjmp(b, args)
case llgoSiglongjmp:
p.siglongjmp(b, args)
case llgoStackSave:
ret = b.StackSave()
case llgoSigjmpbuf: // func sigjmpbuf()
ret = b.AllocaSigjmpBuf()
case llgoDeferData: // func deferData() *Defer

169
cl/rewrite_internal_test.go Normal file
View File

@@ -0,0 +1,169 @@
//go:build !llgo
// +build !llgo
package cl
import (
"go/ast"
"go/parser"
"go/token"
"go/types"
"runtime"
"strings"
"testing"
gpackages "github.com/goplus/gogen/packages"
llssa "github.com/goplus/llgo/ssa"
"github.com/goplus/llgo/ssa/ssatest"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil"
)
func init() {
llssa.Initialize(llssa.InitAll | llssa.InitNative)
}
func compileWithRewrites(t *testing.T, src string, rewrites map[string]string) string {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "rewrite.go", src, 0)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
importer := gpackages.NewImporter(fset)
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
pkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer}, fset,
types.NewPackage(file.Name.Name, file.Name.Name), []*ast.File{file}, mode)
if err != nil {
t.Fatalf("build package failed: %v", err)
}
prog := ssatest.NewProgramEx(t, nil, importer)
prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH))
ret, _, err := NewPackageEx(prog, nil, rewrites, pkg, []*ast.File{file})
if err != nil {
t.Fatalf("NewPackageEx failed: %v", err)
}
return ret.String()
}
func TestRewriteGlobalStrings(t *testing.T) {
const src = `package rewritepkg
var VarInit = "original_value"
var VarPlain string
func Use() string { return VarInit + VarPlain }
`
ir := compileWithRewrites(t, src, map[string]string{
"VarInit": "rewrite_init",
"VarPlain": "rewrite_plain",
})
if strings.Contains(ir, "original_value") {
t.Fatalf("original initializer still present:\n%s", ir)
}
for _, want := range []string{`c"rewrite_init"`, `c"rewrite_plain"`} {
if !strings.Contains(ir, want) {
t.Fatalf("missing %s in IR:\n%s", want, ir)
}
}
}
func TestRewriteSkipsNonConstStores(t *testing.T) {
const src = `package rewritepkg
import "strings"
var VarInit = strings.ToUpper("original_value")
var VarPlain string
func Use() string { return VarInit + VarPlain }
`
ir := compileWithRewrites(t, src, map[string]string{
"VarInit": "rewrite_init",
"VarPlain": "rewrite_plain",
})
if !strings.Contains(ir, `c"rewrite_init"`) {
t.Fatalf("expected rewrite_init constant to remain:\n%s", ir)
}
if !strings.Contains(ir, "strings.ToUpper") {
t.Fatalf("expected call to strings.ToUpper in IR:\n%s", ir)
}
}
func TestRewriteValueNoDot(t *testing.T) {
ctx := &context{rewrites: map[string]string{"VarInit": "rewrite_init"}}
if _, ok := ctx.rewriteValue("VarInit"); ok {
t.Fatalf("rewriteValue should skip names without package prefix")
}
if _, ok := ctx.rewriteValue("pkg."); ok {
t.Fatalf("rewriteValue should skip trailing dot names")
}
}
func TestIsStringPtrTypeDefault(t *testing.T) {
ctx := &context{}
if ctx.isStringPtrType(types.NewPointer(types.Typ[types.Int])) {
t.Fatalf("expected non-string pointer to return false")
}
}
func TestIsStringPtrTypeBranches(t *testing.T) {
ctx := &context{}
if ctx.isStringPtrType(types.NewSlice(types.Typ[types.String])) {
t.Fatalf("slice should trigger default branch and return false")
}
if ctx.isStringPtrType(nil) {
t.Fatalf("nil type should return false")
}
if !ctx.isStringPtrType(types.NewPointer(types.Typ[types.String])) {
t.Fatalf("*string should return true")
}
}
func TestRewriteIgnoredInNonInitStore(t *testing.T) {
const src = `package rewritepkg
var VarInit = "original_value"
func Override() { VarInit = "override_value" }
`
ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"})
if !strings.Contains(ir, `c"override_value"`) {
t.Fatalf("override store should retain original literal:\n%s", ir)
}
if !strings.Contains(ir, `c"rewrite_init"`) {
t.Fatalf("global initializer should still be rewritten:\n%s", ir)
}
}
func TestRewriteMissingEntry(t *testing.T) {
const src = `package rewritepkg
var VarInit = "original_value"
var VarOther = "other_value"
`
ir := compileWithRewrites(t, src, map[string]string{"VarInit": "rewrite_init"})
if !strings.Contains(ir, `c"other_value"`) {
t.Fatalf("VarOther should keep original initializer:\n%s", ir)
}
if !strings.Contains(ir, `c"rewrite_init"`) {
t.Fatalf("VarInit should still be rewritten:\n%s", ir)
}
}
func TestRewriteIgnoresNonStringVar(t *testing.T) {
const src = `package rewritepkg
type wrapper struct{ v int }
var VarStruct = wrapper{v: 1}
`
ir := compileWithRewrites(t, src, map[string]string{"VarStruct": "rewrite_struct"})
if strings.Contains(ir, `c"rewrite_struct"`) {
t.Fatalf("non-string variables must not be rewritten:\n%s", ir)
}
}
func TestRewriteIgnoresStringAlias(t *testing.T) {
const src = `package rewritepkg
type T string
var VarAlias T = "original_value"
`
ir := compileWithRewrites(t, src, map[string]string{"VarAlias": "rewrite_alias"})
if strings.Contains(ir, `c"rewrite_alias"`) {
t.Fatalf("string alias types must not be rewritten:\n%s", ir)
}
if !strings.Contains(ir, `c"original_value"`) {
t.Fatalf("original value should remain for alias type:\n%s", ir)
}
}

32
doc/defer-tls-gc.md Normal file
View File

@@ -0,0 +1,32 @@
# Defer Loop GC Integration
## Background
`defer` chains are stored in a per-thread TLS slot so that unwind paths can locate the active `*runtime.Defer`. With the default allocator (`AllocU`) backed by Boehm GC (bdwgc), those TLS-resident pointers were invisible to the collector. In stress scenarios—e.g. `TestDeferLoopStress` with 1,000,000 defers—the collector reclaimed the defer nodes, leaving dangling pointers and causing crashes inside the deferred closures.
Prior experiments (`test-defer-dont-free` branch) confirmed the crash disappeared when allocations bypassed GC (plain `malloc` without `free`), pointing to a root-registration gap rather than logical corruption.
## Solution Overview
1. **GC-aware TLS slot helper** *(from PR [#1347](https://github.com/goplus/llgo/pull/1347))*
- Added `runtime/internal/clite/tls`, which exposes `tls.Alloc` to create per-thread storage that is automatically registered as a Boehm GC root.
- `SetThreadDefer` delegates to this helper so every thread reuses the same GC-safe slot without bespoke plumbing.
- The package handles TLS key creation, root registration/removal, and invokes an optional destructor when a thread exits.
2. **SSA codegen synchronization**
- `ssa/eh.go` now calls `runtime.SetThreadDefer` whenever it updates the TLS pointer (on first allocation and when restoring the previous link during unwind).
- Defer argument nodes and the `runtime.Defer` struct itself are allocated with `aggregateAllocU`, ensuring new memory comes from GC-managed heaps, and nodes are released via `runtime.FreeDeferNode`.
3. **Non-GC builds**
- The `tls` helper falls back to a malloc-backed TLS slot without GC registration, while `FreeDeferNode` continues to release nodes via `c.Free` when building with `-tags nogc`.
## Testing
Run the stress and regression suites to validate the integration:
```sh
./llgo.sh test ./test -run TestDeferLoopStress
./llgo.sh test ./test
```
The updated `TestDeferLoopStress` now asserts 1,000,000 loop defers execute without failure, catching regressions in GC root tracking.

4
go.mod
View File

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

8
go.sum
View File

@@ -6,10 +6,10 @@ 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.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/gogen v1.19.5 h1:YWPwpRA1PusPhptv9jKg/XiN+AQGiAD9r6I86mJ3lR4=
github.com/goplus/gogen v1.19.5/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/lib v0.3.1 h1:Xws4DBVvgOMu58awqB972wtvTacDbk3nqcbHjdx9KSg=
github.com/goplus/lib v0.3.1/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=
github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94=

View File

@@ -132,10 +132,16 @@ type Config struct {
CheckLinkArgs bool // check linkargs valid
ForceEspClang bool // force to use esp-clang
Tags string
GlobalNames map[string][]string // pkg => names
GlobalDatas map[string]string // pkg.name => data
// GlobalRewrites specifies compile-time overrides for global string variables.
// Keys are fully qualified package paths (e.g. "main" or "github.com/user/pkg").
// Each Rewrites entry maps variable names to replacement string values. Only
// string-typed globals are supported and "main" applies to all root main
// packages in the current build.
GlobalRewrites map[string]Rewrites
}
type Rewrites map[string]string
func NewDefaultConf(mode Mode) *Config {
bin := os.Getenv("GOBIN")
if bin == "" {
@@ -335,6 +341,10 @@ func Do(args []string, conf *Config) ([]Package, error) {
crossCompile: export,
cTransformer: cabi.NewTransformer(prog, export.LLVMTarget, export.TargetABI, conf.AbiMode, cabiOptimize),
}
// default runtime globals must be registered before packages are built
addGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil)
addGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil)
pkgs, err := buildAllPkgs(ctx, initial, verbose)
check(err)
if mode == ModeGen {
@@ -345,19 +355,11 @@ func Do(args []string, conf *Config) ([]Package, error) {
}
return nil, fmt.Errorf("initial package not found")
}
dpkg, err := buildAllPkgs(ctx, altPkgs[noRt:], verbose)
check(err)
allPkgs := append([]*aPackage{}, pkgs...)
allPkgs = append(allPkgs, dpkg...)
// update globals importpath.name=value
addGlobalString(conf, "runtime.defaultGOROOT="+runtime.GOROOT(), nil)
addGlobalString(conf, "runtime.buildVersion="+runtime.Version(), nil)
global, err := createGlobals(ctx, ctx.prog, pkgs)
check(err)
for _, pkg := range initial {
if needLink(pkg, mode) {
name := path.Base(pkg.PkgPath)
@@ -369,7 +371,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
}
// Link main package using the output path from buildOutFmts
err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
err = linkMainPkg(ctx, pkg, allPkgs, outFmts.Out, verbose)
if err != nil {
return nil, err
}
@@ -629,58 +631,59 @@ var (
errXflags = errors.New("-X flag requires argument of the form importpath.name=value")
)
// maxRewriteValueLength limits the size of rewrite values to prevent
// excessive memory usage and potential resource exhaustion during compilation.
const maxRewriteValueLength = 1 << 20 // 1 MiB cap per rewrite value
func addGlobalString(conf *Config, arg string, mainPkgs []string) {
addGlobalStringWith(conf, arg, mainPkgs, true)
}
func addGlobalStringWith(conf *Config, arg string, mainPkgs []string, skipIfExists bool) {
eq := strings.Index(arg, "=")
dot := strings.LastIndex(arg[:eq+1], ".")
if eq < 0 || dot < 0 {
panic(errXflags)
}
pkg := arg[:dot]
varName := arg[dot+1 : eq]
value := arg[eq+1:]
validateRewriteInput(pkg, varName, value)
pkgs := []string{pkg}
if pkg == "main" {
pkgs = mainPkgs
}
if conf.GlobalNames == nil {
conf.GlobalNames = make(map[string][]string)
if len(pkgs) == 0 {
return
}
if conf.GlobalDatas == nil {
conf.GlobalDatas = make(map[string]string)
if conf.GlobalRewrites == nil {
conf.GlobalRewrites = make(map[string]Rewrites)
}
for _, pkg := range pkgs {
name := pkg + arg[dot:eq]
value := arg[eq+1:]
if _, ok := conf.GlobalDatas[name]; !ok {
conf.GlobalNames[pkg] = append(conf.GlobalNames[pkg], name)
for _, realPkg := range pkgs {
vars := conf.GlobalRewrites[realPkg]
if vars == nil {
vars = make(Rewrites)
conf.GlobalRewrites[realPkg] = vars
}
conf.GlobalDatas[name] = value
if skipIfExists {
if _, exists := vars[varName]; exists {
continue
}
}
vars[varName] = value
}
}
func createGlobals(ctx *context, prog llssa.Program, pkgs []*aPackage) (llssa.Package, error) {
if len(ctx.buildConf.GlobalDatas) == 0 {
return nil, nil
func validateRewriteInput(pkg, varName, value string) {
if pkg == "" || strings.ContainsAny(pkg, " \t\r\n") {
panic(fmt.Errorf("invalid package path for rewrite: %q", pkg))
}
for _, pkg := range pkgs {
if pkg.ExportFile == "" {
continue
if !token.IsIdentifier(varName) {
panic(fmt.Errorf("invalid variable name for rewrite: %q", varName))
}
if names, ok := ctx.buildConf.GlobalNames[pkg.PkgPath]; ok {
err := pkg.LPkg.Undefined(names...)
if err != nil {
return nil, err
if len(value) > maxRewriteValueLength {
panic(fmt.Errorf("rewrite value too large: %d bytes", len(value)))
}
pkg.ExportFile += "-global"
pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile, []byte(pkg.LPkg.String()))
if err != nil {
return nil, err
}
}
}
global := prog.NewPackage("", "global")
for name, value := range ctx.buildConf.GlobalDatas {
global.AddGlobalString(name, value)
}
return global, nil
}
// compileExtraFiles compiles extra files (.s/.c) from target configuration and returns object files
@@ -752,7 +755,7 @@ func compileExtraFiles(ctx *context, verbose bool) ([]string, error) {
return objFiles, nil
}
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global llssa.Package, outputPath string, verbose bool) error {
func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPath string, verbose bool) error {
needRuntime := false
needPyInit := false
@@ -769,7 +772,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
if p.ExportFile != "" && aPkg != nil { // skip packages that only contain declarations
linkArgs = append(linkArgs, aPkg.LinkArgs...)
objFiles = append(objFiles, aPkg.LLFiles...)
objFiles = append(objFiles, aPkg.ExportFile)
need1, need2 := isNeedRuntimeOrPyInit(ctx, p)
if !needRuntime {
needRuntime = need1
@@ -794,14 +796,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
}
objFiles = append(objFiles, extraObjFiles...)
if global != nil {
export, err := exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile+"-global", []byte(global.String()))
if err != nil {
return err
}
objFiles = append(objFiles, export)
}
if IsFullRpathEnabled() {
// Treat every link-time library search path, specified by the -L parameter, as a runtime search path as well.
// This is to ensure the final executable can locate libraries with a relocatable install_name
@@ -1050,7 +1044,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
cl.SetDebug(cl.DbgFlagAll)
}
ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax)
ret, externs, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.rewriteVars, aPkg.SSA, syntax)
if showDetail {
llssa.SetDebug(0)
cl.SetDebug(0)
@@ -1077,10 +1071,11 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
aPkg.LinkArgs = append(aPkg.LinkArgs, altLdflags...)
}
if pkg.ExportFile != "" {
pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String()))
exportFile, err := exportObject(ctx, pkg.PkgPath, pkg.ExportFile, []byte(ret.String()))
if err != nil {
return fmt.Errorf("export object of %v failed: %v", pkgPath, err)
}
aPkg.LLFiles = append(aPkg.LLFiles, exportFile)
if debugBuild || verbose {
fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile)
}
@@ -1089,11 +1084,15 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
}
func exportObject(ctx *context, pkgPath string, exportFile string, data []byte) (string, error) {
f, err := os.CreateTemp("", "llgo-*.ll")
base := filepath.Base(exportFile)
f, err := os.CreateTemp("", base+"-*.ll")
if err != nil {
return "", err
}
f.Write(data)
if _, err := f.Write(data); err != nil {
f.Close()
return "", err
}
err = f.Close()
if err != nil {
return exportFile, err
@@ -1111,13 +1110,17 @@ func exportObject(ctx *context, pkgPath string, exportFile string, data []byte)
}
return exportFile, os.Rename(f.Name(), exportFile)
}
exportFile += ".o"
args := []string{"-o", exportFile, "-c", f.Name(), "-Wno-override-module"}
objFile, err := os.CreateTemp("", base+"-*.o")
if err != nil {
return "", err
}
objFile.Close()
args := []string{"-o", objFile.Name(), "-c", f.Name(), "-Wno-override-module"}
if ctx.buildConf.Verbose {
fmt.Fprintln(os.Stderr, "clang", args)
}
cmd := ctx.compiler()
return exportFile, cmd.Compile(args...)
return objFile.Name(), cmd.Compile(args...)
}
func llcCheck(env *llvm.Env, exportFile string) (msg string, err error) {
@@ -1173,6 +1176,7 @@ type aPackage struct {
LinkArgs []string
LLFiles []string
rewriteVars map[string]string
}
type Package = *aPackage
@@ -1193,7 +1197,8 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP
return
}
}
all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil, nil})
rewrites := collectRewriteVars(ctx, pkgPath)
all = append(all, &aPackage{p, ssaPkg, altPkg, nil, nil, nil, rewrites})
} else {
errs = append(errs, p)
}
@@ -1201,6 +1206,32 @@ func allPkgs(ctx *context, initial []*packages.Package, verbose bool) (all []*aP
return
}
func collectRewriteVars(ctx *context, pkgPath string) map[string]string {
data := ctx.buildConf.GlobalRewrites
if len(data) == 0 {
return nil
}
basePath := strings.TrimPrefix(pkgPath, altPkgPathPrefix)
if vars := data[basePath]; vars != nil {
return cloneRewrites(vars)
}
if vars := data[pkgPath]; vars != nil {
return cloneRewrites(vars)
}
return nil
}
func cloneRewrites(src Rewrites) map[string]string {
if len(src) == 0 {
return nil
}
dup := make(map[string]string, len(src))
for k, v := range src {
dup[k] = v
}
return dup
}
func createSSAPkg(prog *ssa.Program, p *packages.Package, verbose bool) *ssa.Package {
pkgSSA := prog.ImportedPackage(p.ID)
if pkgSSA == nil {

View File

@@ -8,6 +8,9 @@ import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"testing"
"github.com/goplus/llgo/internal/mockable"
@@ -94,4 +97,65 @@ func TestCmpTest(t *testing.T) {
mockRun([]string{"../../cl/_testgo/runtest"}, &Config{Mode: ModeCmpTest})
}
// TestGenerateOutputFilenames removed - functionality moved to filename_test.go
const (
rewriteMainPkg = "github.com/goplus/llgo/cl/_testgo/rewrite"
rewriteDepPkg = rewriteMainPkg + "/dep"
rewriteDirPath = "../../cl/_testgo/rewrite"
)
func TestLdFlagsRewriteVars(t *testing.T) {
buildRewriteBinary(t, false, "build-main", "build-pkg")
buildRewriteBinary(t, false, "rerun-main", "rerun-pkg")
}
func TestLdFlagsRewriteVarsMainAlias(t *testing.T) {
buildRewriteBinary(t, true, "alias-main", "alias-pkg")
}
func buildRewriteBinary(t *testing.T, useMainAlias bool, mainVal, depVal string) {
t.Helper()
binPath := filepath.Join(t.TempDir(), "rewrite")
if runtime.GOOS == "windows" {
binPath += ".exe"
}
cfg := &Config{Mode: ModeBuild, OutFile: binPath}
mainKey := rewriteMainPkg
var mainPkgs []string
if useMainAlias {
mainKey = "main"
mainPkgs = []string{rewriteMainPkg}
}
mainPlain := mainVal + "-plain"
depPlain := depVal + "-plain"
gorootVal := "goroot-" + mainVal
versionVal := "version-" + mainVal
addGlobalString(cfg, mainKey+".VarName="+mainVal, mainPkgs)
addGlobalString(cfg, mainKey+".VarPlain="+mainPlain, mainPkgs)
addGlobalString(cfg, rewriteDepPkg+".VarName="+depVal, nil)
addGlobalString(cfg, rewriteDepPkg+".VarPlain="+depPlain, nil)
addGlobalString(cfg, "runtime.defaultGOROOT="+gorootVal, nil)
addGlobalString(cfg, "runtime.buildVersion="+versionVal, nil)
if _, err := Do([]string{rewriteDirPath}, cfg); err != nil {
t.Fatalf("ModeBuild failed: %v", err)
}
got := runBinary(t, binPath)
want := fmt.Sprintf(
"main.VarName: %s\nmain.VarPlain: %s\ndep.VarName: %s\ndep.VarPlain: %s\nruntime.GOROOT(): %s\nruntime.Version(): %s\n",
mainVal, mainPlain, depVal, depPlain, gorootVal, versionVal,
)
if got != want {
t.Fatalf("unexpected binary output:\nwant %q\ngot %q", want, got)
}
}
func runBinary(t *testing.T, path string, args ...string) string {
t.Helper()
cmd := exec.Command(path, args...)
output, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("failed to run %s: %v\n%s", path, err, output)
}
return string(output)
}

View File

@@ -1,3 +1,5 @@
//go:build !llgo
package libc
import (

View File

@@ -30,8 +30,6 @@ const (
var buildVersion string
// Version returns the version of the running LLGo binary.
//
//export LLGoVersion
func Version() string {
if buildVersion != "" {
return buildVersion

View File

@@ -1,622 +0,0 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package parser
import (
"fmt"
"go/ast"
"go/token"
"strings"
)
const debugResolve = false
// resolveFile walks the given file to resolve identifiers within the file
// scope, updating ast.Ident.Obj fields with declaration information.
//
// If declErr is non-nil, it is used to report declaration errors during
// resolution. tok is used to format position in error messages.
func resolveFile(file *ast.File, handle *token.File, declErr func(token.Pos, string)) {
pkgScope := ast.NewScope(nil)
r := &resolver{
handle: handle,
declErr: declErr,
topScope: pkgScope,
pkgScope: pkgScope,
depth: 1,
}
for _, decl := range file.Decls {
ast.Walk(r, decl)
}
r.closeScope()
assert(r.topScope == nil, "unbalanced scopes")
assert(r.labelScope == nil, "unbalanced label scopes")
// resolve global identifiers within the same file
i := 0
for _, ident := range r.unresolved {
// i <= index for current ident
assert(ident.Obj == unresolved, "object already resolved")
ident.Obj = r.pkgScope.Lookup(ident.Name) // also removes unresolved sentinel
if ident.Obj == nil {
r.unresolved[i] = ident
i++
} else if debugResolve {
pos := ident.Obj.Decl.(interface{ Pos() token.Pos }).Pos()
r.trace("resolved %s@%v to package object %v", ident.Name, ident.Pos(), pos)
}
}
file.Scope = r.pkgScope
file.Unresolved = r.unresolved[0:i]
}
const maxScopeDepth int = 1e3
type resolver struct {
handle *token.File
declErr func(token.Pos, string)
// Ordinary identifier scopes
pkgScope *ast.Scope // pkgScope.Outer == nil
topScope *ast.Scope // top-most scope; may be pkgScope
unresolved []*ast.Ident // unresolved identifiers
depth int // scope depth
// Label scopes
// (maintained by open/close LabelScope)
labelScope *ast.Scope // label scope for current function
targetStack [][]*ast.Ident // stack of unresolved labels
}
func (r *resolver) trace(format string, args ...any) {
fmt.Println(strings.Repeat(". ", r.depth) + r.sprintf(format, args...))
}
func (r *resolver) sprintf(format string, args ...any) string {
for i, arg := range args {
switch arg := arg.(type) {
case token.Pos:
args[i] = r.handle.Position(arg)
}
}
return fmt.Sprintf(format, args...)
}
func (r *resolver) openScope(pos token.Pos) {
r.depth++
if r.depth > maxScopeDepth {
panic(bailout{pos: pos, msg: "exceeded max scope depth during object resolution"})
}
if debugResolve {
r.trace("opening scope @%v", pos)
}
r.topScope = ast.NewScope(r.topScope)
}
func (r *resolver) closeScope() {
r.depth--
if debugResolve {
r.trace("closing scope")
}
r.topScope = r.topScope.Outer
}
func (r *resolver) openLabelScope() {
r.labelScope = ast.NewScope(r.labelScope)
r.targetStack = append(r.targetStack, nil)
}
func (r *resolver) closeLabelScope() {
// resolve labels
n := len(r.targetStack) - 1
scope := r.labelScope
for _, ident := range r.targetStack[n] {
ident.Obj = scope.Lookup(ident.Name)
if ident.Obj == nil && r.declErr != nil {
r.declErr(ident.Pos(), fmt.Sprintf("label %s undefined", ident.Name))
}
}
// pop label scope
r.targetStack = r.targetStack[0:n]
r.labelScope = r.labelScope.Outer
}
func (r *resolver) declare(decl, data any, scope *ast.Scope, kind ast.ObjKind, idents ...*ast.Ident) {
for _, ident := range idents {
if ident.Obj != nil {
panic(fmt.Sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name))
}
obj := ast.NewObj(kind, ident.Name)
// remember the corresponding declaration for redeclaration
// errors and global variable resolution/typechecking phase
obj.Decl = decl
obj.Data = data
// Identifiers (for receiver type parameters) are written to the scope, but
// never set as the resolved object. See go.dev/issue/50956.
if _, ok := decl.(*ast.Ident); !ok {
ident.Obj = obj
}
if ident.Name != "_" {
if debugResolve {
r.trace("declaring %s@%v", ident.Name, ident.Pos())
}
if alt := scope.Insert(obj); alt != nil && r.declErr != nil {
prevDecl := ""
if pos := alt.Pos(); pos.IsValid() {
prevDecl = r.sprintf("\n\tprevious declaration at %v", pos)
}
r.declErr(ident.Pos(), fmt.Sprintf("%s redeclared in this block%s", ident.Name, prevDecl))
}
}
}
}
func (r *resolver) shortVarDecl(decl *ast.AssignStmt) {
// Go spec: A short variable declaration may redeclare variables
// provided they were originally declared in the same block with
// the same type, and at least one of the non-blank variables is new.
n := 0 // number of new variables
for _, x := range decl.Lhs {
if ident, isIdent := x.(*ast.Ident); isIdent {
assert(ident.Obj == nil, "identifier already declared or resolved")
obj := ast.NewObj(ast.Var, ident.Name)
// remember corresponding assignment for other tools
obj.Decl = decl
ident.Obj = obj
if ident.Name != "_" {
if debugResolve {
r.trace("declaring %s@%v", ident.Name, ident.Pos())
}
if alt := r.topScope.Insert(obj); alt != nil {
ident.Obj = alt // redeclaration
} else {
n++ // new declaration
}
}
}
}
if n == 0 && r.declErr != nil {
r.declErr(decl.Lhs[0].Pos(), "no new variables on left side of :=")
}
}
// The unresolved object is a sentinel to mark identifiers that have been added
// to the list of unresolved identifiers. The sentinel is only used for verifying
// internal consistency.
var unresolved = new(ast.Object)
// If x is an identifier, resolve attempts to resolve x by looking up
// the object it denotes. If no object is found and collectUnresolved is
// set, x is marked as unresolved and collected in the list of unresolved
// identifiers.
func (r *resolver) resolve(ident *ast.Ident, collectUnresolved bool) {
if ident.Obj != nil {
panic(r.sprintf("%v: identifier %s already declared or resolved", ident.Pos(), ident.Name))
}
// '_' should never refer to existing declarations, because it has special
// handling in the spec.
if ident.Name == "_" {
return
}
for s := r.topScope; s != nil; s = s.Outer {
if obj := s.Lookup(ident.Name); obj != nil {
if debugResolve {
r.trace("resolved %v:%s to %v", ident.Pos(), ident.Name, obj)
}
assert(obj.Name != "", "obj with no name")
// Identifiers (for receiver type parameters) are written to the scope,
// but never set as the resolved object. See go.dev/issue/50956.
if _, ok := obj.Decl.(*ast.Ident); !ok {
ident.Obj = obj
}
return
}
}
// all local scopes are known, so any unresolved identifier
// must be found either in the file scope, package scope
// (perhaps in another file), or universe scope --- collect
// them so that they can be resolved later
if collectUnresolved {
ident.Obj = unresolved
r.unresolved = append(r.unresolved, ident)
}
}
func (r *resolver) walkExprs(list []ast.Expr) {
for _, node := range list {
ast.Walk(r, node)
}
}
func Unparen(e ast.Expr) ast.Expr {
for {
paren, ok := e.(*ast.ParenExpr)
if !ok {
return e
}
e = paren.X
}
}
func (r *resolver) walkLHS(list []ast.Expr) {
for _, expr := range list {
expr := Unparen(expr)
if _, ok := expr.(*ast.Ident); !ok && expr != nil {
ast.Walk(r, expr)
}
}
}
func (r *resolver) walkStmts(list []ast.Stmt) {
for _, stmt := range list {
ast.Walk(r, stmt)
}
}
func (r *resolver) Visit(node ast.Node) ast.Visitor {
if debugResolve && node != nil {
r.trace("node %T@%v", node, node.Pos())
}
switch n := node.(type) {
// Expressions.
case *ast.Ident:
r.resolve(n, true)
case *ast.FuncLit:
r.openScope(n.Pos())
defer r.closeScope()
r.walkFuncType(n.Type)
r.walkBody(n.Body)
case *ast.SelectorExpr:
ast.Walk(r, n.X)
// Note: don't try to resolve n.Sel, as we don't support qualified
// resolution.
case *ast.StructType:
r.openScope(n.Pos())
defer r.closeScope()
r.walkFieldList(n.Fields, ast.Var)
case *ast.FuncType:
r.openScope(n.Pos())
defer r.closeScope()
r.walkFuncType(n)
case *ast.CompositeLit:
if n.Type != nil {
ast.Walk(r, n.Type)
}
for _, e := range n.Elts {
if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
// See go.dev/issue/45160: try to resolve composite lit keys, but don't
// collect them as unresolved if resolution failed. This replicates
// existing behavior when resolving during parsing.
if ident, _ := kv.Key.(*ast.Ident); ident != nil {
r.resolve(ident, false)
} else {
ast.Walk(r, kv.Key)
}
ast.Walk(r, kv.Value)
} else {
ast.Walk(r, e)
}
}
case *ast.InterfaceType:
r.openScope(n.Pos())
defer r.closeScope()
r.walkFieldList(n.Methods, ast.Fun)
// Statements
case *ast.LabeledStmt:
r.declare(n, nil, r.labelScope, ast.Lbl, n.Label)
ast.Walk(r, n.Stmt)
case *ast.AssignStmt:
r.walkExprs(n.Rhs)
if n.Tok == token.DEFINE {
r.shortVarDecl(n)
} else {
r.walkExprs(n.Lhs)
}
case *ast.BranchStmt:
// add to list of unresolved targets
if n.Tok != token.FALLTHROUGH && n.Label != nil {
depth := len(r.targetStack) - 1
r.targetStack[depth] = append(r.targetStack[depth], n.Label)
}
case *ast.BlockStmt:
r.openScope(n.Pos())
defer r.closeScope()
r.walkStmts(n.List)
case *ast.IfStmt:
r.openScope(n.Pos())
defer r.closeScope()
if n.Init != nil {
ast.Walk(r, n.Init)
}
ast.Walk(r, n.Cond)
ast.Walk(r, n.Body)
if n.Else != nil {
ast.Walk(r, n.Else)
}
case *ast.CaseClause:
r.walkExprs(n.List)
r.openScope(n.Pos())
defer r.closeScope()
r.walkStmts(n.Body)
case *ast.SwitchStmt:
r.openScope(n.Pos())
defer r.closeScope()
if n.Init != nil {
ast.Walk(r, n.Init)
}
if n.Tag != nil {
// The scope below reproduces some unnecessary behavior of the parser,
// opening an extra scope in case this is a type switch. It's not needed
// for expression switches.
// TODO: remove this once we've matched the parser resolution exactly.
if n.Init != nil {
r.openScope(n.Tag.Pos())
defer r.closeScope()
}
ast.Walk(r, n.Tag)
}
if n.Body != nil {
r.walkStmts(n.Body.List)
}
case *ast.TypeSwitchStmt:
if n.Init != nil {
r.openScope(n.Pos())
defer r.closeScope()
ast.Walk(r, n.Init)
}
r.openScope(n.Assign.Pos())
defer r.closeScope()
ast.Walk(r, n.Assign)
// s.Body consists only of case clauses, so does not get its own
// scope.
if n.Body != nil {
r.walkStmts(n.Body.List)
}
case *ast.CommClause:
r.openScope(n.Pos())
defer r.closeScope()
if n.Comm != nil {
ast.Walk(r, n.Comm)
}
r.walkStmts(n.Body)
case *ast.SelectStmt:
// as for switch statements, select statement bodies don't get their own
// scope.
if n.Body != nil {
r.walkStmts(n.Body.List)
}
case *ast.ForStmt:
r.openScope(n.Pos())
defer r.closeScope()
if n.Init != nil {
ast.Walk(r, n.Init)
}
if n.Cond != nil {
ast.Walk(r, n.Cond)
}
if n.Post != nil {
ast.Walk(r, n.Post)
}
ast.Walk(r, n.Body)
case *ast.RangeStmt:
r.openScope(n.Pos())
defer r.closeScope()
ast.Walk(r, n.X)
var lhs []ast.Expr
if n.Key != nil {
lhs = append(lhs, n.Key)
}
if n.Value != nil {
lhs = append(lhs, n.Value)
}
if len(lhs) > 0 {
if n.Tok == token.DEFINE {
// Note: we can't exactly match the behavior of object resolution
// during the parsing pass here, as it uses the position of the RANGE
// token for the RHS OpPos. That information is not contained within
// the AST.
as := &ast.AssignStmt{
Lhs: lhs,
Tok: token.DEFINE,
TokPos: n.TokPos,
Rhs: []ast.Expr{&ast.UnaryExpr{Op: token.RANGE, X: n.X}},
}
// TODO(rFindley): this walkLHS reproduced the parser resolution, but
// is it necessary? By comparison, for a normal AssignStmt we don't
// walk the LHS in case there is an invalid identifier list.
r.walkLHS(lhs)
r.shortVarDecl(as)
} else {
r.walkExprs(lhs)
}
}
ast.Walk(r, n.Body)
// Declarations
case *ast.GenDecl:
switch n.Tok {
case token.CONST, token.VAR:
for i, spec := range n.Specs {
spec := spec.(*ast.ValueSpec)
kind := ast.Con
if n.Tok == token.VAR {
kind = ast.Var
}
r.walkExprs(spec.Values)
if spec.Type != nil {
ast.Walk(r, spec.Type)
}
r.declare(spec, i, r.topScope, kind, spec.Names...)
}
case token.TYPE:
for _, spec := range n.Specs {
spec := spec.(*ast.TypeSpec)
// Go spec: The scope of a type identifier declared inside a function begins
// at the identifier in the TypeSpec and ends at the end of the innermost
// containing block.
r.declare(spec, nil, r.topScope, ast.Typ, spec.Name)
if spec.TypeParams != nil {
r.openScope(spec.Pos())
r.walkTParams(spec.TypeParams)
r.closeScope()
}
ast.Walk(r, spec.Type)
}
}
case *ast.FuncDecl:
// Open the function scope.
r.openScope(n.Pos())
defer r.closeScope()
r.walkRecv(n.Recv)
// Type parameters are walked normally: they can reference each other, and
// can be referenced by normal parameters.
if n.Type.TypeParams != nil {
r.walkTParams(n.Type.TypeParams)
// TODO(rFindley): need to address receiver type parameters.
}
// Resolve and declare parameters in a specific order to get duplicate
// declaration errors in the correct location.
r.resolveList(n.Type.Params)
r.resolveList(n.Type.Results)
r.declareList(n.Recv, ast.Var)
r.declareList(n.Type.Params, ast.Var)
r.declareList(n.Type.Results, ast.Var)
r.walkBody(n.Body)
if n.Recv == nil && n.Name.Name != "init" {
r.declare(n, nil, r.pkgScope, ast.Fun, n.Name)
}
default:
return r
}
return nil
}
func (r *resolver) walkFuncType(typ *ast.FuncType) {
// typ.TypeParams must be walked separately for FuncDecls.
r.resolveList(typ.Params)
r.resolveList(typ.Results)
r.declareList(typ.Params, ast.Var)
r.declareList(typ.Results, ast.Var)
}
func (r *resolver) resolveList(list *ast.FieldList) {
if list == nil {
return
}
for _, f := range list.List {
if f.Type != nil {
ast.Walk(r, f.Type)
}
}
}
func (r *resolver) declareList(list *ast.FieldList, kind ast.ObjKind) {
if list == nil {
return
}
for _, f := range list.List {
r.declare(f, nil, r.topScope, kind, f.Names...)
}
}
func (r *resolver) walkRecv(recv *ast.FieldList) {
// If our receiver has receiver type parameters, we must declare them before
// trying to resolve the rest of the receiver, and avoid re-resolving the
// type parameter identifiers.
if recv == nil || len(recv.List) == 0 {
return // nothing to do
}
typ := recv.List[0].Type
if ptr, ok := typ.(*ast.StarExpr); ok {
typ = ptr.X
}
var declareExprs []ast.Expr // exprs to declare
var resolveExprs []ast.Expr // exprs to resolve
switch typ := typ.(type) {
case *ast.IndexExpr:
declareExprs = []ast.Expr{typ.Index}
resolveExprs = append(resolveExprs, typ.X)
case *ast.IndexListExpr:
declareExprs = typ.Indices
resolveExprs = append(resolveExprs, typ.X)
default:
resolveExprs = append(resolveExprs, typ)
}
for _, expr := range declareExprs {
if id, _ := expr.(*ast.Ident); id != nil {
r.declare(expr, nil, r.topScope, ast.Typ, id)
} else {
// The receiver type parameter expression is invalid, but try to resolve
// it anyway for consistency.
resolveExprs = append(resolveExprs, expr)
}
}
for _, expr := range resolveExprs {
if expr != nil {
ast.Walk(r, expr)
}
}
// The receiver is invalid, but try to resolve it anyway for consistency.
for _, f := range recv.List[1:] {
if f.Type != nil {
ast.Walk(r, f.Type)
}
}
}
func (r *resolver) walkFieldList(list *ast.FieldList, kind ast.ObjKind) {
if list == nil {
return
}
r.resolveList(list)
r.declareList(list, kind)
}
// walkTParams is like walkFieldList, but declares type parameters eagerly so
// that they may be resolved in the constraint expressions held in the field
// Type.
func (r *resolver) walkTParams(list *ast.FieldList) {
r.declareList(list, ast.Typ)
r.resolveList(list)
}
func (r *resolver) walkBody(body *ast.BlockStmt) {
if body == nil {
return
}
r.openLabelScope()
defer r.closeLabelScope()
r.walkStmts(body.List)
}

View File

@@ -1 +0,0 @@
package testing

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
//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
// Handle is a no-op TLS handle used when building without the llgo tag.
type Handle[T any] struct{}
// Alloc returns a stub TLS handle that ignores all operations.
func Alloc[T any](func(*T)) Handle[T] { return Handle[T]{} }
// Get always returns the zero value.
func (Handle[T]) Get() (zero T) { return zero }
// Set is a no-op.
func (Handle[T]) Set(T) {}
// Clear is a no-op.
func (Handle[T]) Clear() {}

View File

@@ -0,0 +1,40 @@
/*
* 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 runtime
import "github.com/goplus/llgo/runtime/internal/clite/tls"
var deferTLS = tls.Alloc[*Defer](func(head **Defer) {
if head != nil {
*head = nil
}
})
// SetThreadDefer associates the current thread with the given defer chain.
func SetThreadDefer(head *Defer) {
deferTLS.Set(head)
}
// GetThreadDefer returns the current thread's defer chain head.
func GetThreadDefer() *Defer {
return deferTLS.Get()
}
// ClearThreadDefer resets the current thread's defer chain to nil.
func ClearThreadDefer() {
deferTLS.Clear()
}

View File

@@ -0,0 +1,33 @@
//go:build !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 runtime
import (
"unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/bdwgc"
)
// FreeDeferNode releases a defer argument node allocated from the Boehm heap.
func FreeDeferNode(ptr unsafe.Pointer) {
if ptr != nil {
bdwgc.Free(c.Pointer(ptr))
}
}

View File

@@ -0,0 +1,32 @@
//go:build 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 runtime
import (
"unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
)
// FreeDeferNode releases the defer node when GC integration is disabled.
func FreeDeferNode(ptr unsafe.Pointer) {
if ptr != nil {
c.Free(ptr)
}
}

View File

@@ -365,7 +365,7 @@ func NewItab(inter *InterfaceType, typ *Type) *Itab {
ret.fun[0] = 0
} else {
data := (*uintptr)(c.Advance(ptr, int(itabHdrSize)))
mthds := methods(u, inter.PkgPath_)
mthds := u.Methods()
for i, m := range inter.Methods {
fn := findMethod(mthds, m)
if fn == nil {
@@ -395,13 +395,6 @@ func findMethod(mthds []abi.Method, im abi.Imethod) abi.Text {
return nil
}
func methods(u *abi.UncommonType, from string) []abi.Method {
if u.PkgPath_ == from {
return u.Methods()
}
return u.ExportedMethods()
}
func IfaceType(i iface) *abi.Type {
if i.tab == nil {
return nil

View File

@@ -109,18 +109,6 @@ const MaxZero = 1024
var ZeroVal [MaxZero]byte
// func init() {
// signal.Signal(c.Int(syscall.SIGSEGV), func(v c.Int) {
// switch syscall.Signal(v) {
// case syscall.SIGSEGV:
// panic(errorString("invalid memory address or nil pointer dereference"))
// default:
// var buf [20]byte
// panic(errorString("unexpected signal value: " + string(itoa(buf[:], uint64(v)))))
// }
// })
// }
// -----------------------------------------------------------------------------
type SigjmpBuf struct {

View File

@@ -0,0 +1,48 @@
//go:build !wasm && !baremetal
/*
* 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 runtime
import (
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/signal"
)
const (
// SIGSEGV is signal number 11 on all Unix-like systems (Linux, Darwin, BSD, etc.)
// Using a hardcoded constant avoids importing the syscall package, which would
// introduce dependencies on errors and internal/reflectlite packages that cause
// linking issues in c-shared and c-archive build modes.
SIGSEGV = c.Int(0xb)
)
// This file contains platform-specific runtime initialization for non-wasm targets.
// The SIGSEGV signal handler enables Go-style panic recovery for nil pointer dereferences
// instead of immediate process termination.
//
// For wasm platform compatibility, signal handling is excluded via build tags.
// See PR #1059 for wasm platform requirements.
func init() {
signal.Signal(SIGSEGV, func(v c.Int) {
if v == SIGSEGV {
panic(errorString("invalid memory address or nil pointer dereference"))
}
var buf [20]byte
panic(errorString("unexpected signal value: " + string(itoa(buf[:], uint64(v)))))
})
}

View File

@@ -7,27 +7,11 @@ import (
//go:embed _overlay/runtime/runtime.go
var fakeRuntime string
//go:embed _overlay/go/parser/resolver.go
var go_parser_resolver string
//go:embed _overlay/testing/testing.go
var testing_testing string
//go:embed _overlay/testing/testing_go123.go
var testing_testing_go123 string
//go:embed _overlay/testing/testing_go124.go
var testing_testing_go124 string
//go:embed _overlay/net/textproto/textproto.go
var net_textproto string
var OverlayFiles = map[string]string{
"math/exp_amd64.go": "package math;",
"go/parser/resolver.go": go_parser_resolver,
"testing/testing.go": testing_testing,
"testing/testing_go123.go": testing_testing_go123,
"testing/testing_go124.go": testing_testing_go124,
"net/textproto/textproto.go": net_textproto,
"runtime/runtime.go": fakeRuntime,
}

View File

@@ -294,7 +294,7 @@ func (p Function) NewBuilder() Builder {
// TODO(xsw): Finalize may cause panic, so comment it.
// b.Finalize()
return &aBuilder{b, nil, p, p.Pkg, prog,
make(map[Expr]dbgExpr), make(map[*types.Scope]DIScope)}
make(map[*types.Scope]DIScope)}
}
// HasBody reports whether the function has a body.

View File

@@ -662,14 +662,8 @@ func (b diBuilder) createExpression(ops []uint64) DIExpression {
// Copy to alloca'd memory to get declareable address.
func (b Builder) constructDebugAddr(v Expr) (dbgPtr Expr, dbgVal Expr, exists bool) {
if v, ok := b.dbgVars[v]; ok {
return v.ptr, v.val, true
}
t := v.Type.RawType().Underlying()
dbgPtr, dbgVal = b.doConstructDebugAddr(v, t)
dbgExpr := dbgExpr{dbgPtr, dbgVal}
b.dbgVars[v] = dbgExpr
b.dbgVars[dbgVal] = dbgExpr
return dbgPtr, dbgVal, false
}
@@ -874,11 +868,7 @@ func (b Builder) DebugFunction(f Function, pos token.Position, bodyPos token.Pos
}
func (b Builder) Param(idx int) Expr {
p := b.Func.Param(idx)
if v, ok := b.dbgVars[p]; ok {
return v.val
}
return p
return b.Func.Param(idx)
}
// -----------------------------------------------------------------------------

110
ssa/eh.go
View File

@@ -91,6 +91,16 @@ func (p Program) tySiglongjmp() *types.Signature {
return p.sigljmpTy
}
// func() unsafe.Pointer
func (p Program) tyStacksave() *types.Signature {
if p.stackSaveTy == nil {
paramPtr := types.NewParam(token.NoPos, nil, "", p.VoidPtr().raw.Type)
params := types.NewTuple(paramPtr)
p.stackSaveTy = types.NewSignatureType(nil, nil, nil, nil, params, false)
}
return p.stackSaveTy
}
func (b Builder) AllocaSigjmpBuf() Expr {
prog := b.Prog
n := unsafe.Sizeof(sigjmpbuf{})
@@ -98,6 +108,12 @@ func (b Builder) AllocaSigjmpBuf() Expr {
return b.Alloca(size)
}
// declare ptr @llvm.stacksave.p0()
func (b Builder) StackSave() Expr {
fn := b.Pkg.cFunc("llvm.stacksave", b.Prog.tyStacksave())
return b.InlineCall(fn)
}
func (b Builder) Sigsetjmp(jb, savemask Expr) Expr {
if b.Prog.target.GOARCH == "wasm" {
return b.Setjmp(jb)
@@ -133,10 +149,6 @@ func (b Builder) Longjmp(jb, retval Expr) {
// -----------------------------------------------------------------------------
const (
deferKey = "__llgo_defer"
)
func (p Function) deferInitBuilder() (b Builder, next BasicBlock) {
b = p.NewBuilder()
next = b.setBlockMoveLast(p.blks[0])
@@ -146,7 +158,6 @@ func (p Function) deferInitBuilder() (b Builder, next BasicBlock) {
type aDefer struct {
nextBit int // next defer bit
key Expr // pthread TLS key
data Expr // pointer to runtime.Defer
bitsPtr Expr // pointer to defer bits
rethPtr Expr // next block of Rethrow
@@ -158,30 +169,6 @@ type aDefer struct {
stmts []func(bits Expr)
}
func (p Package) keyInit(name string) {
keyVar := p.VarOf(name)
if keyVar == nil {
return
}
prog := p.Prog
keyVar.InitNil()
keyVar.impl.SetLinkage(llvm.LinkOnceAnyLinkage)
b := p.afterBuilder()
eq := b.BinOp(token.EQL, b.Load(keyVar.Expr), prog.IntVal(0, prog.CInt()))
b.IfThen(eq, func() {
b.pthreadKeyCreate(keyVar.Expr, prog.Nil(prog.VoidPtr()))
})
}
func (p Package) newKey(name string) Global {
return p.NewVarEx(name, p.Prog.CIntPtr())
}
func (b Builder) deferKey() Expr {
return b.Load(b.Pkg.newKey(deferKey).Expr)
}
const (
// 0: addr sigjmpbuf
// 1: bits uintptr
@@ -214,17 +201,19 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
blks := self.MakeBlocks(2)
procBlk, rethrowBlk := blks[0], blks[1]
key := b.deferKey()
zero := prog.Val(uintptr(0))
link := Expr{b.pthreadGetspecific(key).impl, prog.DeferPtr()}
link := b.Call(b.Pkg.rtFunc("GetThreadDefer"))
jb := b.AllocaSigjmpBuf()
ptr := b.aggregateAlloca(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl)
ptr := b.aggregateAllocU(prog.Defer(), jb.impl, zero.impl, link.impl, procBlk.Addr().impl)
deferData := Expr{ptr, prog.DeferPtr()}
b.pthreadSetspecific(key, deferData)
b.Call(b.Pkg.rtFunc("SetThreadDefer"), deferData)
bitsPtr := b.FieldAddr(deferData, deferBits)
rethPtr := b.FieldAddr(deferData, deferRethrow)
rundPtr := b.FieldAddr(deferData, deferRunDefers)
argsPtr := b.FieldAddr(deferData, deferArgs)
// Initialize the args list so later guards (e.g. DeferAlways/DeferInLoop)
// can safely detect an empty chain without a prior push.
b.Store(argsPtr, prog.Nil(prog.VoidPtr()))
czero := prog.IntVal(0, prog.CInt())
retval := b.Sigsetjmp(jb, czero)
@@ -237,7 +226,6 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
b.If(b.BinOp(token.EQL, retval, czero), next, panicBlk)
self.defer_ = &aDefer{
key: key,
data: deferData,
bitsPtr: bitsPtr,
rethPtr: rethPtr,
@@ -262,8 +250,7 @@ func (b Builder) getDefer(kind DoAction) *aDefer {
// DeferData returns the defer data (*runtime.Defer).
func (b Builder) DeferData() Expr {
key := b.deferKey()
return Expr{b.pthreadGetspecific(key).impl, b.Prog.DeferPtr()}
return b.Call(b.Pkg.rtFunc("GetThreadDefer"))
}
// Defer emits a defer instruction.
@@ -278,14 +265,17 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) {
case DeferInCond:
prog = b.Prog
next := self.nextBit
if uintptr(next) >= unsafe.Sizeof(uintptr(0))*8 {
panic("too many conditional defers")
}
self.nextBit++
bits := b.Load(self.bitsPtr)
nextbit = prog.Val(uintptr(1 << next))
b.Store(self.bitsPtr, b.BinOp(token.OR, bits, nextbit))
case DeferAlways:
// nothing to do
default:
panic("todo: DeferInLoop is not supported - " + b.Func.Name())
case DeferInLoop:
// Loop defers rely on a dedicated drain loop inserted below.
}
typ := b.saveDeferArgs(self, fn, args)
self.stmts = append(self.stmts, func(bits Expr) {
@@ -298,6 +288,29 @@ func (b Builder) Defer(kind DoAction, fn Expr, args ...Expr) {
})
case DeferAlways:
b.callDefer(self, typ, fn, args)
case DeferInLoop:
prog := b.Prog
condBlk := b.Func.MakeBlock()
bodyBlk := b.Func.MakeBlock()
exitBlk := b.Func.MakeBlock()
// Control flow:
// condBlk: check argsPtr for non-nil to see if there's work to drain.
// bodyBlk: execute a single defer node, then jump back to condBlk.
// exitBlk: reached when the list is empty (argsPtr == nil).
// This mirrors runtime's linked-list unwinding semantics for loop defers.
// jump to condition check before executing
b.Jump(condBlk)
b.SetBlockEx(condBlk, AtEnd, true)
list := b.Load(self.argsPtr)
has := b.BinOp(token.NEQ, list, prog.Nil(prog.VoidPtr()))
b.If(has, bodyBlk, exitBlk)
b.SetBlockEx(bodyBlk, AtEnd, true)
b.callDefer(self, typ, fn, args)
b.Jump(condBlk)
b.SetBlockEx(exitBlk, AtEnd, true)
}
})
}
@@ -338,7 +351,7 @@ func (b Builder) saveDeferArgs(self *aDefer, fn Expr, args []Expr) Type {
flds[i+offset] = arg.impl
}
typ := prog.Struct(typs...)
ptr := Expr{b.aggregateMalloc(typ, flds...), prog.VoidPtr()}
ptr := Expr{b.aggregateAllocU(typ, flds...), prog.VoidPtr()}
b.Store(self.argsPtr, ptr)
return typ
}
@@ -349,19 +362,28 @@ func (b Builder) callDefer(self *aDefer, typ Type, fn Expr, args []Expr) {
return
}
prog := b.Prog
zero := prog.Nil(prog.VoidPtr())
list := b.Load(self.argsPtr)
has := b.BinOp(token.NEQ, list, zero)
// The guard is required because callDefer is reused by endDefer() after the
// list has been drained. Without this check we would dereference a nil
// pointer when no loop defers were recorded.
b.IfThen(has, func() {
ptr := b.Load(self.argsPtr)
data := b.Load(Expr{ptr.impl, prog.Pointer(typ)})
offset := 1
b.Store(self.argsPtr, Expr{b.getField(data, 0).impl, prog.VoidPtr()})
if fn.kind == vkClosure {
fn = b.getField(data, 1)
callFn := fn
if callFn.kind == vkClosure {
callFn = b.getField(data, 1)
offset++
}
for i := 0; i < len(args); i++ {
args[i] = b.getField(data, i+offset)
}
b.Call(fn, args...)
b.free(ptr)
b.Call(callFn, args...)
b.Call(b.Pkg.rtFunc("FreeDeferNode"), ptr)
})
}
// RunDefers emits instructions to run deferred instructions.
@@ -415,7 +437,7 @@ func (p Function) endDefer(b Builder) {
}
}
link := b.getField(b.Load(self.data), deferLink)
b.pthreadSetspecific(self.key, link)
b.Call(b.Pkg.rtFunc("SetThreadDefer"), link)
b.IndirectJump(b.Load(rundPtr), nexts)
b.SetBlockEx(panicBlk, AtEnd, false) // panicBlk: exec runDefers and rethrow

53
ssa/eh_loop_test.go Normal file
View File

@@ -0,0 +1,53 @@
//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_test
import (
"strings"
"testing"
"github.com/goplus/llgo/ssa"
"github.com/goplus/llgo/ssa/ssatest"
)
func TestDeferInLoopIR(t *testing.T) {
prog := ssatest.NewProgram(t, nil)
pkg := prog.NewPackage("foo", "foo")
callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo)
cb := callee.MakeBody(1)
cb.Return()
cb.EndBuild()
fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo)
b := fn.MakeBody(1)
fn.SetRecover(fn.MakeBlock())
// Ensure entry block has a terminator like real codegen
b.Return()
b.SetBlockEx(fn.Block(0), ssa.BeforeLast, true)
b.Defer(ssa.DeferInLoop, callee.Expr)
b.EndBuild()
ir := pkg.Module().String()
if !strings.Contains(ir, "icmp ne ptr") {
t.Fatalf("expected loop defer condition in IR, got:\n%s", ir)
}
}

View File

@@ -31,6 +31,19 @@ func (pkg Package) AddGlobalString(name string, value string) {
pkg.NewVarEx(name, prog.Pointer(styp)).Init(Expr{cv, styp})
}
// ConstString creates an SSA expression representing a Go string literal. The
// returned value is backed by an anonymous global constant and can be used to
// initialize package-level variables or other constant contexts that expect a
// Go string value.
func (pkg Package) ConstString(value string) Expr {
prog := pkg.Prog
styp := prog.String()
data := pkg.createGlobalStr(value)
length := prog.IntVal(uint64(len(value)), prog.Uintptr())
cv := llvm.ConstNamedStruct(styp.ll, []llvm.Value{data, length.impl})
return Expr{cv, styp}
}
// Undefined global string var by names
func (pkg Package) Undefined(names ...string) error {
prog := pkg.Prog

View File

@@ -195,6 +195,7 @@ type aProgram struct {
mallocTy *types.Signature
freeTy *types.Signature
memsetInlineTy *types.Signature
stackSaveTy *types.Signature
createKeyTy *types.Signature
getSpecTy *types.Signature
@@ -797,7 +798,6 @@ func (p Package) afterBuilder() Builder {
// AfterInit is called after the package is initialized (init all packages that depends on).
func (p Package) AfterInit(b Builder, ret BasicBlock) {
p.keyInit(deferKey)
doAfterb := p.afterb != nil
doPyLoadModSyms := p.pyHasModSyms()
if doAfterb || doPyLoadModSyms {

View File

@@ -60,6 +60,42 @@ func TestUnsafeString(t *testing.T) {
b.Return()
}
func TestTooManyConditionalDefers(t *testing.T) {
wd, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
os.Chdir("../../runtime")
defer os.Chdir(wd)
prog := NewProgram(nil)
prog.SetRuntime(func() *types.Package {
fset := token.NewFileSet()
imp := packages.NewImporter(fset)
pkg, _ := imp.Import(PkgRuntime)
return pkg
})
pkg := prog.NewPackage("foo", "foo")
target := pkg.NewFunc("f", NoArgsNoRet, InGo)
fn := pkg.NewFunc("main", NoArgsNoRet, InGo)
fn.SetRecover(fn.MakeBlock())
b := fn.MakeBody(1)
defer func() {
if r := recover(); r == nil {
t.Fatal("expected panic: too many conditional defers")
} else if r != "too many conditional defers" {
t.Fatalf("unexpected panic: %v", r)
}
}()
b.Return()
for i := 0; i < 65; i++ {
b.Defer(DeferInCond, target.Expr)
}
}
func TestPointerSize(t *testing.T) {
expected := unsafe.Sizeof(uintptr(0))
if size := NewProgram(nil).PointerSize(); size != int(expected) {

View File

@@ -57,11 +57,6 @@ func (p BasicBlock) Addr() Expr {
// -----------------------------------------------------------------------------
type dbgExpr struct {
ptr Expr
val Expr
}
type aBuilder struct {
impl llvm.Builder
blk BasicBlock
@@ -69,7 +64,6 @@ type aBuilder struct {
Pkg Package
Prog Program
dbgVars map[Expr]dbgExpr // save copied address and values for debug info
diScopeCache map[*types.Scope]DIScope // avoid duplicated DILexicalBlock(s)
}

326
test/defer_test.go Normal file
View File

@@ -0,0 +1,326 @@
/*
* 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 test
import (
"reflect"
"strconv"
"testing"
)
// runLoopDefers exercises a defer statement inside a loop and relies on
// defers executing after the loop but before the function returns.
func runLoopDefers() (result []int) {
for i := 0; i < 3; i++ {
v := i
defer func() {
result = append(result, v)
}()
}
return
}
func runLoopDeferCount(n int) (count int) {
for i := 0; i < n; i++ {
defer func() {
count++
}()
}
return
}
func runDeferRecover() (recovered any, ran bool) {
defer func() {
recovered = recover()
ran = true
}()
panic("defer recover sentinel")
}
func runNestedLoopDeferOrder() (order []string) {
outerNestedLoop(&order)
return
}
func outerNestedLoop(order *[]string) {
for i := 0; i < 3; i++ {
v := i
label := "outer:" + strconv.Itoa(v)
defer func(label string) {
*order = append(*order, label)
}(label)
}
middleNestedLoop(order)
}
func middleNestedLoop(order *[]string) {
for i := 0; i < 2; i++ {
v := i
label := "middle:" + strconv.Itoa(v)
defer func(label string) {
*order = append(*order, label)
}(label)
}
innerNestedLoop(order)
}
func innerNestedLoop(order *[]string) {
for i := 0; i < 4; i++ {
v := i
label := "inner:" + strconv.Itoa(v)
defer func(label string) {
*order = append(*order, label)
}(label)
}
}
func TestDeferInLoopOrder(t *testing.T) {
got := runLoopDefers()
want := []int{2, 1, 0}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unexpected defer order: got %v, want %v", got, want)
}
}
func TestDeferLoopStress(t *testing.T) {
const n = 1_000_000
if got := runLoopDeferCount(n); got != n {
t.Fatalf("unexpected count: got %d, want %d", got, n)
}
}
func TestDeferRecoverHandlesPanic(t *testing.T) {
got, ran := runDeferRecover()
want := "defer recover sentinel"
if !ran {
t.Fatalf("recover defer not executed")
}
str, ok := got.(string)
if !ok {
t.Fatalf("recover returned %T, want string", got)
}
if str != want {
t.Fatalf("unexpected recover value: got %q, want %q", str, want)
}
}
func TestNestedDeferLoops(t *testing.T) {
got := runNestedLoopDeferOrder()
want := []string{
"inner:3", "inner:2", "inner:1", "inner:0",
"middle:1", "middle:0",
"outer:2", "outer:1", "outer:0",
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unexpected nested order: got %v, want %v", got, want)
}
}
func runNestedConditionalDeferWithRecover() (order []string, recovered any) {
defer func() { recovered = recover() }()
nestedCondOuter(&order)
return
}
func nestedCondOuter(order *[]string) {
for i := 0; i < 3; i++ {
v := i
label := "outer:" + strconv.Itoa(v)
if v%2 == 0 {
defer func(label string) {
*order = append(*order, label)
}(label)
}
nestedCondMiddle(order, v)
}
}
func nestedCondMiddle(order *[]string, v int) {
for j := 0; j < 3; j++ {
u := j
label := "middle:" + strconv.Itoa(u)
if u < 2 {
defer func(label string) {
*order = append(*order, label)
}(label)
}
nestedCondInner(order)
}
if v == 1 {
panic("nested-conditional-boom")
}
}
func nestedCondInner(order *[]string) {
for k := 0; k < 2; k++ {
w := k
label := "inner:" + strconv.Itoa(w)
defer func(label string) {
*order = append(*order, label)
}(label)
}
}
func TestNestedConditionalDeferWithRecover(t *testing.T) {
gotOrder, gotRecovered := runNestedConditionalDeferWithRecover()
wantRecovered := "nested-conditional-boom"
if s, ok := gotRecovered.(string); !ok || s != wantRecovered {
t.Fatalf("unexpected recover value: got %v, want %q", gotRecovered, wantRecovered)
}
wantOrder := []string{
"inner:1", "inner:0",
"inner:1", "inner:0",
"inner:1", "inner:0",
"middle:1", "middle:0",
"inner:1", "inner:0",
"inner:1", "inner:0",
"inner:1", "inner:0",
"middle:1", "middle:0",
"outer:0",
}
if !reflect.DeepEqual(gotOrder, wantOrder) {
t.Fatalf("unexpected nested conditional order: got %v, want %v", gotOrder, wantOrder)
}
}
func callWithRecover(fn func()) (recovered any) {
defer func() { recovered = recover() }()
fn()
return
}
func loopBranchEven(order *[]string, i int) {
label := "even:" + strconv.Itoa(i)
defer func() { *order = append(*order, label) }()
}
func loopBranchOddNoRecover(order *[]string, i int) {
label := "odd-wrap:" + strconv.Itoa(i)
defer func() { *order = append(*order, label) }()
panic("odd-no-recover")
}
func loopBranchOddLocalRecover(order *[]string, i int) {
label := "odd-local:" + strconv.Itoa(i)
defer func() { *order = append(*order, label) }()
defer func() { _ = recover() }()
panic("odd-local-recover")
}
func runLoopBranchRecoverMixed(n int) (order []string, recoveredVals []any) {
for i := 0; i < n; i++ {
if i%2 == 0 {
loopBranchEven(&order, i)
} else if i%4 == 3 {
rec := callWithRecover(func() { loopBranchOddNoRecover(&order, i) })
recoveredVals = append(recoveredVals, rec)
} else {
loopBranchOddLocalRecover(&order, i)
}
}
return
}
func TestLoopBranchRecoverMixed(t *testing.T) {
order, recovered := runLoopBranchRecoverMixed(6)
wantOrder := []string{
"even:0",
"odd-local:1",
"even:2",
"odd-wrap:3",
"even:4",
"odd-local:5",
}
if !reflect.DeepEqual(order, wantOrder) {
t.Fatalf("unexpected loop/branch order: got %v, want %v", order, wantOrder)
}
if len(recovered) != 1 {
t.Fatalf("unexpected recovered count: got %d, want %d", len(recovered), 1)
}
if s, ok := recovered[0].(string); !ok || s != "odd-no-recover" {
t.Fatalf("unexpected recovered value: got %v, want %q", recovered[0], "odd-no-recover")
}
}
func deepInner(order *[]string) {
for i := 0; i < 2; i++ {
idx := i
label := "inner:" + strconv.Itoa(idx)
defer func(label string) {
*order = append(*order, label)
}(label)
if idx == 0 {
continue
}
panic("deep-boom")
}
}
func deepMiddle(order *[]string) {
for i := 0; i < 2; i++ {
idx := i
label := "middle:" + strconv.Itoa(idx)
defer func(label string) {
*order = append(*order, label)
}(label)
if idx == 0 {
continue
}
defer func() {
if rec := recover(); rec != nil {
panic(rec)
}
}()
deepInner(order)
}
}
func deepOuter(order *[]string) (recovered any) {
for i := 0; i < 2; i++ {
idx := i
label := "outer:" + strconv.Itoa(idx)
defer func(label string) {
*order = append(*order, label)
}(label)
if idx == 0 {
continue
}
defer func() {
if rec := recover(); rec != nil {
recovered = rec
}
}()
deepMiddle(order)
}
return
}
func TestPanicCrossTwoFunctionsRecover(t *testing.T) {
var order []string
recovered := deepOuter(&order)
if s, ok := recovered.(string); !ok || s != "deep-boom" {
t.Fatalf("unexpected recovered value: got %v, want %q", recovered, "deep-boom")
}
wantOrder := []string{
"inner:1", "inner:0",
"middle:1", "middle:0",
"outer:1", "outer:0",
}
if !reflect.DeepEqual(order, wantOrder) {
t.Fatalf("unexpected cross-function defer order: got %v, want %v", order, wantOrder)
}
}