Compare commits
107 Commits
xgopilot/c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ced14b9ec | ||
|
|
6b865828a3 | ||
|
|
b25ae1b4e7 | ||
|
|
a6516de181 | ||
|
|
4a268650fc | ||
|
|
7e4e53eb1c | ||
|
|
1473ee98f7 | ||
|
|
8c7e8b6290 | ||
|
|
2a4b2ef023 | ||
|
|
7abb468592 | ||
|
|
bf9c6abb23 | ||
|
|
2d80951e7d | ||
|
|
034b05c53c | ||
|
|
1ba7d1e561 | ||
|
|
d17ff2592a | ||
|
|
4b26cccc90 | ||
|
|
3a1d8693e9 | ||
|
|
2a52d422c5 | ||
|
|
b0f5d34b39 | ||
|
|
8ba8ec71b5 | ||
|
|
1e4616a758 | ||
|
|
22a43622a0 | ||
|
|
065126e270 | ||
|
|
552156ff40 | ||
|
|
36e84196c6 | ||
|
|
7f1e07755a | ||
|
|
af27d0475d | ||
|
|
bb29e8c768 | ||
|
|
eec2c271bd | ||
|
|
1ed924ed50 | ||
|
|
c46ca84122 | ||
|
|
b36be05c1e | ||
|
|
66a537ad29 | ||
|
|
33a00dff1b | ||
|
|
e4a69ce413 | ||
|
|
531f69ae6a | ||
|
|
812dfd45c9 | ||
|
|
e2bb68489d | ||
|
|
9b76be9e9e | ||
|
|
2f65c98eb4 | ||
|
|
aeb5d82d3e | ||
|
|
317de80b42 | ||
|
|
0455ad4443 | ||
|
|
e459ca928b | ||
|
|
21fef123d2 | ||
|
|
983a189c18 | ||
|
|
cb173f91d0 | ||
|
|
30bde9f6b5 | ||
|
|
3307860d33 | ||
|
|
16709411a0 | ||
|
|
3f8c95cf87 | ||
|
|
0dbe528e6d | ||
|
|
91f6ad9bfa | ||
|
|
4d1906d722 | ||
|
|
ff4a180860 | ||
|
|
7ce2733edf | ||
|
|
e11ae0e21b | ||
|
|
dac3365c73 | ||
|
|
2363d28d57 | ||
|
|
830e8e7058 | ||
|
|
34caf518a1 | ||
|
|
a676ba29db | ||
|
|
d368cade1c | ||
|
|
4e374a99ff | ||
|
|
41b403aef7 | ||
|
|
cda9d682f2 | ||
|
|
742bfd95a2 | ||
|
|
01af858a2e | ||
|
|
c557aa2af1 | ||
|
|
c15c7a05b7 | ||
|
|
8f5f36e447 | ||
|
|
bf6f785988 | ||
|
|
86cafff113 | ||
|
|
3c88949557 | ||
|
|
533ba9ebd8 | ||
|
|
f34062166b | ||
|
|
c036243b3f | ||
|
|
e16fc69ce3 | ||
|
|
a2c81327ea | ||
|
|
a74ca940e2 | ||
|
|
0c68ae00c9 | ||
|
|
8d6d1b76f2 | ||
|
|
e47728b053 | ||
|
|
9b397725da | ||
|
|
420ad8e010 | ||
|
|
6e41cc702f | ||
|
|
0a94a54772 | ||
|
|
d09ce613c8 | ||
|
|
e614edfab4 | ||
|
|
29504f2560 | ||
|
|
ee49fad4a4 | ||
|
|
946a4bf990 | ||
|
|
8b61831b0d | ||
|
|
e96625cf07 | ||
|
|
8ac7ada7f9 | ||
|
|
0b00e06185 | ||
|
|
c4fdb1edc0 | ||
|
|
224e3b9440 | ||
|
|
7fbcc8cd10 | ||
|
|
938f883be9 | ||
|
|
d145967c35 | ||
|
|
2e0fc5fb7f | ||
|
|
d8cf93a6cd | ||
|
|
01ada11b74 | ||
|
|
c3dbc580aa | ||
|
|
93e660a2b0 | ||
|
|
2ec5653f5e |
2
.github/actions/setup-deps/action.yml
vendored
2
.github/actions/setup-deps/action.yml
vendored
@@ -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
|
||||
|
||||
|
||||
8
.github/workflows/doc.yml
vendored
8
.github/workflows/doc.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: ./.github/actions/setup-go
|
||||
@@ -84,7 +84,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
@@ -136,7 +136,7 @@ jobs:
|
||||
- ubuntu-latest
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v6
|
||||
|
||||
2
.github/workflows/fmt.yml
vendored
2
.github/workflows/fmt.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Go
|
||||
uses: ./.github/actions/setup-go
|
||||
|
||||
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
llvm: [19]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
|
||||
27
.github/workflows/llgo.yml
vendored
27
.github/workflows/llgo.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
wget -P ./_demo/c/llama2-c https://huggingface.co/karpathy/tinyllamas/resolve/main/stories15M.bin
|
||||
|
||||
- name: Upload model as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: llama2-model
|
||||
path: ./_demo/c/llama2-c/stories15M.bin
|
||||
@@ -46,13 +46,13 @@ jobs:
|
||||
go: ["1.21.13", "1.22.12", "1.23.6", "1.24.2"]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
llvm-version: ${{matrix.llvm}}
|
||||
- name: Download model artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: llama2-model
|
||||
path: ./_demo/c/llama2-c/
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
if ${{ startsWith(matrix.os, 'macos') }}; then
|
||||
DEMO_PKG="cargs_darwin_arm64.zip"
|
||||
else
|
||||
DEMO_PKG="cargs_linux_amd64.zip"
|
||||
DEMO_PKG="cargs_linux_amd64.zip"
|
||||
fi
|
||||
|
||||
mkdir -p ./_demo/c/cargs/libs
|
||||
@@ -133,6 +133,13 @@ jobs:
|
||||
chmod +x test.sh
|
||||
./test.sh
|
||||
|
||||
- name: Test export with different symbol names on embedded targets
|
||||
run: |
|
||||
echo "Testing //export with different symbol names on embedded targets..."
|
||||
cd _demo/embed/export
|
||||
chmod +x verify_export.sh
|
||||
./verify_export.sh
|
||||
|
||||
- name: _xtool build tests
|
||||
run: |
|
||||
cd _xtool
|
||||
@@ -159,7 +166,7 @@ jobs:
|
||||
go: ["1.24.2"]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
@@ -186,11 +193,15 @@ jobs:
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{matrix.go}}
|
||||
|
||||
- name: Test Baremetal GC
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: runtime/internal/runtime/tinygogc
|
||||
run: llgo test -tags testGC .
|
||||
- name: run llgo test
|
||||
run: |
|
||||
llgo test ./...
|
||||
|
||||
|
||||
hello:
|
||||
continue-on-error: true
|
||||
timeout-minutes: 30
|
||||
@@ -201,7 +212,7 @@ jobs:
|
||||
go: ["1.21.13", "1.22.12", "1.23.6", "1.24.2"]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
@@ -259,7 +270,7 @@ jobs:
|
||||
llvm: [19]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
|
||||
22
.github/workflows/release-build.yml
vendored
22
.github/workflows/release-build.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
linux-cache-key: ${{ steps.cache-keys.outputs.linux-key }}
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Calculate cache keys
|
||||
id: cache-keys
|
||||
run: |
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Check Linux sysroot cache
|
||||
id: cache-linux-sysroot
|
||||
uses: actions/cache/restore@v4
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
needs: [setup, populate-linux-sysroot]
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
- name: Set up Release
|
||||
uses: ./.github/actions/setup-goreleaser
|
||||
with:
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
release --verbose --skip=publish,nfpm,snapcraft --snapshot --clean
|
||||
|
||||
- name: Upload Darwin AMD64 Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: llgo-darwin-amd64
|
||||
path: .dist/*darwin-amd64.tar.gz
|
||||
@@ -90,7 +90,7 @@ jobs:
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Upload Darwin ARM64 Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: llgo-darwin-arm64
|
||||
path: .dist/*darwin-arm64.tar.gz
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Upload Linux AMD64 Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: llgo-linux-amd64
|
||||
path: .dist/*linux-amd64.tar.gz
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Upload Linux ARM64 Artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: llgo-linux-arm64
|
||||
path: .dist/*linux-arm64.tar.gz
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
include-hidden-files: true
|
||||
|
||||
- name: Upload Checksums
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: llgo-checksums
|
||||
path: .dist/*checksums.txt
|
||||
@@ -148,7 +148,7 @@ jobs:
|
||||
go-mod-version: "1.24"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
@@ -158,7 +158,7 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Download Platform Artifact
|
||||
uses: actions/download-artifact@v5
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: llgo-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: .
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Release
|
||||
|
||||
2
.github/workflows/targets.yml
vendored
2
.github/workflows/targets.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
llvm: [19]
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/setup-deps
|
||||
with:
|
||||
|
||||
50
CLAUDE.md
50
CLAUDE.md
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
22
_demo/c/stacksave/stacksave_amd64.go
Normal file
22
_demo/c/stacksave/stacksave_amd64.go
Normal 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")
|
||||
}
|
||||
}
|
||||
22
_demo/c/stacksave/stacksave_arm64.go
Normal file
22
_demo/c/stacksave/stacksave_arm64.go
Normal 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")
|
||||
}
|
||||
}
|
||||
67
_demo/embed/export/main.go
Normal file
67
_demo/embed/export/main.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/goplus/lib/c"
|
||||
)
|
||||
|
||||
// This demo shows how to use //export with different symbol names on embedded targets.
|
||||
//
|
||||
// On embedded targets, you can export Go functions with different C symbol names.
|
||||
// This is useful for hardware interrupt handlers that require specific names.
|
||||
|
||||
// Standard Go export - same name
|
||||
//
|
||||
//export HelloWorld
|
||||
func HelloWorld() {
|
||||
c.Printf(c.Str("Hello from "))
|
||||
c.Printf(c.Str("HelloWorld\n"))
|
||||
}
|
||||
|
||||
// Embedded target export - different name
|
||||
// Go function name: interruptLPSPI2
|
||||
// Exported C symbol: LPSPI2_IRQHandler
|
||||
//
|
||||
//export LPSPI2_IRQHandler
|
||||
func interruptLPSPI2() {
|
||||
c.Printf(c.Str("LPSPI2 interrupt "))
|
||||
c.Printf(c.Str("handler called\n"))
|
||||
}
|
||||
|
||||
// Embedded target export - different name
|
||||
// Go function name: systemTickHandler
|
||||
// Exported C symbol: SysTick_Handler
|
||||
//
|
||||
//export SysTick_Handler
|
||||
func systemTickHandler() {
|
||||
c.Printf(c.Str("SysTick "))
|
||||
c.Printf(c.Str("handler called\n"))
|
||||
}
|
||||
|
||||
// Embedded target export - different name
|
||||
// Go function name: Add
|
||||
// Exported C symbol: AddFunc
|
||||
//
|
||||
//export AddFunc
|
||||
func Add(a, b int) int {
|
||||
result := a + b
|
||||
c.Printf(c.Str("AddFunc(%d, %d) = %d\n"), a, b, result)
|
||||
return result
|
||||
}
|
||||
|
||||
func main() {
|
||||
c.Printf(c.Str("=== Export Demo ===\n\n"))
|
||||
|
||||
// Call exported functions directly from Go
|
||||
c.Printf(c.Str("Calling HelloWorld:\n"))
|
||||
HelloWorld()
|
||||
|
||||
c.Printf(c.Str("\nSimulating hardware interrupts:\n"))
|
||||
interruptLPSPI2()
|
||||
systemTickHandler()
|
||||
|
||||
c.Printf(c.Str("\nTesting function with return value:\n"))
|
||||
result := Add(10, 20)
|
||||
c.Printf(c.Str("Result: %d\n"), result)
|
||||
|
||||
c.Printf(c.Str("\n=== Demo Complete ===\n"))
|
||||
}
|
||||
54
_demo/embed/export/verify_export.sh
Executable file
54
_demo/embed/export/verify_export.sh
Executable file
@@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "Building for embedded target..."
|
||||
|
||||
# Build for embedded target as executable
|
||||
# Use llgo directly instead of llgo.sh to avoid go.mod version check
|
||||
llgo build -o test-verify --target=esp32 .
|
||||
|
||||
echo "Checking exported symbols..."
|
||||
|
||||
# Get exported symbols
|
||||
exported_symbols=$(nm -gU ./test-verify.elf | grep -E "(HelloWorld|LPSPI2_IRQHandler|SysTick_Handler|AddFunc)" | awk '{print $NF}')
|
||||
|
||||
echo ""
|
||||
echo "Exported symbols:"
|
||||
echo "$exported_symbols" | awk '{print " " $0}'
|
||||
echo ""
|
||||
|
||||
# Check expected symbols
|
||||
expected=("HelloWorld" "LPSPI2_IRQHandler" "SysTick_Handler" "AddFunc")
|
||||
missing=""
|
||||
|
||||
for symbol in "${expected[@]}"; do
|
||||
if ! echo "$exported_symbols" | grep -q "^$symbol$"; then
|
||||
missing="$missing $symbol"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$missing" ]; then
|
||||
echo "❌ Missing symbols:$missing"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Symbol name mapping verification:"
|
||||
echo " HelloWorld -> HelloWorld"
|
||||
echo " interruptLPSPI2 -> LPSPI2_IRQHandler"
|
||||
echo " systemTickHandler -> SysTick_Handler"
|
||||
echo " Add -> AddFunc"
|
||||
echo ""
|
||||
echo "🎉 All export symbols verified successfully!"
|
||||
echo ""
|
||||
|
||||
echo "Testing that non-embedded target rejects different export names..."
|
||||
# Build without --target should fail with panic
|
||||
if llgo build -o test-notarget . 2>&1 | grep -q 'export comment has wrong name "LPSPI2_IRQHandler"'; then
|
||||
echo "✅ Correctly rejected different export name on non-embedded target"
|
||||
else
|
||||
echo "❌ Should have panicked with 'export comment has wrong name' error"
|
||||
exit 1
|
||||
fi
|
||||
echo ""
|
||||
echo "Note: Different symbol names are only supported on embedded targets."
|
||||
@@ -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")
|
||||
|
||||
|
||||
147
_demo/go/gobuild/demo.go
Normal file
147
_demo/go/gobuild/demo.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/build"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("runtime.Compiler = %q\n", runtime.Compiler)
|
||||
|
||||
// Test 1: Check build.Default context
|
||||
ctx := build.Default
|
||||
fmt.Printf("build.Default.Compiler = %q\n", ctx.Compiler)
|
||||
if ctx.Compiler != "gc" {
|
||||
panic(fmt.Sprintf("expected build.Default.Compiler to be \"gc\", got %q", ctx.Compiler))
|
||||
}
|
||||
|
||||
if len(ctx.ToolTags) == 0 {
|
||||
panic("expected build.Default.ToolTags to be non-empty")
|
||||
}
|
||||
fmt.Printf("build.Default.ToolTags = %v\n", ctx.ToolTags)
|
||||
|
||||
if len(ctx.ReleaseTags) == 0 {
|
||||
panic("expected build.Default.ReleaseTags to be non-empty")
|
||||
}
|
||||
fmt.Printf("build.Default.ReleaseTags count = %d\n", len(ctx.ReleaseTags))
|
||||
|
||||
// Validate GOOS and GOARCH are set
|
||||
if ctx.GOOS == "" {
|
||||
panic("expected build.Default.GOOS to be non-empty")
|
||||
}
|
||||
if ctx.GOARCH == "" {
|
||||
panic("expected build.Default.GOARCH to be non-empty")
|
||||
}
|
||||
fmt.Printf("build.Default.GOOS = %q, GOARCH = %q\n", ctx.GOOS, ctx.GOARCH)
|
||||
|
||||
// Test 2: Import standard library package with FindOnly
|
||||
pkg, err := build.Import("fmt", "", build.FindOnly)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build.Import(\"fmt\") failed: %v", err))
|
||||
}
|
||||
if pkg.ImportPath != "fmt" {
|
||||
panic(fmt.Sprintf("expected ImportPath \"fmt\", got %q", pkg.ImportPath))
|
||||
}
|
||||
if !pkg.Goroot {
|
||||
panic("expected fmt package to be in GOROOT")
|
||||
}
|
||||
fmt.Printf("build.Import(\"fmt\"): ImportPath=%s, Goroot=%v\n", pkg.ImportPath, pkg.Goroot)
|
||||
|
||||
// Test 3: Import nested standard library package
|
||||
osPkg, err := build.Import("os/exec", "", build.FindOnly)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build.Import(\"os/exec\") failed: %v", err))
|
||||
}
|
||||
if osPkg.ImportPath != "os/exec" {
|
||||
panic(fmt.Sprintf("expected ImportPath \"os/exec\", got %q", osPkg.ImportPath))
|
||||
}
|
||||
if !osPkg.Goroot {
|
||||
panic("expected os/exec package to be in GOROOT")
|
||||
}
|
||||
fmt.Printf("build.Import(\"os/exec\"): ImportPath=%s, Goroot=%v\n", osPkg.ImportPath, osPkg.Goroot)
|
||||
|
||||
// Test 4: Import internal package (should succeed with FindOnly)
|
||||
internalPkg, err := build.Import("internal/cpu", "", build.FindOnly)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build.Import(\"internal/cpu\") failed: %v", err))
|
||||
}
|
||||
if internalPkg.ImportPath != "internal/cpu" {
|
||||
panic(fmt.Sprintf("expected ImportPath \"internal/cpu\", got %q", internalPkg.ImportPath))
|
||||
}
|
||||
fmt.Printf("build.Import(\"internal/cpu\"): ImportPath=%s\n", internalPkg.ImportPath)
|
||||
|
||||
// Test 5: Import with srcDir parameter
|
||||
runtimePkg, err := build.Import("runtime", "", build.FindOnly)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build.Import(\"runtime\") failed: %v", err))
|
||||
}
|
||||
if runtimePkg.ImportPath != "runtime" {
|
||||
panic(fmt.Sprintf("expected ImportPath \"runtime\", got %q", runtimePkg.ImportPath))
|
||||
}
|
||||
if runtimePkg.Dir == "" {
|
||||
panic("expected runtime package Dir to be non-empty")
|
||||
}
|
||||
fmt.Printf("build.Import(\"runtime\"): ImportPath=%s, Dir exists=%v\n", runtimePkg.ImportPath, runtimePkg.Dir != "")
|
||||
|
||||
// Test 6: ImportDir with current directory
|
||||
dirPkg, err := build.ImportDir(".", build.FindOnly)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build.ImportDir(\".\") failed: %v", err))
|
||||
}
|
||||
// Note: Name might be empty with FindOnly mode as it doesn't read source files
|
||||
fmt.Printf("build.ImportDir(\".\"): Dir=%s, ImportPath=%s\n", dirPkg.Dir, dirPkg.ImportPath)
|
||||
|
||||
// Test 7: IsLocalImport with various paths
|
||||
testCases := []struct {
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{"./foo", true},
|
||||
{"../bar", true},
|
||||
{"./", true},
|
||||
{"fmt", false},
|
||||
{"github.com/user/repo", false},
|
||||
{"", false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
result := build.IsLocalImport(tc.path)
|
||||
if result != tc.expected {
|
||||
panic(fmt.Sprintf("build.IsLocalImport(%q): expected %v, got %v", tc.path, tc.expected, result))
|
||||
}
|
||||
}
|
||||
fmt.Printf("build.IsLocalImport: all test cases passed\n")
|
||||
|
||||
// Test 8: Verify Context has expected fields
|
||||
if ctx.GOPATH == "" && ctx.GOROOT == "" {
|
||||
panic("expected either GOPATH or GOROOT to be set")
|
||||
}
|
||||
fmt.Printf("build.Default.GOROOT exists = %v\n", ctx.GOROOT != "")
|
||||
|
||||
// Test 9: Import with AllowBinary flag
|
||||
binaryPkg, err := build.Import("fmt", "", build.FindOnly|build.AllowBinary)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("build.Import with AllowBinary failed: %v", err))
|
||||
}
|
||||
if binaryPkg.ImportPath != "fmt" {
|
||||
panic(fmt.Sprintf("expected ImportPath \"fmt\", got %q", binaryPkg.ImportPath))
|
||||
}
|
||||
fmt.Printf("build.Import(\"fmt\") with AllowBinary: success\n")
|
||||
|
||||
// Test 10: Verify compiler tag in build context
|
||||
hasCompilerTag := false
|
||||
for _, tag := range ctx.ReleaseTags {
|
||||
if strings.HasPrefix(tag, "go1.") {
|
||||
hasCompilerTag = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasCompilerTag {
|
||||
panic("expected at least one go1.x release tag")
|
||||
}
|
||||
fmt.Printf("build.Default.ReleaseTags: contains go1.x tags = %v\n", hasCompilerTag)
|
||||
|
||||
fmt.Printf("\nSuccess! All go/build public functions work correctly with llgo\n")
|
||||
fmt.Printf("Total tests passed: 10\n")
|
||||
}
|
||||
356
_demo/go/gotoken/main.go
Normal file
356
_demo/go/gotoken/main.go
Normal 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
488
_demo/go/gotypes/main.go
Normal 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")
|
||||
}
|
||||
49
_demo/go/reflectindirect/reflect-indirect.go
Normal file
49
_demo/go/reflectindirect/reflect-indirect.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func main() {
|
||||
x := 42
|
||||
p := &x
|
||||
|
||||
// Test 1: Non-pointer value - should return same value
|
||||
v1 := reflect.Indirect(reflect.ValueOf(x))
|
||||
if !v1.IsValid() || v1.Interface() != 42 {
|
||||
panic("Non-pointer test failed: expected 42")
|
||||
}
|
||||
|
||||
// Test 2: Pointer - should dereference
|
||||
v2 := reflect.Indirect(reflect.ValueOf(p))
|
||||
if !v2.IsValid() || v2.Interface() != 42 {
|
||||
panic("Pointer dereference test failed: expected 42")
|
||||
}
|
||||
|
||||
// Test 3: Nil pointer - should return invalid Value
|
||||
var nilPtr *int
|
||||
v3 := reflect.Indirect(reflect.ValueOf(nilPtr))
|
||||
if v3.IsValid() {
|
||||
panic("Nil pointer test failed: expected invalid Value")
|
||||
}
|
||||
|
||||
// Test 4: Struct value - should return same value
|
||||
type Person struct {
|
||||
Name string
|
||||
Age int
|
||||
}
|
||||
person := Person{Name: "Alice", Age: 30}
|
||||
v4 := reflect.Indirect(reflect.ValueOf(person))
|
||||
if !v4.IsValid() || v4.Interface().(Person).Name != "Alice" || v4.Interface().(Person).Age != 30 {
|
||||
panic("Struct value test failed: expected Person{Name: Alice, Age: 30}")
|
||||
}
|
||||
|
||||
// Test 5: Struct pointer - should dereference
|
||||
personPtr := &Person{Name: "Bob", Age: 25}
|
||||
v5 := reflect.Indirect(reflect.ValueOf(personPtr))
|
||||
if !v5.IsValid() || v5.Interface().(Person).Name != "Bob" || v5.Interface().(Person).Age != 25 {
|
||||
panic("Struct pointer test failed: expected Person{Name: Bob, Age: 25}")
|
||||
}
|
||||
|
||||
println("PASS")
|
||||
}
|
||||
25
cl/_testdata/geometry1370/geometry.go
Normal file
25
cl/_testdata/geometry1370/geometry.go
Normal 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)
|
||||
}
|
||||
90
cl/_testdata/geometry1370/out.ll
Normal file
90
cl/_testdata/geometry1370/out.ll
Normal 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")
|
||||
51
cl/_testgo/defercomplex/in.go
Normal file
51
cl/_testgo/defercomplex/in.go
Normal 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))
|
||||
}
|
||||
1
cl/_testgo/defercomplex/out.ll
Normal file
1
cl/_testgo/defercomplex/out.ll
Normal file
@@ -0,0 +1 @@
|
||||
;
|
||||
7
cl/_testgo/deferloop/in.go
Normal file
7
cl/_testgo/deferloop/in.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
for i := 0; i < 3; i++ {
|
||||
defer println("loop", i)
|
||||
}
|
||||
}
|
||||
1
cl/_testgo/deferloop/out.ll
Normal file
1
cl/_testgo/deferloop/out.ll
Normal file
@@ -0,0 +1 @@
|
||||
;
|
||||
11
cl/_testgo/interface1370/in.go
Normal file
11
cl/_testgo/interface1370/in.go
Normal 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())
|
||||
}
|
||||
333
cl/_testgo/interface1370/out.ll
Normal file
333
cl/_testgo/interface1370/out.ll
Normal 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)
|
||||
11
cl/_testgo/rewrite/dep/dep.go
Normal file
11
cl/_testgo/rewrite/dep/dep.go
Normal 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)
|
||||
}
|
||||
23
cl/_testgo/rewrite/main.go
Normal file
23
cl/_testgo/rewrite/main.go
Normal 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())
|
||||
}
|
||||
1
cl/_testgo/rewrite/out.ll
Normal file
1
cl/_testgo/rewrite/out.ll
Normal file
@@ -0,0 +1 @@
|
||||
;
|
||||
14
cl/_testrt/stacksave/in.go
Normal file
14
cl/_testrt/stacksave/in.go
Normal 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)
|
||||
}
|
||||
34
cl/_testrt/stacksave/out.ll
Normal file
34
cl/_testrt/stacksave/out.ll
Normal 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 }
|
||||
@@ -393,10 +393,10 @@ func TestErrImport(t *testing.T) {
|
||||
|
||||
func TestErrInitLinkname(t *testing.T) {
|
||||
var ctx context
|
||||
ctx.initLinkname("//llgo:link abc", func(name string) (string, bool, bool) {
|
||||
ctx.initLinkname("//llgo:link abc", func(name string, isExport bool) (string, bool, bool) {
|
||||
return "", false, false
|
||||
})
|
||||
ctx.initLinkname("//go:linkname Printf printf", func(name string) (string, bool, bool) {
|
||||
ctx.initLinkname("//go:linkname Printf printf", func(name string, isExport bool) (string, bool, bool) {
|
||||
return "", false, false
|
||||
})
|
||||
defer func() {
|
||||
@@ -404,7 +404,7 @@ func TestErrInitLinkname(t *testing.T) {
|
||||
t.Fatal("initLinkname: no error?")
|
||||
}
|
||||
}()
|
||||
ctx.initLinkname("//go:linkname Printf printf", func(name string) (string, bool, bool) {
|
||||
ctx.initLinkname("//go:linkname Printf printf", func(name string, isExport bool) (string, bool, bool) {
|
||||
return "foo.Printf", false, name == "Printf"
|
||||
})
|
||||
}
|
||||
@@ -506,3 +506,238 @@ func TestInstantiate(t *testing.T) {
|
||||
t.Fatal("error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleExportDiffName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
enableExportRename bool
|
||||
line string
|
||||
fullName string
|
||||
inPkgName string
|
||||
wantHasLinkname bool
|
||||
wantLinkname string
|
||||
wantExport string
|
||||
}{
|
||||
{
|
||||
name: "ExportDiffNames_DifferentName",
|
||||
enableExportRename: true,
|
||||
line: "//export IRQ_Handler",
|
||||
fullName: "pkg.HandleInterrupt",
|
||||
inPkgName: "HandleInterrupt",
|
||||
wantHasLinkname: true,
|
||||
wantLinkname: "IRQ_Handler",
|
||||
wantExport: "IRQ_Handler",
|
||||
},
|
||||
{
|
||||
name: "ExportDiffNames_SameName",
|
||||
enableExportRename: true,
|
||||
line: "//export SameName",
|
||||
fullName: "pkg.SameName",
|
||||
inPkgName: "SameName",
|
||||
wantHasLinkname: true,
|
||||
wantLinkname: "SameName",
|
||||
wantExport: "SameName",
|
||||
},
|
||||
{
|
||||
name: "ExportDiffNames_WithSpaces",
|
||||
enableExportRename: true,
|
||||
line: "//export Timer_Callback ",
|
||||
fullName: "pkg.OnTimerTick",
|
||||
inPkgName: "OnTimerTick",
|
||||
wantHasLinkname: true,
|
||||
wantLinkname: "Timer_Callback",
|
||||
wantExport: "Timer_Callback",
|
||||
},
|
||||
{
|
||||
name: "ExportDiffNames_Disabled_MatchingName",
|
||||
enableExportRename: false,
|
||||
line: "//export Func",
|
||||
fullName: "pkg.Func",
|
||||
inPkgName: "Func",
|
||||
wantHasLinkname: true,
|
||||
wantLinkname: "Func",
|
||||
wantExport: "Func",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Save and restore global state
|
||||
oldEnableExportRename := enableExportRename
|
||||
defer func() {
|
||||
EnableExportRename(oldEnableExportRename)
|
||||
}()
|
||||
EnableExportRename(tt.enableExportRename)
|
||||
|
||||
// Setup context
|
||||
prog := llssa.NewProgram(nil)
|
||||
pkg := prog.NewPackage("test", "test")
|
||||
ctx := &context{
|
||||
prog: prog,
|
||||
pkg: pkg,
|
||||
}
|
||||
|
||||
// Call initLinkname with closure that mimics initLinknameByDoc behavior
|
||||
ret := ctx.initLinkname(tt.line, func(name string, isExport bool) (string, bool, bool) {
|
||||
return tt.fullName, false, name == tt.inPkgName || (isExport && enableExportRename)
|
||||
})
|
||||
|
||||
// Verify result
|
||||
hasLinkname := (ret == hasLinkname)
|
||||
if hasLinkname != tt.wantHasLinkname {
|
||||
t.Errorf("hasLinkname = %v, want %v", hasLinkname, tt.wantHasLinkname)
|
||||
}
|
||||
|
||||
if tt.wantHasLinkname {
|
||||
// Check linkname was set
|
||||
if link, ok := prog.Linkname(tt.fullName); !ok || link != tt.wantLinkname {
|
||||
t.Errorf("linkname = %q (ok=%v), want %q", link, ok, tt.wantLinkname)
|
||||
}
|
||||
|
||||
// Check export was set
|
||||
exports := pkg.ExportFuncs()
|
||||
if export, ok := exports[tt.fullName]; !ok || export != tt.wantExport {
|
||||
t.Errorf("export = %q (ok=%v), want %q", export, ok, tt.wantExport)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitLinknameByDocExportDiffNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
enableExportRename bool
|
||||
doc *ast.CommentGroup
|
||||
fullName string
|
||||
inPkgName string
|
||||
wantExported bool // Whether the symbol should be exported with different name
|
||||
wantLinkname string
|
||||
wantExport string
|
||||
}{
|
||||
{
|
||||
name: "WithExportDiffNames_DifferentNameExported",
|
||||
enableExportRename: true,
|
||||
doc: &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
{Text: "//export IRQ_Handler"},
|
||||
},
|
||||
},
|
||||
fullName: "pkg.HandleInterrupt",
|
||||
inPkgName: "HandleInterrupt",
|
||||
wantExported: true,
|
||||
wantLinkname: "IRQ_Handler",
|
||||
wantExport: "IRQ_Handler",
|
||||
},
|
||||
{
|
||||
name: "WithoutExportDiffNames_NotExported",
|
||||
enableExportRename: false,
|
||||
doc: &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
{Text: "//export DifferentName"},
|
||||
},
|
||||
},
|
||||
fullName: "pkg.HandleInterrupt",
|
||||
inPkgName: "HandleInterrupt",
|
||||
wantExported: false,
|
||||
// Without enableExportRename, it goes through normal flow which expects same name
|
||||
// The symbol "DifferentName" won't be found, so no export happens
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Without enableExportRename, export with different name will panic
|
||||
if !tt.wantExported && !tt.enableExportRename {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("expected panic for export with different name when enableExportRename=false")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Save and restore global state
|
||||
oldEnableExportRename := enableExportRename
|
||||
defer func() {
|
||||
EnableExportRename(oldEnableExportRename)
|
||||
}()
|
||||
EnableExportRename(tt.enableExportRename)
|
||||
|
||||
// Setup context
|
||||
prog := llssa.NewProgram(nil)
|
||||
pkg := prog.NewPackage("test", "test")
|
||||
ctx := &context{
|
||||
prog: prog,
|
||||
pkg: pkg,
|
||||
}
|
||||
|
||||
// Call initLinknameByDoc
|
||||
ctx.initLinknameByDoc(tt.doc, tt.fullName, tt.inPkgName, false)
|
||||
|
||||
// Verify export behavior
|
||||
exports := pkg.ExportFuncs()
|
||||
if tt.wantExported {
|
||||
// Should have exported the symbol with different name
|
||||
if export, ok := exports[tt.fullName]; !ok || export != tt.wantExport {
|
||||
t.Errorf("export = %q (ok=%v), want %q", export, ok, tt.wantExport)
|
||||
}
|
||||
// Check linkname was also set
|
||||
if link, ok := prog.Linkname(tt.fullName); !ok || link != tt.wantLinkname {
|
||||
t.Errorf("linkname = %q (ok=%v), want %q", link, ok, tt.wantLinkname)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitLinkExportDiffNames(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
enableExportRename bool
|
||||
line string
|
||||
wantPanic bool
|
||||
}{
|
||||
{
|
||||
name: "ExportDiffNames_Enabled_NoError",
|
||||
enableExportRename: true,
|
||||
line: "//export IRQ_Handler",
|
||||
wantPanic: false,
|
||||
},
|
||||
{
|
||||
name: "ExportDiffNames_Disabled_Panic",
|
||||
enableExportRename: false,
|
||||
line: "//export IRQ_Handler",
|
||||
wantPanic: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.wantPanic {
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Error("expected panic but didn't panic")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
oldEnableExportRename := enableExportRename
|
||||
defer func() {
|
||||
EnableExportRename(oldEnableExportRename)
|
||||
}()
|
||||
EnableExportRename(tt.enableExportRename)
|
||||
|
||||
prog := llssa.NewProgram(nil)
|
||||
pkg := prog.NewPackage("test", "test")
|
||||
ctx := &context{
|
||||
prog: prog,
|
||||
pkg: pkg,
|
||||
}
|
||||
|
||||
ctx.initLinkname(tt.line, func(inPkgName string, isExport bool) (fullName string, isVar, ok bool) {
|
||||
// Simulate initLinknames scenario: symbol not found (like in decl packages)
|
||||
return "", false, false
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
150
cl/compile.go
150
cl/compile.go
@@ -53,6 +53,11 @@ var (
|
||||
enableDbg bool
|
||||
enableDbgSyms bool
|
||||
disableInline bool
|
||||
|
||||
// enableExportRename enables //export to use different C symbol names than Go function names.
|
||||
// This is for TinyGo compatibility when using -target flag for embedded targets.
|
||||
// Currently, using -target implies TinyGo embedded target mode.
|
||||
enableExportRename bool
|
||||
)
|
||||
|
||||
// SetDebug sets debug flags.
|
||||
@@ -73,6 +78,12 @@ func EnableTrace(b bool) {
|
||||
enableCallTracing = b
|
||||
}
|
||||
|
||||
// EnableExportRename enables or disables //export with different C symbol names.
|
||||
// This is enabled when using -target flag for TinyGo compatibility.
|
||||
func EnableExportRename(b bool) {
|
||||
enableExportRename = b
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type instrOrValue interface {
|
||||
@@ -98,18 +109,19 @@ type pkgInfo struct {
|
||||
type none = struct{}
|
||||
|
||||
type context struct {
|
||||
prog llssa.Program
|
||||
pkg llssa.Package
|
||||
fn llssa.Function
|
||||
fset *token.FileSet
|
||||
goProg *ssa.Program
|
||||
goTyps *types.Package
|
||||
goPkg *ssa.Package
|
||||
pyMod string
|
||||
skips map[string]none
|
||||
loaded map[*types.Package]*pkgInfo // loaded packages
|
||||
bvals map[ssa.Value]llssa.Expr // block values
|
||||
vargs map[*ssa.Alloc][]llssa.Expr // varargs
|
||||
prog llssa.Program
|
||||
pkg llssa.Package
|
||||
fn llssa.Function
|
||||
fset *token.FileSet
|
||||
goProg *ssa.Program
|
||||
goTyps *types.Package
|
||||
goPkg *ssa.Package
|
||||
pyMod string
|
||||
skips map[string]none
|
||||
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 +138,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,7 +239,16 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) {
|
||||
log.Println("==> NewVar", name, typ)
|
||||
}
|
||||
g := pkg.NewVar(name, typ, llssa.Background(vtype))
|
||||
if define {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -263,6 +336,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 +345,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 +357,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 +451,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 +471,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 +866,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 +888,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 +936,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 +945,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 {
|
||||
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)
|
||||
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)
|
||||
scope := b.DIScope(p.fn, v.Parent())
|
||||
return b.DIVarAuto(scope, pos, v.Name(), t)
|
||||
}
|
||||
@@ -970,12 +1059,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 +1107,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")
|
||||
|
||||
22
cl/import.go
22
cl/import.go
@@ -73,7 +73,7 @@ func (p *pkgSymInfo) initLinknames(ctx *context) {
|
||||
lines := bytes.Split(b, sep)
|
||||
for _, line := range lines {
|
||||
if bytes.HasPrefix(line, commentPrefix) {
|
||||
ctx.initLinkname(string(line), func(inPkgName string) (fullName string, isVar, ok bool) {
|
||||
ctx.initLinkname(string(line), func(inPkgName string, isExport bool) (fullName string, isVar, ok bool) {
|
||||
if sym, ok := p.syms[inPkgName]; ok && file == sym.file {
|
||||
return sym.fullName, sym.isVar, true
|
||||
}
|
||||
@@ -277,8 +277,8 @@ func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName s
|
||||
if doc != nil {
|
||||
for n := len(doc.List) - 1; n >= 0; n-- {
|
||||
line := doc.List[n].Text
|
||||
ret := p.initLinkname(line, func(name string) (_ string, _, ok bool) {
|
||||
return fullName, isVar, name == inPkgName
|
||||
ret := p.initLinkname(line, func(name string, isExport bool) (_ string, _, ok bool) {
|
||||
return fullName, isVar, name == inPkgName || (isExport && enableExportRename)
|
||||
})
|
||||
if ret != unknownDirective {
|
||||
return ret == hasLinkname
|
||||
@@ -294,7 +294,7 @@ const (
|
||||
unknownDirective = -1
|
||||
)
|
||||
|
||||
func (p *context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) int {
|
||||
func (p *context) initLinkname(line string, f func(inPkgName string, isExport bool) (fullName string, isVar, ok bool)) int {
|
||||
const (
|
||||
linkname = "//go:linkname "
|
||||
llgolink = "//llgo:link "
|
||||
@@ -324,17 +324,24 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
|
||||
return noDirective
|
||||
}
|
||||
|
||||
func (p *context) initLink(line string, prefix int, export bool, f func(inPkgName string) (fullName string, isVar, ok bool)) {
|
||||
func (p *context) initLink(line string, prefix int, export bool, f func(inPkgName string, isExport bool) (fullName string, isVar, ok bool)) {
|
||||
text := strings.TrimSpace(line[prefix:])
|
||||
if idx := strings.IndexByte(text, ' '); idx > 0 {
|
||||
inPkgName := text[:idx]
|
||||
if fullName, _, ok := f(inPkgName); ok {
|
||||
if fullName, _, ok := f(inPkgName, export); ok {
|
||||
link := strings.TrimLeft(text[idx+1:], " ")
|
||||
p.prog.SetLinkname(fullName, link)
|
||||
if export {
|
||||
p.pkg.SetExport(fullName, link)
|
||||
}
|
||||
} else {
|
||||
// Export with different names already processed by initLinknameByDoc
|
||||
if export && enableExportRename {
|
||||
return
|
||||
}
|
||||
if export {
|
||||
panic(fmt.Sprintf("export comment has wrong name %q", inPkgName))
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "==>", line)
|
||||
fmt.Fprintf(os.Stderr, "llgo: linkname %s not found and ignored\n", inPkgName)
|
||||
}
|
||||
@@ -503,7 +510,8 @@ const (
|
||||
llgoCgoCheckPointer = llgoCgoBase + 0x6
|
||||
llgoCgoCgocall = llgoCgoBase + 0x7
|
||||
|
||||
llgoAsm = llgoInstrBase + 0x40
|
||||
llgoAsm = llgoInstrBase + 0x40
|
||||
llgoStackSave = llgoInstrBase + 0x41
|
||||
|
||||
llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin)
|
||||
)
|
||||
|
||||
@@ -425,7 +425,8 @@ var llgoInstrs = map[string]int{
|
||||
"_cgoCheckPointer": llgoCgoCheckPointer,
|
||||
"_cgo_runtime_cgocall": llgoCgoCgocall,
|
||||
|
||||
"asm": llgoAsm,
|
||||
"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
169
cl/rewrite_internal_test.go
Normal 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
32
doc/defer-tls-gc.md
Normal 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
4
go.mod
@@ -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
8
go.sum
@@ -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=
|
||||
|
||||
@@ -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 == "" {
|
||||
@@ -214,6 +220,11 @@ func Do(args []string, conf *Config) ([]Package, error) {
|
||||
conf.Goarch = export.GOARCH
|
||||
}
|
||||
|
||||
// Enable different export names for TinyGo compatibility when using -target
|
||||
if conf.Target != "" {
|
||||
cl.EnableExportRename(true)
|
||||
}
|
||||
|
||||
verbose := conf.Verbose
|
||||
patterns := args
|
||||
tags := "llgo,math_big_pure_go"
|
||||
@@ -335,6 +346,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 +360,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 +376,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 +636,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 names, ok := ctx.buildConf.GlobalNames[pkg.PkgPath]; ok {
|
||||
err := pkg.LPkg.Undefined(names...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pkg.ExportFile += "-global"
|
||||
pkg.ExportFile, err = exportObject(ctx, pkg.PkgPath+".global", pkg.ExportFile, []byte(pkg.LPkg.String()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !token.IsIdentifier(varName) {
|
||||
panic(fmt.Errorf("invalid variable name for rewrite: %q", varName))
|
||||
}
|
||||
global := prog.NewPackage("", "global")
|
||||
for name, value := range ctx.buildConf.GlobalDatas {
|
||||
global.AddGlobalString(name, value)
|
||||
if len(value) > maxRewriteValueLength {
|
||||
panic(fmt.Errorf("rewrite value too large: %d bytes", len(value)))
|
||||
}
|
||||
return global, nil
|
||||
}
|
||||
|
||||
// compileExtraFiles compiles extra files (.s/.c) from target configuration and returns object files
|
||||
@@ -752,7 +760,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 +777,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
|
||||
@@ -780,11 +787,11 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
|
||||
}
|
||||
})
|
||||
// Generate main module file (needed for global variables even in library modes)
|
||||
entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit)
|
||||
entryPkg := genMainModule(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit)
|
||||
entryObjFile, err := exportObject(ctx, entryPkg.PkgPath, entryPkg.ExportFile, []byte(entryPkg.LPkg.String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// defer os.Remove(entryLLFile)
|
||||
objFiles = append(objFiles, entryObjFile)
|
||||
|
||||
// Compile extra files from target configuration
|
||||
@@ -794,14 +801,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
|
||||
@@ -914,118 +913,6 @@ func needStart(ctx *context) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func genMainModuleFile(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) (path string, err error) {
|
||||
var (
|
||||
pyInitDecl string
|
||||
pyInit string
|
||||
rtInitDecl string
|
||||
rtInit string
|
||||
)
|
||||
mainPkgPath := pkg.PkgPath
|
||||
if needRuntime {
|
||||
rtInit = "call void @\"" + rtPkgPath + ".init\"()"
|
||||
rtInitDecl = "declare void @\"" + rtPkgPath + ".init\"()"
|
||||
}
|
||||
if needPyInit {
|
||||
pyInit = "call void @Py_Initialize()"
|
||||
pyInitDecl = "declare void @Py_Initialize()"
|
||||
}
|
||||
declSizeT := "%size_t = type i64"
|
||||
if is32Bits(ctx.buildConf.Goarch) {
|
||||
declSizeT = "%size_t = type i32"
|
||||
}
|
||||
stdioDecl := ""
|
||||
stdioNobuf := ""
|
||||
if IsStdioNobuf() {
|
||||
stdioDecl = `
|
||||
@stdout = external global ptr
|
||||
@stderr = external global ptr
|
||||
@__stdout = external global ptr
|
||||
@__stderr = external global ptr
|
||||
declare i32 @setvbuf(ptr, ptr, i32, %size_t)
|
||||
`
|
||||
stdioNobuf = `
|
||||
; Set stdout with no buffer
|
||||
%stdout_is_null = icmp eq ptr @stdout, null
|
||||
%stdout_ptr = select i1 %stdout_is_null, ptr @__stdout, ptr @stdout
|
||||
call i32 @setvbuf(ptr %stdout_ptr, ptr null, i32 2, %size_t 0)
|
||||
; Set stderr with no buffer
|
||||
%stderr_ptr = select i1 %stdout_is_null, ptr @__stderr, ptr @stderr
|
||||
call i32 @setvbuf(ptr %stderr_ptr, ptr null, i32 2, %size_t 0)
|
||||
`
|
||||
}
|
||||
// TODO(lijie): workaround for libc-free
|
||||
// Remove main/_start when -buildmode and libc are ready
|
||||
startDefine := `
|
||||
define weak void @_start() {
|
||||
; argc = 0
|
||||
%argc = add i32 0, 0
|
||||
; argv = null
|
||||
%argv = inttoptr i64 0 to i8**
|
||||
call i32 @main(i32 %argc, i8** %argv)
|
||||
ret void
|
||||
}
|
||||
`
|
||||
mainDefine := "define i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr"
|
||||
if !needStart(ctx) && isWasmTarget(ctx.buildConf.Goos) {
|
||||
mainDefine = "define hidden noundef i32 @__main_argc_argv(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr"
|
||||
}
|
||||
if !needStart(ctx) {
|
||||
startDefine = ""
|
||||
}
|
||||
|
||||
var mainCode string
|
||||
// For library modes (c-archive, c-shared), only generate global variables
|
||||
if ctx.buildConf.BuildMode != BuildModeExe {
|
||||
mainCode = `; ModuleID = 'main'
|
||||
source_filename = "main"
|
||||
@__llgo_argc = global i32 0, align 4
|
||||
@__llgo_argv = global ptr null, align 8
|
||||
`
|
||||
} else {
|
||||
// For executable mode, generate full main function
|
||||
mainCode = fmt.Sprintf(`; ModuleID = 'main'
|
||||
source_filename = "main"
|
||||
%s
|
||||
@__llgo_argc = global i32 0, align 4
|
||||
@__llgo_argv = global ptr null, align 8
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
declare void @"%s.init"()
|
||||
declare void @"%s.main"()
|
||||
define weak void @runtime.init() {
|
||||
ret void
|
||||
}
|
||||
|
||||
; TODO(lijie): workaround for syscall patch
|
||||
define weak void @"syscall.init"() {
|
||||
ret void
|
||||
}
|
||||
|
||||
%s
|
||||
|
||||
%s {
|
||||
_llgo_0:
|
||||
store i32 %%0, ptr @__llgo_argc, align 4
|
||||
store ptr %%1, ptr @__llgo_argv, align 8
|
||||
%s
|
||||
%s
|
||||
%s
|
||||
call void @runtime.init()
|
||||
call void @"%s.init"()
|
||||
call void @"%s.main"()
|
||||
ret i32 0
|
||||
}
|
||||
`, declSizeT, stdioDecl,
|
||||
pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath,
|
||||
startDefine, mainDefine, stdioNobuf,
|
||||
pyInit, rtInit, mainPkgPath, mainPkgPath)
|
||||
}
|
||||
|
||||
return exportObject(ctx, pkg.PkgPath+".main", pkg.ExportFile+"-main", []byte(mainCode))
|
||||
}
|
||||
|
||||
func is32Bits(goarch string) bool {
|
||||
return goarch == "386" || goarch == "arm" || goarch == "mips" || goarch == "wasm"
|
||||
}
|
||||
@@ -1050,7 +937,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 +964,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 +977,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 +1003,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) {
|
||||
@@ -1171,8 +1067,9 @@ type aPackage struct {
|
||||
AltPkg *packages.Cached
|
||||
LPkg llssa.Package
|
||||
|
||||
LinkArgs []string
|
||||
LLFiles []string
|
||||
LinkArgs []string
|
||||
LLFiles []string
|
||||
rewriteVars map[string]string
|
||||
}
|
||||
|
||||
type Package = *aPackage
|
||||
@@ -1193,7 +1090,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 +1099,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 {
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/goplus/llgo/internal/mockable"
|
||||
@@ -94,4 +97,70 @@ 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)
|
||||
}
|
||||
|
||||
func TestRunPrintfWithStdioNobuf(t *testing.T) {
|
||||
t.Setenv(llgoStdioNobuf, "1")
|
||||
mockRun([]string{"../../cl/_testdata/printf"}, &Config{Mode: ModeRun})
|
||||
}
|
||||
|
||||
231
internal/build/main_module.go
Normal file
231
internal/build/main_module.go
Normal file
@@ -0,0 +1,231 @@
|
||||
//go:build !llgo
|
||||
// +build !llgo
|
||||
|
||||
/*
|
||||
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Package build contains the llgo compiler build orchestration logic.
|
||||
//
|
||||
// The main_module.go file generates the entry point module for llgo programs,
|
||||
// which contains the main() function, initialization sequence, and global
|
||||
// variables like argc/argv. This module is generated differently depending on
|
||||
// BuildMode (exe, c-archive, c-shared).
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/goplus/llgo/internal/packages"
|
||||
llvm "github.com/goplus/llvm"
|
||||
|
||||
llssa "github.com/goplus/llgo/ssa"
|
||||
)
|
||||
|
||||
// genMainModule generates the main entry module for an llgo program.
|
||||
//
|
||||
// The module contains argc/argv globals and, for executable build modes,
|
||||
// the entry function that wires initialization and main. For C archive or
|
||||
// shared library modes, only the globals are emitted.
|
||||
func genMainModule(ctx *context, rtPkgPath string, pkg *packages.Package, needRuntime, needPyInit bool) Package {
|
||||
prog := ctx.prog
|
||||
mainPkg := prog.NewPackage("", pkg.ID+".main")
|
||||
|
||||
argcVar := mainPkg.NewVarEx("__llgo_argc", prog.Pointer(prog.Int32()))
|
||||
argcVar.Init(prog.Zero(prog.Int32()))
|
||||
|
||||
argvValueType := prog.Pointer(prog.CStr())
|
||||
argvVar := mainPkg.NewVarEx("__llgo_argv", prog.Pointer(argvValueType))
|
||||
argvVar.InitNil()
|
||||
|
||||
exportFile := pkg.ExportFile
|
||||
if exportFile == "" {
|
||||
exportFile = pkg.PkgPath
|
||||
}
|
||||
mainAPkg := &aPackage{
|
||||
Package: &packages.Package{
|
||||
PkgPath: pkg.PkgPath + ".main",
|
||||
ExportFile: exportFile + "-main",
|
||||
},
|
||||
LPkg: mainPkg,
|
||||
}
|
||||
|
||||
if ctx.buildConf.BuildMode != BuildModeExe {
|
||||
return mainAPkg
|
||||
}
|
||||
|
||||
runtimeStub := defineWeakNoArgStub(mainPkg, "runtime.init")
|
||||
// TODO(lijie): workaround for syscall patch
|
||||
defineWeakNoArgStub(mainPkg, "syscall.init")
|
||||
|
||||
var pyInit llssa.Function
|
||||
if needPyInit {
|
||||
pyInit = declareNoArgFunc(mainPkg, "Py_Initialize")
|
||||
}
|
||||
|
||||
var rtInit llssa.Function
|
||||
if needRuntime {
|
||||
rtInit = declareNoArgFunc(mainPkg, rtPkgPath+".init")
|
||||
}
|
||||
|
||||
mainInit := declareNoArgFunc(mainPkg, pkg.PkgPath+".init")
|
||||
mainMain := declareNoArgFunc(mainPkg, pkg.PkgPath+".main")
|
||||
|
||||
entryFn := defineEntryFunction(ctx, mainPkg, argcVar, argvVar, argvValueType, runtimeStub, mainInit, mainMain, pyInit, rtInit)
|
||||
|
||||
if needStart(ctx) {
|
||||
defineStart(mainPkg, entryFn, argvValueType)
|
||||
}
|
||||
|
||||
return mainAPkg
|
||||
}
|
||||
|
||||
// defineEntryFunction creates the program's entry function. The name is
|
||||
// "main" for standard targets, or "__main_argc_argv" with hidden visibility
|
||||
// for WASM targets that don't require _start.
|
||||
//
|
||||
// The entry stores argc/argv, optionally disables stdio buffering, runs
|
||||
// initialization hooks (Python, runtime, package init), and finally calls
|
||||
// main.main before returning 0.
|
||||
func defineEntryFunction(ctx *context, pkg llssa.Package, argcVar, argvVar llssa.Global, argvType llssa.Type, runtimeStub, mainInit, mainMain llssa.Function, pyInit, rtInit llssa.Function) llssa.Function {
|
||||
prog := pkg.Prog
|
||||
entryName := "main"
|
||||
if !needStart(ctx) && isWasmTarget(ctx.buildConf.Goos) {
|
||||
entryName = "__main_argc_argv"
|
||||
}
|
||||
sig := newEntrySignature(argvType.RawType())
|
||||
fn := pkg.NewFunc(entryName, sig, llssa.InC)
|
||||
fnVal := pkg.Module().NamedFunction(entryName)
|
||||
if entryName != "main" {
|
||||
fnVal.SetVisibility(llvm.HiddenVisibility)
|
||||
fnVal.SetUnnamedAddr(true)
|
||||
}
|
||||
b := fn.MakeBody(1)
|
||||
b.Store(argcVar.Expr, fn.Param(0))
|
||||
b.Store(argvVar.Expr, fn.Param(1))
|
||||
if IsStdioNobuf() {
|
||||
emitStdioNobuf(b, pkg, ctx.buildConf.Goos)
|
||||
}
|
||||
if pyInit != nil {
|
||||
b.Call(pyInit.Expr)
|
||||
}
|
||||
if rtInit != nil {
|
||||
b.Call(rtInit.Expr)
|
||||
}
|
||||
b.Call(runtimeStub.Expr)
|
||||
b.Call(mainInit.Expr)
|
||||
b.Call(mainMain.Expr)
|
||||
b.Return(prog.IntVal(0, prog.Int32()))
|
||||
return fn
|
||||
}
|
||||
|
||||
func defineStart(pkg llssa.Package, entry llssa.Function, argvType llssa.Type) {
|
||||
fn := pkg.NewFunc("_start", llssa.NoArgsNoRet, llssa.InC)
|
||||
pkg.Module().NamedFunction("_start").SetLinkage(llvm.WeakAnyLinkage)
|
||||
b := fn.MakeBody(1)
|
||||
prog := pkg.Prog
|
||||
b.Call(entry.Expr, prog.IntVal(0, prog.Int32()), prog.Nil(argvType))
|
||||
b.Return()
|
||||
}
|
||||
|
||||
func declareNoArgFunc(pkg llssa.Package, name string) llssa.Function {
|
||||
return pkg.NewFunc(name, llssa.NoArgsNoRet, llssa.InC)
|
||||
}
|
||||
|
||||
func defineWeakNoArgStub(pkg llssa.Package, name string) llssa.Function {
|
||||
fn := pkg.NewFunc(name, llssa.NoArgsNoRet, llssa.InC)
|
||||
pkg.Module().NamedFunction(name).SetLinkage(llvm.WeakAnyLinkage)
|
||||
b := fn.MakeBody(1)
|
||||
b.Return()
|
||||
return fn
|
||||
}
|
||||
|
||||
const (
|
||||
// ioNoBuf represents the _IONBF flag for setvbuf (no buffering)
|
||||
ioNoBuf = 2
|
||||
)
|
||||
|
||||
// emitStdioNobuf generates code to disable buffering on stdout and stderr
|
||||
// when the LLGO_STDIO_NOBUF environment variable is set. Only Darwin uses
|
||||
// the alternate `__stdoutp`/`__stderrp` symbols; other targets rely on the
|
||||
// standard `stdout`/`stderr` globals.
|
||||
func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goos string) {
|
||||
prog := pkg.Prog
|
||||
streamType := prog.VoidPtr()
|
||||
streamPtrType := prog.Pointer(streamType)
|
||||
|
||||
stdoutName := "stdout"
|
||||
stderrName := "stderr"
|
||||
if goos == "darwin" {
|
||||
stdoutName = "__stdoutp"
|
||||
stderrName = "__stderrp"
|
||||
}
|
||||
stdout := declareExternalPtrGlobal(pkg, stdoutName, streamPtrType)
|
||||
stderr := declareExternalPtrGlobal(pkg, stderrName, streamPtrType)
|
||||
stdoutPtr := b.Load(stdout)
|
||||
stderrPtr := b.Load(stderr)
|
||||
sizeType := prog.Uintptr()
|
||||
setvbuf := declareSetvbuf(pkg, streamPtrType, prog.CStr(), prog.Int32(), sizeType)
|
||||
|
||||
noBufMode := prog.IntVal(ioNoBuf, prog.Int32())
|
||||
zeroSize := prog.Zero(sizeType)
|
||||
nullBuf := prog.Nil(prog.CStr())
|
||||
|
||||
b.Call(setvbuf.Expr, stdoutPtr, nullBuf, noBufMode, zeroSize)
|
||||
b.Call(setvbuf.Expr, stderrPtr, nullBuf, noBufMode, zeroSize)
|
||||
}
|
||||
|
||||
func declareExternalPtrGlobal(pkg llssa.Package, name string, valueType llssa.Type) llssa.Expr {
|
||||
global := pkg.NewVarEx(name, valueType)
|
||||
pkg.Module().NamedGlobal(name).SetLinkage(llvm.ExternalLinkage)
|
||||
return global.Expr
|
||||
}
|
||||
|
||||
func declareSetvbuf(pkg llssa.Package, streamPtrType, bufPtrType, intType, sizeType llssa.Type) llssa.Function {
|
||||
sig := newSignature(
|
||||
[]types.Type{
|
||||
streamPtrType.RawType(),
|
||||
bufPtrType.RawType(),
|
||||
intType.RawType(),
|
||||
sizeType.RawType(),
|
||||
},
|
||||
[]types.Type{intType.RawType()},
|
||||
)
|
||||
return pkg.NewFunc("setvbuf", sig, llssa.InC)
|
||||
}
|
||||
|
||||
func tupleOf(tys ...types.Type) *types.Tuple {
|
||||
if len(tys) == 0 {
|
||||
return types.NewTuple()
|
||||
}
|
||||
vars := make([]*types.Var, len(tys))
|
||||
for i, t := range tys {
|
||||
vars[i] = types.NewParam(token.NoPos, nil, "", t)
|
||||
}
|
||||
return types.NewTuple(vars...)
|
||||
}
|
||||
|
||||
func newSignature(params []types.Type, results []types.Type) *types.Signature {
|
||||
return types.NewSignatureType(nil, nil, nil, tupleOf(params...), tupleOf(results...), false)
|
||||
}
|
||||
|
||||
func newEntrySignature(argvType types.Type) *types.Signature {
|
||||
return newSignature(
|
||||
[]types.Type{types.Typ[types.Int32], argvType},
|
||||
[]types.Type{types.Typ[types.Int32]},
|
||||
)
|
||||
}
|
||||
70
internal/build/main_module_test.go
Normal file
70
internal/build/main_module_test.go
Normal file
@@ -0,0 +1,70 @@
|
||||
//go:build !llgo
|
||||
// +build !llgo
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/goplus/llvm"
|
||||
|
||||
"github.com/goplus/llgo/internal/packages"
|
||||
llssa "github.com/goplus/llgo/ssa"
|
||||
)
|
||||
|
||||
func init() {
|
||||
llssa.Initialize(llssa.InitAll)
|
||||
}
|
||||
|
||||
func TestGenMainModuleExecutable(t *testing.T) {
|
||||
llvm.InitializeAllTargets()
|
||||
t.Setenv(llgoStdioNobuf, "")
|
||||
ctx := &context{
|
||||
prog: llssa.NewProgram(nil),
|
||||
buildConf: &Config{
|
||||
BuildMode: BuildModeExe,
|
||||
Goos: "linux",
|
||||
Goarch: "amd64",
|
||||
},
|
||||
}
|
||||
pkg := &packages.Package{PkgPath: "example.com/foo", ExportFile: "foo.a"}
|
||||
mod := genMainModule(ctx, llssa.PkgRuntime, pkg, true, true)
|
||||
if mod.ExportFile != "foo.a-main" {
|
||||
t.Fatalf("unexpected export file: %s", mod.ExportFile)
|
||||
}
|
||||
ir := mod.LPkg.String()
|
||||
checks := []string{
|
||||
"define i32 @main(",
|
||||
"call void @Py_Initialize()",
|
||||
"call void @\"example.com/foo.init\"()",
|
||||
"define weak void @_start()",
|
||||
}
|
||||
for _, want := range checks {
|
||||
if !strings.Contains(ir, want) {
|
||||
t.Fatalf("main module IR missing %q:\n%s", want, ir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenMainModuleLibrary(t *testing.T) {
|
||||
llvm.InitializeAllTargets()
|
||||
t.Setenv(llgoStdioNobuf, "")
|
||||
ctx := &context{
|
||||
prog: llssa.NewProgram(nil),
|
||||
buildConf: &Config{
|
||||
BuildMode: BuildModeCArchive,
|
||||
Goos: "linux",
|
||||
Goarch: "amd64",
|
||||
},
|
||||
}
|
||||
pkg := &packages.Package{PkgPath: "example.com/foo", ExportFile: "foo.a"}
|
||||
mod := genMainModule(ctx, llssa.PkgRuntime, pkg, false, false)
|
||||
ir := mod.LPkg.String()
|
||||
if strings.Contains(ir, "define i32 @main") {
|
||||
t.Fatalf("library mode should not emit main function:\n%s", ir)
|
||||
}
|
||||
if !strings.Contains(ir, "@__llgo_argc = global i32 0") {
|
||||
t.Fatalf("library mode missing argc global:\n%s", ir)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !llgo
|
||||
|
||||
package libc
|
||||
|
||||
import (
|
||||
|
||||
2
internal/env/version.go
vendored
2
internal/env/version.go
vendored
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
@@ -22,6 +22,7 @@ var hasAltPkg = map[string]none{
|
||||
"crypto/sha256": {},
|
||||
"crypto/sha512": {},
|
||||
"crypto/subtle": {},
|
||||
"go/build": {},
|
||||
"go/parser": {},
|
||||
"hash/crc32": {},
|
||||
"hash/maphash": {},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !nogc
|
||||
// +build !nogc
|
||||
//go:build !nogc && !baremetal
|
||||
|
||||
/*
|
||||
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build nogc
|
||||
// +build nogc
|
||||
//go:build nogc || baremetal
|
||||
|
||||
/*
|
||||
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build llgo
|
||||
//go:build llgo && !baremetal
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build llgo && !nogc
|
||||
//go:build llgo && !baremetal && !nogc
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build llgo && nogc
|
||||
//go:build llgo && (nogc || baremetal)
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
|
||||
34
runtime/internal/clite/tls/tls_stub.go
Normal file
34
runtime/internal/clite/tls/tls_stub.go
Normal file
@@ -0,0 +1,34 @@
|
||||
//go:build !llgo || baremetal
|
||||
|
||||
/*
|
||||
* 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() {}
|
||||
19
runtime/internal/lib/go/build/build.go
Normal file
19
runtime/internal/lib/go/build/build.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package build provides alternative implementations for go/build.
|
||||
// We override build.Default.Compiler in an init function.
|
||||
|
||||
package build
|
||||
|
||||
import (
|
||||
"go/build"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// LLGO PATCH: Override build.Default.Compiler to be "gc" instead of "llgo"
|
||||
// This prevents "unknown compiler" errors when user code uses go/build package
|
||||
// Even though runtime.Compiler = "llgo", we set build.Default.Compiler = "gc"
|
||||
build.Default.Compiler = "gc"
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
"github.com/goplus/llgo/runtime/internal/runtime"
|
||||
)
|
||||
|
||||
// llgo:skip init CompareString
|
||||
// llgo:skip init
|
||||
type _bytealg struct{}
|
||||
|
||||
func IndexByte(b []byte, ch byte) int {
|
||||
|
||||
@@ -1759,6 +1759,16 @@ func ValueOf(i any) Value {
|
||||
return unpackEface(i)
|
||||
}
|
||||
|
||||
// Indirect returns the value that v points to.
|
||||
// If v is a nil pointer, Indirect returns a zero Value.
|
||||
// If v is not a pointer, Indirect returns v.
|
||||
func Indirect(v Value) Value {
|
||||
if v.Kind() != Pointer {
|
||||
return v
|
||||
}
|
||||
return v.Elem()
|
||||
}
|
||||
|
||||
// arrayAt returns the i-th element of p,
|
||||
// an array whose elements are eltSize bytes wide.
|
||||
// The array pointed at by p must have at least i+1 elements:
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
package runtime
|
||||
|
||||
import "runtime"
|
||||
|
||||
// Layout of in-memory per-function information prepared by linker
|
||||
// See https://golang.org/s/go12symtab.
|
||||
// Keep in sync with linker (../cmd/link/internal/ld/pcln.go:/pclntab)
|
||||
@@ -30,10 +28,6 @@ func StopTrace() {
|
||||
panic("todo: runtime.StopTrace")
|
||||
}
|
||||
|
||||
func ReadMemStats(m *runtime.MemStats) {
|
||||
panic("todo: runtime.ReadMemStats")
|
||||
}
|
||||
|
||||
func SetMutexProfileFraction(rate int) int {
|
||||
panic("todo: runtime.SetMutexProfileFraction")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
//go:build !nogc
|
||||
//go:build !nogc && !baremetal
|
||||
|
||||
package runtime
|
||||
|
||||
import "github.com/goplus/llgo/runtime/internal/clite/bdwgc"
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/goplus/llgo/runtime/internal/clite/bdwgc"
|
||||
)
|
||||
|
||||
func ReadMemStats(m *runtime.MemStats) {
|
||||
panic("todo: runtime.ReadMemStats")
|
||||
}
|
||||
|
||||
func GC() {
|
||||
bdwgc.Gcollect()
|
||||
|
||||
29
runtime/internal/lib/runtime/runtime_gc_baremetal.go
Normal file
29
runtime/internal/lib/runtime/runtime_gc_baremetal.go
Normal file
@@ -0,0 +1,29 @@
|
||||
//go:build !nogc && baremetal
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/goplus/llgo/runtime/internal/runtime/tinygogc"
|
||||
)
|
||||
|
||||
func ReadMemStats(m *runtime.MemStats) {
|
||||
stats := tinygogc.ReadGCStats()
|
||||
m.Alloc = stats.Alloc
|
||||
m.TotalAlloc = stats.TotalAlloc
|
||||
m.Sys = stats.Sys
|
||||
m.Mallocs = stats.Mallocs
|
||||
m.Frees = stats.Frees
|
||||
m.HeapAlloc = stats.HeapAlloc
|
||||
m.HeapSys = stats.HeapSys
|
||||
m.HeapIdle = stats.HeapIdle
|
||||
m.HeapInuse = stats.HeapInuse
|
||||
m.StackInuse = stats.StackInuse
|
||||
m.StackSys = stats.StackSys
|
||||
m.GCSys = stats.GCSys
|
||||
}
|
||||
|
||||
func GC() {
|
||||
tinygogc.GC()
|
||||
}
|
||||
@@ -140,6 +140,10 @@ type Func struct {
|
||||
opaque struct{} // unexported field to disallow conversions
|
||||
}
|
||||
|
||||
func (f *Func) Name() string {
|
||||
panic("todo")
|
||||
}
|
||||
|
||||
// moduledata records information about the layout of the executable
|
||||
// image. It is written by the linker. Any changes here must be
|
||||
// matched changes to the code in cmd/link/internal/ld/symtab.go:symtab.
|
||||
|
||||
40
runtime/internal/runtime/defer_tls.go
Normal file
40
runtime/internal/runtime/defer_tls.go
Normal 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()
|
||||
}
|
||||
184
runtime/internal/runtime/tinygogc/gc.go
Normal file
184
runtime/internal/runtime/tinygogc/gc.go
Normal file
@@ -0,0 +1,184 @@
|
||||
//go:build baremetal || testGC
|
||||
|
||||
package tinygogc
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type GCStats struct {
|
||||
// General statistics.
|
||||
|
||||
// Alloc is bytes of allocated heap objects.
|
||||
//
|
||||
// This is the same as HeapAlloc (see below).
|
||||
Alloc uint64
|
||||
|
||||
// TotalAlloc is cumulative bytes allocated for heap objects.
|
||||
//
|
||||
// TotalAlloc increases as heap objects are allocated, but
|
||||
// unlike Alloc and HeapAlloc, it does not decrease when
|
||||
// objects are freed.
|
||||
TotalAlloc uint64
|
||||
|
||||
// Sys is the total bytes of memory obtained from the OS.
|
||||
//
|
||||
// Sys is the sum of the XSys fields below. Sys measures the
|
||||
// virtual address space reserved by the Go runtime for the
|
||||
// heap, stacks, and other internal data structures. It's
|
||||
// likely that not all of the virtual address space is backed
|
||||
// by physical memory at any given moment, though in general
|
||||
// it all was at some point.
|
||||
Sys uint64
|
||||
|
||||
// Mallocs is the cumulative count of heap objects allocated.
|
||||
// The number of live objects is Mallocs - Frees.
|
||||
Mallocs uint64
|
||||
|
||||
// Frees is the cumulative count of heap objects freed.
|
||||
Frees uint64
|
||||
|
||||
// Heap memory statistics.
|
||||
//
|
||||
// Interpreting the heap statistics requires some knowledge of
|
||||
// how Go organizes memory. Go divides the virtual address
|
||||
// space of the heap into "spans", which are contiguous
|
||||
// regions of memory 8K or larger. A span may be in one of
|
||||
// three states:
|
||||
//
|
||||
// An "idle" span contains no objects or other data. The
|
||||
// physical memory backing an idle span can be released back
|
||||
// to the OS (but the virtual address space never is), or it
|
||||
// can be converted into an "in use" or "stack" span.
|
||||
//
|
||||
// An "in use" span contains at least one heap object and may
|
||||
// have free space available to allocate more heap objects.
|
||||
//
|
||||
// A "stack" span is used for goroutine stacks. Stack spans
|
||||
// are not considered part of the heap. A span can change
|
||||
// between heap and stack memory; it is never used for both
|
||||
// simultaneously.
|
||||
|
||||
// HeapAlloc is bytes of allocated heap objects.
|
||||
//
|
||||
// "Allocated" heap objects include all reachable objects, as
|
||||
// well as unreachable objects that the garbage collector has
|
||||
// not yet freed. Specifically, HeapAlloc increases as heap
|
||||
// objects are allocated and decreases as the heap is swept
|
||||
// and unreachable objects are freed. Sweeping occurs
|
||||
// incrementally between GC cycles, so these two processes
|
||||
// occur simultaneously, and as a result HeapAlloc tends to
|
||||
// change smoothly (in contrast with the sawtooth that is
|
||||
// typical of stop-the-world garbage collectors).
|
||||
HeapAlloc uint64
|
||||
|
||||
// HeapSys is bytes of heap memory obtained from the OS.
|
||||
//
|
||||
// HeapSys measures the amount of virtual address space
|
||||
// reserved for the heap. This includes virtual address space
|
||||
// that has been reserved but not yet used, which consumes no
|
||||
// physical memory, but tends to be small, as well as virtual
|
||||
// address space for which the physical memory has been
|
||||
// returned to the OS after it became unused (see HeapReleased
|
||||
// for a measure of the latter).
|
||||
//
|
||||
// HeapSys estimates the largest size the heap has had.
|
||||
HeapSys uint64
|
||||
|
||||
// HeapIdle is bytes in idle (unused) spans.
|
||||
//
|
||||
// Idle spans have no objects in them. These spans could be
|
||||
// (and may already have been) returned to the OS, or they can
|
||||
// be reused for heap allocations, or they can be reused as
|
||||
// stack memory.
|
||||
//
|
||||
// HeapIdle minus HeapReleased estimates the amount of memory
|
||||
// that could be returned to the OS, but is being retained by
|
||||
// the runtime so it can grow the heap without requesting more
|
||||
// memory from the OS. If this difference is significantly
|
||||
// larger than the heap size, it indicates there was a recent
|
||||
// transient spike in live heap size.
|
||||
HeapIdle uint64
|
||||
|
||||
// HeapInuse is bytes in in-use spans.
|
||||
//
|
||||
// In-use spans have at least one object in them. These spans
|
||||
// can only be used for other objects of roughly the same
|
||||
// size.
|
||||
//
|
||||
// HeapInuse minus HeapAlloc estimates the amount of memory
|
||||
// that has been dedicated to particular size classes, but is
|
||||
// not currently being used. This is an upper bound on
|
||||
// fragmentation, but in general this memory can be reused
|
||||
// efficiently.
|
||||
HeapInuse uint64
|
||||
|
||||
// Stack memory statistics.
|
||||
//
|
||||
// Stacks are not considered part of the heap, but the runtime
|
||||
// can reuse a span of heap memory for stack memory, and
|
||||
// vice-versa.
|
||||
|
||||
// StackInuse is bytes in stack spans.
|
||||
//
|
||||
// In-use stack spans have at least one stack in them. These
|
||||
// spans can only be used for other stacks of the same size.
|
||||
//
|
||||
// There is no StackIdle because unused stack spans are
|
||||
// returned to the heap (and hence counted toward HeapIdle).
|
||||
StackInuse uint64
|
||||
|
||||
// StackSys is bytes of stack memory obtained from the OS.
|
||||
//
|
||||
// StackSys is StackInuse, plus any memory obtained directly
|
||||
// from the OS for OS thread stacks.
|
||||
//
|
||||
// In non-cgo programs this metric is currently equal to StackInuse
|
||||
// (but this should not be relied upon, and the value may change in
|
||||
// the future).
|
||||
//
|
||||
// In cgo programs this metric includes OS thread stacks allocated
|
||||
// directly from the OS. Currently, this only accounts for one stack in
|
||||
// c-shared and c-archive build modes and other sources of stacks from
|
||||
// the OS (notably, any allocated by C code) are not currently measured.
|
||||
// Note this too may change in the future.
|
||||
StackSys uint64
|
||||
|
||||
// GCSys is bytes of memory in garbage collection metadata.
|
||||
GCSys uint64
|
||||
}
|
||||
|
||||
func ReadGCStats() GCStats {
|
||||
var heapInuse, heapIdle uint64
|
||||
|
||||
lock(&gcMutex)
|
||||
|
||||
for block := uintptr(0); block < endBlock; block++ {
|
||||
bstate := gcStateOf(block)
|
||||
if bstate == blockStateFree {
|
||||
heapIdle += uint64(bytesPerBlock)
|
||||
} else {
|
||||
heapInuse += uint64(bytesPerBlock)
|
||||
}
|
||||
}
|
||||
|
||||
stackEnd := uintptr(unsafe.Pointer(&_stackEnd))
|
||||
stackSys := stackTop - stackEnd
|
||||
|
||||
stats := GCStats{
|
||||
Alloc: (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock),
|
||||
TotalAlloc: gcTotalAlloc,
|
||||
Sys: uint64(heapEnd - heapStart),
|
||||
Mallocs: gcMallocs,
|
||||
Frees: gcFrees,
|
||||
HeapAlloc: (gcTotalBlocks - gcFreedBlocks) * uint64(bytesPerBlock),
|
||||
HeapSys: heapInuse + heapIdle,
|
||||
HeapIdle: heapIdle,
|
||||
HeapInuse: heapInuse,
|
||||
StackInuse: uint64(stackTop - uintptr(getsp())),
|
||||
StackSys: uint64(stackSys),
|
||||
GCSys: uint64(heapEnd - uintptr(metadataStart)),
|
||||
}
|
||||
|
||||
unlock(&gcMutex)
|
||||
|
||||
return stats
|
||||
}
|
||||
54
runtime/internal/runtime/tinygogc/gc_link.go
Normal file
54
runtime/internal/runtime/tinygogc/gc_link.go
Normal file
@@ -0,0 +1,54 @@
|
||||
//go:build !testGC
|
||||
|
||||
package tinygogc
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
// LLGoPackage instructs the LLGo linker to wrap C standard library memory allocation
|
||||
// functions (malloc, realloc, calloc) so they use the tinygogc allocator instead.
|
||||
// This ensures all memory allocations go through the GC, including C library calls.
|
||||
const LLGoPackage = "link: --wrap=malloc --wrap=realloc --wrap=calloc"
|
||||
|
||||
//export __wrap_malloc
|
||||
func __wrap_malloc(size uintptr) unsafe.Pointer {
|
||||
return Alloc(size)
|
||||
}
|
||||
|
||||
//export __wrap_calloc
|
||||
func __wrap_calloc(nmemb, size uintptr) unsafe.Pointer {
|
||||
totalSize := nmemb * size
|
||||
// Check for multiplication overflow
|
||||
if nmemb != 0 && totalSize/nmemb != size {
|
||||
return nil // Overflow
|
||||
}
|
||||
return Alloc(totalSize)
|
||||
}
|
||||
|
||||
//export __wrap_realloc
|
||||
func __wrap_realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer {
|
||||
return Realloc(ptr, size)
|
||||
}
|
||||
|
||||
//go:linkname getsp llgo.stackSave
|
||||
func getsp() unsafe.Pointer
|
||||
|
||||
//go:linkname _heapStart _heapStart
|
||||
var _heapStart [0]byte
|
||||
|
||||
//go:linkname _heapEnd _heapEnd
|
||||
var _heapEnd [0]byte
|
||||
|
||||
//go:linkname _stackStart _stack_top
|
||||
var _stackStart [0]byte
|
||||
|
||||
//go:linkname _stackEnd _stack_end
|
||||
var _stackEnd [0]byte
|
||||
|
||||
//go:linkname _globals_start _globals_start
|
||||
var _globals_start [0]byte
|
||||
|
||||
//go:linkname _globals_end _globals_end
|
||||
var _globals_end [0]byte
|
||||
25
runtime/internal/runtime/tinygogc/gc_test.go
Normal file
25
runtime/internal/runtime/tinygogc/gc_test.go
Normal file
@@ -0,0 +1,25 @@
|
||||
//go:build testGC
|
||||
|
||||
package tinygogc
|
||||
|
||||
import (
|
||||
_ "unsafe"
|
||||
)
|
||||
|
||||
var currentStack uintptr
|
||||
|
||||
func getsp() uintptr {
|
||||
return currentStack
|
||||
}
|
||||
|
||||
var _heapStart [0]byte
|
||||
|
||||
var _heapEnd [0]byte
|
||||
|
||||
var _stackStart [0]byte
|
||||
|
||||
var _stackEnd [0]byte
|
||||
|
||||
var _globals_start [0]byte
|
||||
|
||||
var _globals_end [0]byte
|
||||
570
runtime/internal/runtime/tinygogc/gc_tinygo.go
Normal file
570
runtime/internal/runtime/tinygogc/gc_tinygo.go
Normal file
@@ -0,0 +1,570 @@
|
||||
//go:build baremetal || testGC
|
||||
|
||||
/*
|
||||
* Copyright (c) 2018-2025 The TinyGo Authors. All rights reserved.
|
||||
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* 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 tinygogc implements a conservative mark-and-sweep garbage collector
|
||||
// for baremetal environments where the standard Go runtime and bdwgc are unavailable.
|
||||
//
|
||||
// This implementation is based on TinyGo's GC and is designed for resource-constrained
|
||||
// embedded systems. It uses a block-based allocator with conservative pointer scanning.
|
||||
//
|
||||
// Build tags:
|
||||
// - baremetal: Enables this GC for baremetal targets
|
||||
// - testGC: Enables testing mode with mock implementations
|
||||
//
|
||||
// Memory Layout:
|
||||
// The heap is divided into fixed-size blocks (32 bytes on 64-bit). Metadata is stored
|
||||
// at the end of the heap, using 2 bits per block to track state (free/head/tail/mark).
|
||||
package tinygogc
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
c "github.com/goplus/llgo/runtime/internal/clite"
|
||||
)
|
||||
|
||||
const gcDebug = false
|
||||
|
||||
// blockState stores the four states in which a block can be. It is two bits in
|
||||
// size.
|
||||
const (
|
||||
blockStateFree uint8 = 0 // 00
|
||||
blockStateHead uint8 = 1 // 01
|
||||
blockStateTail uint8 = 2 // 10
|
||||
blockStateMark uint8 = 3 // 11
|
||||
blockStateMask uint8 = 3 // 11
|
||||
)
|
||||
|
||||
// The byte value of a block where every block is a 'tail' block.
|
||||
const blockStateByteAllTails = 0 |
|
||||
uint8(blockStateTail<<(stateBits*3)) |
|
||||
uint8(blockStateTail<<(stateBits*2)) |
|
||||
uint8(blockStateTail<<(stateBits*1)) |
|
||||
uint8(blockStateTail<<(stateBits*0))
|
||||
|
||||
var (
|
||||
heapStart uintptr // start address of heap area
|
||||
heapEnd uintptr // end address of heap area
|
||||
globalsStart uintptr // start address of global variable area
|
||||
globalsEnd uintptr // end address of global variable area
|
||||
stackTop uintptr // the top of stack
|
||||
endBlock uintptr // GC end block index
|
||||
metadataStart unsafe.Pointer // start address of GC metadata
|
||||
|
||||
nextAlloc uintptr // the next block that should be tried by the allocator
|
||||
gcTotalAlloc uint64 // total number of bytes allocated
|
||||
gcTotalBlocks uint64 // total number of allocated blocks
|
||||
gcMallocs uint64 // total number of allocations
|
||||
gcFrees uint64 // total number of objects freed
|
||||
gcFreedBlocks uint64 // total number of freed blocks
|
||||
|
||||
// stackOverflow is a flag which is set when the GC scans too deep while marking.
|
||||
// After it is set, all marked allocations must be re-scanned.
|
||||
markStackOverflow bool
|
||||
|
||||
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
|
||||
zeroSizedAlloc uint8
|
||||
|
||||
gcMutex mutex // gcMutex protects GC related variables
|
||||
isGCInit bool // isGCInit indicates GC initialization state
|
||||
)
|
||||
|
||||
// Some globals + constants for the entire GC.
|
||||
|
||||
const (
|
||||
wordsPerBlock = 4 // number of pointers in an allocated block
|
||||
bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart)
|
||||
stateBits = 2 // how many bits a block state takes (see blockState type)
|
||||
blocksPerStateByte = 8 / stateBits
|
||||
markStackSize = 8 * unsafe.Sizeof((*int)(nil)) // number of to-be-marked blocks to queue before forcing a rescan
|
||||
)
|
||||
|
||||
// this function MUST be initalized first, which means it's required to be initalized before runtime
|
||||
func initGC() {
|
||||
// reserve 2K blocks for libc internal malloc, we cannot wrap those internal functions
|
||||
heapStart = uintptr(unsafe.Pointer(&_heapStart)) + 2048
|
||||
heapEnd = uintptr(unsafe.Pointer(&_heapEnd))
|
||||
globalsStart = uintptr(unsafe.Pointer(&_globals_start))
|
||||
globalsEnd = uintptr(unsafe.Pointer(&_globals_end))
|
||||
totalSize := heapEnd - heapStart
|
||||
metadataSize := (totalSize + blocksPerStateByte*bytesPerBlock) / (1 + blocksPerStateByte*bytesPerBlock)
|
||||
metadataStart = unsafe.Pointer(heapEnd - metadataSize)
|
||||
endBlock = (uintptr(metadataStart) - heapStart) / bytesPerBlock
|
||||
stackTop = uintptr(unsafe.Pointer(&_stackStart))
|
||||
|
||||
c.Memset(metadataStart, 0, metadataSize)
|
||||
}
|
||||
|
||||
func lazyInit() {
|
||||
if !isGCInit {
|
||||
initGC()
|
||||
isGCInit = true
|
||||
}
|
||||
}
|
||||
|
||||
func gcPanic(s *c.Char) {
|
||||
c.Printf(c.Str("%s"), s)
|
||||
c.Exit(2)
|
||||
}
|
||||
|
||||
// blockFromAddr returns a block given an address somewhere in the heap (which
|
||||
// might not be heap-aligned).
|
||||
func blockFromAddr(addr uintptr) uintptr {
|
||||
if addr < heapStart || addr >= uintptr(metadataStart) {
|
||||
gcPanic(c.Str("gc: trying to get block from invalid address"))
|
||||
}
|
||||
return (addr - heapStart) / bytesPerBlock
|
||||
}
|
||||
|
||||
// Return a pointer to the start of the allocated object.
|
||||
func gcPointerOf(blockAddr uintptr) unsafe.Pointer {
|
||||
return unsafe.Pointer(gcAddressOf(blockAddr))
|
||||
}
|
||||
|
||||
// Return the address of the start of the allocated object.
|
||||
func gcAddressOf(blockAddr uintptr) uintptr {
|
||||
addr := heapStart + blockAddr*bytesPerBlock
|
||||
if addr > uintptr(metadataStart) {
|
||||
gcPanic(c.Str("gc: block pointing inside metadata"))
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// findHead returns the head (first block) of an object, assuming the block
|
||||
// points to an allocated object. It returns the same block if this block
|
||||
// already points to the head.
|
||||
func gcFindHead(blockAddr uintptr) uintptr {
|
||||
for {
|
||||
// Optimization: check whether the current block state byte (which
|
||||
// contains the state of multiple blocks) is composed entirely of tail
|
||||
// blocks. If so, we can skip back to the last block in the previous
|
||||
// state byte.
|
||||
// This optimization speeds up findHead for pointers that point into a
|
||||
// large allocation.
|
||||
stateByte := gcStateByteOf(blockAddr)
|
||||
if stateByte == blockStateByteAllTails {
|
||||
blockAddr -= (blockAddr % blocksPerStateByte) + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Check whether we've found a non-tail block, which means we found the
|
||||
// head.
|
||||
state := gcStateFromByte(blockAddr, stateByte)
|
||||
if state != blockStateTail {
|
||||
break
|
||||
}
|
||||
blockAddr--
|
||||
}
|
||||
if gcStateOf(blockAddr) != blockStateHead && gcStateOf(blockAddr) != blockStateMark {
|
||||
gcPanic(c.Str("gc: found tail without head"))
|
||||
}
|
||||
return blockAddr
|
||||
}
|
||||
|
||||
// findNext returns the first block just past the end of the tail. This may or
|
||||
// may not be the head of an object.
|
||||
func gcFindNext(blockAddr uintptr) uintptr {
|
||||
if gcStateOf(blockAddr) == blockStateHead || gcStateOf(blockAddr) == blockStateMark {
|
||||
blockAddr++
|
||||
}
|
||||
for gcAddressOf(blockAddr) < uintptr(metadataStart) && gcStateOf(blockAddr) == blockStateTail {
|
||||
blockAddr++
|
||||
}
|
||||
return blockAddr
|
||||
}
|
||||
|
||||
func gcStateByteOf(blockAddr uintptr) byte {
|
||||
return *(*uint8)(unsafe.Add(metadataStart, blockAddr/blocksPerStateByte))
|
||||
}
|
||||
|
||||
// Return the block state given a state byte. The state byte must have been
|
||||
// obtained using b.stateByte(), otherwise the result is incorrect.
|
||||
func gcStateFromByte(blockAddr uintptr, stateByte byte) uint8 {
|
||||
return uint8(stateByte>>((blockAddr%blocksPerStateByte)*stateBits)) & blockStateMask
|
||||
}
|
||||
|
||||
// State returns the current block state.
|
||||
func gcStateOf(blockAddr uintptr) uint8 {
|
||||
return gcStateFromByte(blockAddr, gcStateByteOf(blockAddr))
|
||||
}
|
||||
|
||||
// setState sets the current block to the given state, which must contain more
|
||||
// bits than the current state. Allowed transitions: from free to any state and
|
||||
// from head to mark.
|
||||
func gcSetState(blockAddr uintptr, newState uint8) {
|
||||
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, blockAddr/blocksPerStateByte))
|
||||
*stateBytePtr |= uint8(newState << ((blockAddr % blocksPerStateByte) * stateBits))
|
||||
if gcStateOf(blockAddr) != newState {
|
||||
gcPanic(c.Str("gc: setState() was not successful"))
|
||||
}
|
||||
}
|
||||
|
||||
// markFree sets the block state to free, no matter what state it was in before.
|
||||
func gcMarkFree(blockAddr uintptr) {
|
||||
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, blockAddr/blocksPerStateByte))
|
||||
*stateBytePtr &^= uint8(blockStateMask << ((blockAddr % blocksPerStateByte) * stateBits))
|
||||
if gcStateOf(blockAddr) != blockStateFree {
|
||||
gcPanic(c.Str("gc: markFree() was not successful"))
|
||||
}
|
||||
*(*[wordsPerBlock]uintptr)(unsafe.Pointer(gcAddressOf(blockAddr))) = [wordsPerBlock]uintptr{}
|
||||
}
|
||||
|
||||
// unmark changes the state of the block from mark to head. It must be marked
|
||||
// before calling this function.
|
||||
func gcUnmark(blockAddr uintptr) {
|
||||
if gcStateOf(blockAddr) != blockStateMark {
|
||||
gcPanic(c.Str("gc: unmark() on a block that is not marked"))
|
||||
}
|
||||
clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state
|
||||
stateBytePtr := (*uint8)(unsafe.Add(metadataStart, blockAddr/blocksPerStateByte))
|
||||
*stateBytePtr &^= uint8(clearMask << ((blockAddr % blocksPerStateByte) * stateBits))
|
||||
if gcStateOf(blockAddr) != blockStateHead {
|
||||
gcPanic(c.Str("gc: unmark() was not successful"))
|
||||
}
|
||||
}
|
||||
|
||||
func isOnHeap(ptr uintptr) bool {
|
||||
return ptr >= heapStart && ptr < uintptr(metadataStart)
|
||||
}
|
||||
|
||||
func isPointer(ptr uintptr) bool {
|
||||
// TODO: implement precise GC
|
||||
return isOnHeap(ptr)
|
||||
}
|
||||
|
||||
// alloc tries to find some free space on the heap, possibly doing a garbage
|
||||
// collection cycle if needed. If no space is free, it panics.
|
||||
//
|
||||
//go:noinline
|
||||
func Alloc(size uintptr) unsafe.Pointer {
|
||||
if size == 0 {
|
||||
return unsafe.Pointer(&zeroSizedAlloc)
|
||||
}
|
||||
lock(&gcMutex)
|
||||
lazyInit()
|
||||
|
||||
gcTotalAlloc += uint64(size)
|
||||
gcMallocs++
|
||||
|
||||
neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock
|
||||
gcTotalBlocks += uint64(neededBlocks)
|
||||
|
||||
// Continue looping until a run of free blocks has been found that fits the
|
||||
// requested size.
|
||||
index := nextAlloc
|
||||
numFreeBlocks := uintptr(0)
|
||||
heapScanCount := uint8(0)
|
||||
for {
|
||||
if index == nextAlloc {
|
||||
if heapScanCount == 0 {
|
||||
heapScanCount = 1
|
||||
} else if heapScanCount == 1 {
|
||||
// The entire heap has been searched for free memory, but none
|
||||
// could be found. Run a garbage collection cycle to reclaim
|
||||
// free memory and try again.
|
||||
heapScanCount = 2
|
||||
freeBytes := gc()
|
||||
heapSize := uintptr(metadataStart) - heapStart
|
||||
if freeBytes < heapSize/3 {
|
||||
// Ensure there is at least 33% headroom.
|
||||
// This percentage was arbitrarily chosen, and may need to
|
||||
// be tuned in the future.
|
||||
growHeap()
|
||||
}
|
||||
} else {
|
||||
// Even after garbage collection, no free memory could be found.
|
||||
// Try to increase heap size.
|
||||
if growHeap() {
|
||||
// Success, the heap was increased in size. Try again with a
|
||||
// larger heap.
|
||||
} else {
|
||||
// Unfortunately the heap could not be increased. This
|
||||
// happens on baremetal systems for example (where all
|
||||
// available RAM has already been dedicated to the heap).
|
||||
gcPanic(c.Str("out of memory"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap around the end of the heap.
|
||||
if index == endBlock {
|
||||
index = 0
|
||||
// Reset numFreeBlocks as allocations cannot wrap.
|
||||
numFreeBlocks = 0
|
||||
// In rare cases, the initial heap might be so small that there are
|
||||
// no blocks at all. In this case, it's better to jump back to the
|
||||
// start of the loop and try again, until the GC realizes there is
|
||||
// no memory and grows the heap.
|
||||
// This can sometimes happen on WebAssembly, where the initial heap
|
||||
// is created by whatever is left on the last memory page.
|
||||
continue
|
||||
}
|
||||
|
||||
// Is the block we're looking at free?
|
||||
if gcStateOf(index) != blockStateFree {
|
||||
// This block is in use. Try again from this point.
|
||||
numFreeBlocks = 0
|
||||
index++
|
||||
continue
|
||||
}
|
||||
numFreeBlocks++
|
||||
index++
|
||||
|
||||
// Are we finished?
|
||||
if numFreeBlocks == neededBlocks {
|
||||
// Found a big enough range of free blocks!
|
||||
nextAlloc = index
|
||||
thisAlloc := index - neededBlocks
|
||||
|
||||
// Set the following blocks as being allocated.
|
||||
gcSetState(thisAlloc, blockStateHead)
|
||||
for i := thisAlloc + 1; i != nextAlloc; i++ {
|
||||
gcSetState(i, blockStateTail)
|
||||
}
|
||||
unlock(&gcMutex)
|
||||
// Return a pointer to this allocation.
|
||||
return c.Memset(gcPointerOf(thisAlloc), 0, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer {
|
||||
if ptr == nil {
|
||||
return Alloc(size)
|
||||
}
|
||||
lock(&gcMutex)
|
||||
lazyInit()
|
||||
unlock(&gcMutex)
|
||||
|
||||
ptrAddress := uintptr(ptr)
|
||||
endOfTailAddress := gcAddressOf(gcFindNext(blockFromAddr(ptrAddress)))
|
||||
|
||||
// this might be a few bytes longer than the original size of
|
||||
// ptr, because we align to full blocks of size bytesPerBlock
|
||||
oldSize := endOfTailAddress - ptrAddress
|
||||
if size <= oldSize {
|
||||
return ptr
|
||||
}
|
||||
|
||||
newAlloc := Alloc(size)
|
||||
c.Memcpy(newAlloc, ptr, oldSize)
|
||||
free(ptr)
|
||||
|
||||
return newAlloc
|
||||
}
|
||||
|
||||
func free(ptr unsafe.Pointer) {
|
||||
// TODO: free blocks on request, when the compiler knows they're unused.
|
||||
}
|
||||
|
||||
func GC() uintptr {
|
||||
lock(&gcMutex)
|
||||
freeBytes := gc()
|
||||
unlock(&gcMutex)
|
||||
return freeBytes
|
||||
}
|
||||
|
||||
// runGC performs a garbage collection cycle. It is the internal implementation
|
||||
// of the runtime.GC() function. The difference is that it returns the number of
|
||||
// free bytes in the heap after the GC is finished.
|
||||
func gc() (freeBytes uintptr) {
|
||||
lazyInit()
|
||||
|
||||
if gcDebug {
|
||||
println("running collection cycle...")
|
||||
}
|
||||
|
||||
// Mark phase: mark all reachable objects, recursively.
|
||||
gcMarkReachable()
|
||||
|
||||
finishMark()
|
||||
|
||||
// If we're using threads, resume all other threads before starting the
|
||||
// sweep.
|
||||
gcResumeWorld()
|
||||
|
||||
// Sweep phase: free all non-marked objects and unmark marked objects for
|
||||
// the next collection cycle.
|
||||
freeBytes = sweep()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// markRoots reads all pointers from start to end (exclusive) and if they look
|
||||
// like a heap pointer and are unmarked, marks them and scans that object as
|
||||
// well (recursively). The start and end parameters must be valid pointers and
|
||||
// must be aligned.
|
||||
func markRoots(start, end uintptr) {
|
||||
if start >= end {
|
||||
gcPanic(c.Str("gc: unexpected range to mark"))
|
||||
}
|
||||
// Reduce the end bound to avoid reading too far on platforms where pointer alignment is smaller than pointer size.
|
||||
// If the size of the range is 0, then end will be slightly below start after this.
|
||||
end -= unsafe.Sizeof(end) - unsafe.Alignof(end)
|
||||
|
||||
for addr := start; addr < end; addr += unsafe.Alignof(addr) {
|
||||
root := *(*uintptr)(unsafe.Pointer(addr))
|
||||
markRoot(addr, root)
|
||||
}
|
||||
}
|
||||
|
||||
// startMark starts the marking process on a root and all of its children.
|
||||
func startMark(root uintptr) {
|
||||
var stack [markStackSize]uintptr
|
||||
stack[0] = root
|
||||
gcSetState(root, blockStateMark)
|
||||
stackLen := 1
|
||||
for stackLen > 0 {
|
||||
// Pop a block off of the stack.
|
||||
stackLen--
|
||||
block := stack[stackLen]
|
||||
|
||||
start, end := gcAddressOf(block), gcAddressOf(gcFindNext(block))
|
||||
|
||||
for addr := start; addr != end; addr += unsafe.Alignof(addr) {
|
||||
// Load the word.
|
||||
word := *(*uintptr)(unsafe.Pointer(addr))
|
||||
|
||||
if !isPointer(word) {
|
||||
// Not a heap pointer.
|
||||
continue
|
||||
}
|
||||
|
||||
// Find the corresponding memory block.
|
||||
referencedBlock := blockFromAddr(word)
|
||||
|
||||
if gcStateOf(referencedBlock) == blockStateFree {
|
||||
// The to-be-marked object doesn't actually exist.
|
||||
// This is probably a false positive.
|
||||
continue
|
||||
}
|
||||
|
||||
// Move to the block's head.
|
||||
referencedBlock = gcFindHead(referencedBlock)
|
||||
|
||||
if gcStateOf(referencedBlock) == blockStateMark {
|
||||
// The block has already been marked by something else.
|
||||
continue
|
||||
}
|
||||
|
||||
// Mark block.
|
||||
gcSetState(referencedBlock, blockStateMark)
|
||||
|
||||
if stackLen == len(stack) {
|
||||
// The stack is full.
|
||||
// It is necessary to rescan all marked blocks once we are done.
|
||||
markStackOverflow = true
|
||||
if gcDebug {
|
||||
println("gc stack overflowed")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Push the pointer onto the stack to be scanned later.
|
||||
stack[stackLen] = referencedBlock
|
||||
stackLen++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finishMark finishes the marking process by processing all stack overflows.
|
||||
func finishMark() {
|
||||
for markStackOverflow {
|
||||
// Re-mark all blocks.
|
||||
markStackOverflow = false
|
||||
for block := uintptr(0); block < endBlock; block++ {
|
||||
if gcStateOf(block) != blockStateMark {
|
||||
// Block is not marked, so we do not need to rescan it.
|
||||
continue
|
||||
}
|
||||
|
||||
// Re-mark the block.
|
||||
startMark(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mark a GC root at the address addr.
|
||||
func markRoot(addr, root uintptr) {
|
||||
if isOnHeap(root) {
|
||||
block := blockFromAddr(root)
|
||||
if gcStateOf(block) == blockStateFree {
|
||||
// The to-be-marked object doesn't actually exist.
|
||||
// This could either be a dangling pointer (oops!) but most likely
|
||||
// just a false positive.
|
||||
return
|
||||
}
|
||||
head := gcFindHead(block)
|
||||
|
||||
if gcStateOf(head) != blockStateMark {
|
||||
startMark(head)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sweep goes through all memory and frees unmarked
|
||||
// It returns how many bytes are free in the heap after the sweep.
|
||||
func sweep() (freeBytes uintptr) {
|
||||
freeCurrentObject := false
|
||||
var freed uint64
|
||||
|
||||
for block := uintptr(0); block < endBlock; block++ {
|
||||
switch gcStateOf(block) {
|
||||
case blockStateHead:
|
||||
// Unmarked head. Free it, including all tail blocks following it.
|
||||
gcMarkFree(block)
|
||||
freeCurrentObject = true
|
||||
gcFrees++
|
||||
freed++
|
||||
case blockStateTail:
|
||||
if freeCurrentObject {
|
||||
// This is a tail object following an unmarked head.
|
||||
// Free it now.
|
||||
gcMarkFree(block)
|
||||
freed++
|
||||
}
|
||||
case blockStateMark:
|
||||
// This is a marked object. The next tail blocks must not be freed,
|
||||
// but the mark bit must be removed so the next GC cycle will
|
||||
// collect this object if it is unreferenced then.
|
||||
gcUnmark(block)
|
||||
freeCurrentObject = false
|
||||
case blockStateFree:
|
||||
freeBytes += bytesPerBlock
|
||||
}
|
||||
}
|
||||
gcFreedBlocks += freed
|
||||
freeBytes += uintptr(freed) * bytesPerBlock
|
||||
return
|
||||
}
|
||||
|
||||
// growHeap tries to grow the heap size. It returns true if it succeeds, false
|
||||
// otherwise.
|
||||
func growHeap() bool {
|
||||
// On baremetal, there is no way the heap can be grown.
|
||||
return false
|
||||
}
|
||||
|
||||
func gcMarkReachable() {
|
||||
markRoots(uintptr(getsp()), stackTop)
|
||||
markRoots(globalsStart, globalsEnd)
|
||||
}
|
||||
|
||||
func gcResumeWorld() {
|
||||
// Nothing to do here (single threaded).
|
||||
}
|
||||
8
runtime/internal/runtime/tinygogc/mutex.go
Normal file
8
runtime/internal/runtime/tinygogc/mutex.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package tinygogc
|
||||
|
||||
// TODO(MeteorsLiu): mutex lock for baremetal GC
|
||||
type mutex struct{}
|
||||
|
||||
func lock(m *mutex) {}
|
||||
|
||||
func unlock(m *mutex) {}
|
||||
604
runtime/internal/runtime/tinygogc/pc_mock_test.go
Normal file
604
runtime/internal/runtime/tinygogc/pc_mock_test.go
Normal file
@@ -0,0 +1,604 @@
|
||||
//go:build testGC
|
||||
|
||||
package tinygogc
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
c "github.com/goplus/llgo/runtime/internal/clite"
|
||||
)
|
||||
|
||||
const (
|
||||
// Mock a typical embedded system with 128KB RAM
|
||||
mockHeapSize = 128 * 1024 // 128KB
|
||||
mockGlobalsSize = 4 * 1024 // 4KB for globals
|
||||
mockStackSize = 8 * 1024 // 8KB for stack
|
||||
mockReservedSize = 2048 // 2KB reserved as in real implementation
|
||||
)
|
||||
|
||||
type testObject struct {
|
||||
data [4]uintptr
|
||||
}
|
||||
|
||||
// mockGCEnv provides a controlled root environment for GC testing
|
||||
type mockGCEnv struct {
|
||||
memory []byte
|
||||
heapStart uintptr
|
||||
heapEnd uintptr
|
||||
globalsStart uintptr
|
||||
globalsEnd uintptr
|
||||
stackStart uintptr
|
||||
stackEnd uintptr
|
||||
// Controlled root sets for testing
|
||||
rootObjects []unsafe.Pointer
|
||||
// Original GC state to restore
|
||||
originalHeapStart uintptr
|
||||
originalHeapEnd uintptr
|
||||
originalGlobalsStart uintptr
|
||||
originalGlobalsEnd uintptr
|
||||
originalStackTop uintptr
|
||||
originalEndBlock uintptr
|
||||
originalMetadataStart unsafe.Pointer
|
||||
originalNextAlloc uintptr
|
||||
originalIsGCInit bool
|
||||
// Mock mode flag
|
||||
mockMode bool
|
||||
}
|
||||
|
||||
// createMockGCEnv creates a completely isolated GC environment
|
||||
func createMockGCEnv() *mockGCEnv {
|
||||
totalMemory := mockHeapSize + mockGlobalsSize + mockStackSize
|
||||
memory := make([]byte, totalMemory)
|
||||
baseAddr := uintptr(unsafe.Pointer(&memory[0]))
|
||||
|
||||
env := &mockGCEnv{
|
||||
memory: memory,
|
||||
globalsStart: baseAddr,
|
||||
globalsEnd: baseAddr + mockGlobalsSize,
|
||||
heapStart: baseAddr + mockGlobalsSize + mockReservedSize,
|
||||
heapEnd: baseAddr + mockGlobalsSize + mockHeapSize,
|
||||
stackStart: baseAddr + mockGlobalsSize + mockHeapSize,
|
||||
stackEnd: baseAddr + uintptr(totalMemory),
|
||||
rootObjects: make([]unsafe.Pointer, 0),
|
||||
mockMode: false,
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// setupMockGC initializes the GC with mock memory layout using initGC's logic
|
||||
func (env *mockGCEnv) setupMockGC() {
|
||||
// Save original GC state
|
||||
env.originalHeapStart = heapStart
|
||||
env.originalHeapEnd = heapEnd
|
||||
env.originalGlobalsStart = globalsStart
|
||||
env.originalGlobalsEnd = globalsEnd
|
||||
env.originalStackTop = stackTop
|
||||
env.originalEndBlock = endBlock
|
||||
env.originalMetadataStart = metadataStart
|
||||
env.originalNextAlloc = nextAlloc
|
||||
env.originalIsGCInit = isGCInit
|
||||
|
||||
// Set currentStack for getsp()
|
||||
currentStack = env.stackStart
|
||||
|
||||
// Apply initGC's logic with our mock memory layout
|
||||
// This is the same logic as initGC() but with our mock addresses
|
||||
heapStart = env.heapStart + 2048 // reserve 2K blocks like initGC does
|
||||
heapEnd = env.heapEnd
|
||||
globalsStart = env.globalsStart
|
||||
globalsEnd = env.globalsEnd
|
||||
stackTop = env.stackEnd
|
||||
|
||||
totalSize := heapEnd - heapStart
|
||||
metadataSize := (totalSize + blocksPerStateByte*bytesPerBlock) / (1 + blocksPerStateByte*bytesPerBlock)
|
||||
metadataStart = unsafe.Pointer(heapEnd - metadataSize)
|
||||
endBlock = (uintptr(metadataStart) - heapStart) / bytesPerBlock
|
||||
|
||||
// Clear metadata using memset like initGC does
|
||||
c.Memset(metadataStart, 0, metadataSize)
|
||||
|
||||
// Reset allocator state and all GC statistics for clean test environment
|
||||
nextAlloc = 0
|
||||
isGCInit = true
|
||||
|
||||
// Reset all GC statistics to start from clean state
|
||||
gcTotalAlloc = 0
|
||||
gcTotalBlocks = 0
|
||||
gcMallocs = 0
|
||||
gcFrees = 0
|
||||
gcFreedBlocks = 0
|
||||
markStackOverflow = false
|
||||
}
|
||||
|
||||
// restoreOriginalGC restores the original GC state
|
||||
func (env *mockGCEnv) restoreOriginalGC() {
|
||||
heapStart = env.originalHeapStart
|
||||
heapEnd = env.originalHeapEnd
|
||||
globalsStart = env.originalGlobalsStart
|
||||
globalsEnd = env.originalGlobalsEnd
|
||||
stackTop = env.originalStackTop
|
||||
endBlock = env.originalEndBlock
|
||||
metadataStart = env.originalMetadataStart
|
||||
nextAlloc = env.originalNextAlloc
|
||||
isGCInit = false
|
||||
}
|
||||
|
||||
// enableMockMode enables mock root scanning mode
|
||||
func (env *mockGCEnv) enableMockMode() {
|
||||
env.mockMode = true
|
||||
}
|
||||
|
||||
// disableMockMode disables mock root scanning mode
|
||||
func (env *mockGCEnv) disableMockMode() {
|
||||
env.mockMode = false
|
||||
}
|
||||
|
||||
// addRoot adds an object to the controlled root set
|
||||
func (env *mockGCEnv) addRoot(ptr unsafe.Pointer) {
|
||||
env.rootObjects = append(env.rootObjects, ptr)
|
||||
}
|
||||
|
||||
// clearRoots removes all objects from the controlled root set
|
||||
func (env *mockGCEnv) clearRoots() {
|
||||
env.rootObjects = env.rootObjects[:0]
|
||||
}
|
||||
|
||||
// mockMarkReachable replaces gcMarkReachable when in mock mode
|
||||
func (env *mockGCEnv) mockMarkReachable() {
|
||||
if !env.mockMode {
|
||||
// Use original logic
|
||||
markRoots(uintptr(getsp()), stackTop)
|
||||
markRoots(globalsStart, globalsEnd)
|
||||
return
|
||||
}
|
||||
|
||||
// Mock mode: only scan our controlled roots
|
||||
for _, root := range env.rootObjects {
|
||||
addr := uintptr(root)
|
||||
markRoot(addr, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// runMockGC runs standard GC but with controlled root scanning
|
||||
func (env *mockGCEnv) runMockGC() uintptr {
|
||||
lock(&gcMutex)
|
||||
defer unlock(&gcMutex)
|
||||
|
||||
lazyInit()
|
||||
|
||||
if gcDebug {
|
||||
println("running mock collection cycle...")
|
||||
}
|
||||
|
||||
// Mark phase: use our mock root scanning
|
||||
env.mockMarkReachable()
|
||||
finishMark()
|
||||
|
||||
// Resume world (no-op in single threaded)
|
||||
gcResumeWorld()
|
||||
|
||||
// Sweep phase: use standard sweep logic
|
||||
return sweep()
|
||||
}
|
||||
|
||||
// createTestObjects creates a network of objects for testing reachability
|
||||
func createTestObjects(env *mockGCEnv) []*testObject {
|
||||
// Allocate several test objects
|
||||
objects := make([]*testObject, 0, 10)
|
||||
|
||||
// Dependencies Graph
|
||||
// root1 -> child1 -> grandchild1 -> child2
|
||||
// root1 -> child2 -> grandchild1
|
||||
|
||||
// Create root objects (reachable from stack/globals)
|
||||
root1 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
root2 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
objects = append(objects, root1, root2)
|
||||
|
||||
// Create objects reachable from root1
|
||||
child1 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
child2 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
root1.data[0] = uintptr(unsafe.Pointer(child1))
|
||||
root1.data[1] = uintptr(unsafe.Pointer(child2))
|
||||
objects = append(objects, child1, child2)
|
||||
|
||||
// Create objects reachable from child1
|
||||
grandchild1 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
child1.data[0] = uintptr(unsafe.Pointer(grandchild1))
|
||||
objects = append(objects, grandchild1)
|
||||
|
||||
// Create circular reference between child2 and grandchild1
|
||||
child2.data[0] = uintptr(unsafe.Pointer(grandchild1))
|
||||
grandchild1.data[0] = uintptr(unsafe.Pointer(child2))
|
||||
|
||||
// Create unreachable objects (garbage)
|
||||
garbage1 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
garbage2 := (*testObject)(Alloc(unsafe.Sizeof(testObject{})))
|
||||
// Create circular reference in garbage
|
||||
garbage1.data[0] = uintptr(unsafe.Pointer(garbage2))
|
||||
garbage2.data[0] = uintptr(unsafe.Pointer(garbage1))
|
||||
objects = append(objects, garbage1, garbage2)
|
||||
|
||||
return objects
|
||||
}
|
||||
|
||||
func TestMockGCBasicAllocation(t *testing.T) {
|
||||
env := createMockGCEnv()
|
||||
env.setupMockGC()
|
||||
defer env.restoreOriginalGC()
|
||||
|
||||
// Test basic allocation
|
||||
ptr1 := Alloc(32)
|
||||
if ptr1 == nil {
|
||||
t.Fatal("Failed to allocate 32 bytes")
|
||||
}
|
||||
|
||||
ptr2 := Alloc(64)
|
||||
if ptr2 == nil {
|
||||
t.Fatal("Failed to allocate 64 bytes")
|
||||
}
|
||||
|
||||
// Verify pointers are within heap bounds
|
||||
addr1 := uintptr(ptr1)
|
||||
addr2 := uintptr(ptr2)
|
||||
|
||||
if addr1 < heapStart || addr1 >= uintptr(metadataStart) {
|
||||
t.Errorf("ptr1 %x not within heap bounds [%x, %x)", addr1, heapStart, uintptr(metadataStart))
|
||||
}
|
||||
|
||||
if addr2 < heapStart || addr2 >= uintptr(metadataStart) {
|
||||
t.Errorf("ptr2 %x not within heap bounds [%x, %x)", addr2, heapStart, uintptr(metadataStart))
|
||||
}
|
||||
|
||||
t.Logf("Allocated ptr1 at %x, ptr2 at %x", addr1, addr2)
|
||||
t.Logf("Heap bounds: [%x, %x)", heapStart, uintptr(metadataStart))
|
||||
}
|
||||
|
||||
func TestMockGCReachabilityAndSweep(t *testing.T) {
|
||||
env := createMockGCEnv()
|
||||
env.setupMockGC()
|
||||
defer env.restoreOriginalGC()
|
||||
|
||||
// Track initial stats
|
||||
initialMallocs := gcMallocs
|
||||
initialFrees := gcFrees
|
||||
|
||||
// Create test object network
|
||||
objects := createTestObjects(env)
|
||||
|
||||
// Add first 2 objects as roots using mock control
|
||||
env.enableMockMode()
|
||||
env.addRoot(unsafe.Pointer(objects[0])) // root1
|
||||
env.addRoot(unsafe.Pointer(objects[1])) // root2
|
||||
|
||||
t.Logf("Created %d objects, 2 are roots", len(objects))
|
||||
t.Logf("Mallocs: %d", gcMallocs-initialMallocs)
|
||||
|
||||
// Verify all objects are initially allocated
|
||||
for i, obj := range objects {
|
||||
addr := uintptr(unsafe.Pointer(obj))
|
||||
block := blockFromAddr(addr)
|
||||
state := gcStateOf(block)
|
||||
if state != blockStateHead {
|
||||
t.Errorf("Object %d at %x has state %d, expected %d (HEAD)", i, addr, state, blockStateHead)
|
||||
}
|
||||
}
|
||||
|
||||
// Perform GC with controlled root scanning
|
||||
freedBytes := env.runMockGC()
|
||||
t.Logf("Freed %d bytes during GC", freedBytes)
|
||||
t.Logf("Frees: %d (delta: %d)", gcFrees, gcFrees-initialFrees)
|
||||
|
||||
// Verify reachable objects are still allocated
|
||||
reachableObjects := []unsafe.Pointer{
|
||||
unsafe.Pointer(objects[0]), // root1
|
||||
unsafe.Pointer(objects[1]), // root2
|
||||
unsafe.Pointer(objects[2]), // child1 (reachable from root1)
|
||||
unsafe.Pointer(objects[3]), // child2 (reachable from root1)
|
||||
unsafe.Pointer(objects[4]), // grandchild1 (reachable from child1, child2)
|
||||
}
|
||||
|
||||
for i, obj := range reachableObjects {
|
||||
addr := uintptr(obj)
|
||||
block := blockFromAddr(addr)
|
||||
state := gcStateOf(block)
|
||||
if state != blockStateHead {
|
||||
t.Errorf("Reachable object %d at %x has state %d, expected %d (HEAD)", i, addr, state, blockStateHead)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify unreachable objects are freed
|
||||
unreachableObjects := []unsafe.Pointer{
|
||||
unsafe.Pointer(objects[5]), // garbage1
|
||||
unsafe.Pointer(objects[6]), // garbage2
|
||||
}
|
||||
|
||||
for i, obj := range unreachableObjects {
|
||||
addr := uintptr(obj)
|
||||
block := blockFromAddr(addr)
|
||||
state := gcStateOf(block)
|
||||
if state != blockStateFree {
|
||||
t.Errorf("Unreachable object %d at %x has state %d, expected %d (FREE)", i, addr, state, blockStateFree)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify some memory was actually freed
|
||||
if freedBytes == 0 {
|
||||
t.Error("Expected some memory to be freed, but freed 0 bytes")
|
||||
}
|
||||
|
||||
if gcFrees == initialFrees {
|
||||
t.Error("Expected some objects to be freed, but free count didn't change")
|
||||
}
|
||||
|
||||
// Clear refs to make grandchild1 unreachable
|
||||
objects[2].data[0] = 0 // child1 -> grandchild1
|
||||
objects[3].data[0] = 0 // child2 -> grandchild1
|
||||
|
||||
// Run GC again with same roots
|
||||
freedBytes = env.runMockGC()
|
||||
|
||||
// child2 should still be reachable (through root1)
|
||||
blockAddr := blockFromAddr(uintptr(unsafe.Pointer(objects[3])))
|
||||
state := gcStateOf(blockAddr)
|
||||
if state != blockStateHead {
|
||||
t.Errorf("Object child2 at %x has state %d, expected %d (HEAD)", blockAddr, state, blockStateHead)
|
||||
}
|
||||
|
||||
// grandchild1 should now be unreachable and freed
|
||||
blockAddr = blockFromAddr(uintptr(unsafe.Pointer(objects[4])))
|
||||
state = gcStateOf(blockAddr)
|
||||
if state != blockStateFree {
|
||||
t.Errorf("Object grandchild1 at %x has state %d, expected %d (FREE)", blockAddr, state, blockStateFree)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockGCMemoryPressure(t *testing.T) {
|
||||
env := createMockGCEnv()
|
||||
env.setupMockGC()
|
||||
defer env.restoreOriginalGC()
|
||||
|
||||
// Calculate available heap space
|
||||
heapSize := uintptr(metadataStart) - heapStart
|
||||
blockSize := bytesPerBlock
|
||||
maxBlocks := heapSize / blockSize
|
||||
|
||||
t.Logf("Heap size: %d bytes, Block size: %d bytes, Max blocks: %d",
|
||||
heapSize, blockSize, maxBlocks)
|
||||
|
||||
// Allocate until we trigger GC
|
||||
var allocations []unsafe.Pointer
|
||||
allocSize := uintptr(32) // Small allocations
|
||||
|
||||
// Allocate about 80% of heap to trigger GC pressure
|
||||
targetAllocations := int(maxBlocks * 4 / 5) // 80% capacity
|
||||
|
||||
for i := 0; i < targetAllocations; i++ {
|
||||
ptr := Alloc(allocSize)
|
||||
if ptr == nil {
|
||||
t.Fatalf("Failed to allocate at iteration %d", i)
|
||||
}
|
||||
allocations = append(allocations, ptr)
|
||||
}
|
||||
|
||||
initialMallocs := gcMallocs
|
||||
t.Logf("Allocated %d objects (%d mallocs total)", len(allocations), initialMallocs)
|
||||
|
||||
// Enable mock mode and keep only half the allocations as roots
|
||||
env.enableMockMode()
|
||||
keepCount := len(allocations) / 2
|
||||
for i := 0; i < keepCount; i++ {
|
||||
env.addRoot(allocations[i])
|
||||
}
|
||||
|
||||
t.Logf("Keeping %d objects as roots, %d should be freed", keepCount, len(allocations)-keepCount)
|
||||
|
||||
// Force GC with controlled roots
|
||||
freeBytes := env.runMockGC()
|
||||
|
||||
t.Logf("GC freed %d bytes", freeBytes)
|
||||
t.Logf("Objects freed: %d", gcFrees)
|
||||
|
||||
// Try to allocate more after GC
|
||||
for i := 0; i < 10; i++ {
|
||||
ptr := Alloc(allocSize)
|
||||
if ptr == nil {
|
||||
t.Fatalf("Failed to allocate after GC at iteration %d", i)
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("Successfully allocated more objects after GC")
|
||||
}
|
||||
|
||||
func TestMockGCStats(t *testing.T) {
|
||||
env := createMockGCEnv()
|
||||
env.setupMockGC()
|
||||
defer env.restoreOriginalGC()
|
||||
|
||||
// Get initial stats
|
||||
initialStats := ReadGCStats()
|
||||
t.Logf("Initial stats - Mallocs: %d, Frees: %d, TotalAlloc: %d, Alloc: %d",
|
||||
initialStats.Mallocs, initialStats.Frees, initialStats.TotalAlloc, initialStats.Alloc)
|
||||
|
||||
// Verify basic system stats
|
||||
expectedSys := uint64(env.heapEnd - env.heapStart - 2048)
|
||||
if initialStats.Sys != expectedSys {
|
||||
t.Errorf("Expected Sys %d, got %d", expectedSys, initialStats.Sys)
|
||||
}
|
||||
|
||||
expectedGCSys := uint64(env.heapEnd - uintptr(metadataStart))
|
||||
if initialStats.GCSys != expectedGCSys {
|
||||
t.Errorf("Expected GCSys %d, got %d", expectedGCSys, initialStats.GCSys)
|
||||
}
|
||||
|
||||
// Allocate some objects
|
||||
var allocations []unsafe.Pointer
|
||||
allocSize := uintptr(64)
|
||||
numAllocs := 10
|
||||
|
||||
for i := 0; i < numAllocs; i++ {
|
||||
ptr := Alloc(allocSize)
|
||||
if ptr == nil {
|
||||
t.Fatalf("Failed to allocate at iteration %d", i)
|
||||
}
|
||||
allocations = append(allocations, ptr)
|
||||
}
|
||||
|
||||
// Check stats after allocation
|
||||
afterAllocStats := ReadGCStats()
|
||||
t.Logf("After allocation - Mallocs: %d, Frees: %d, TotalAlloc: %d, Alloc: %d",
|
||||
afterAllocStats.Mallocs, afterAllocStats.Frees, afterAllocStats.TotalAlloc, afterAllocStats.Alloc)
|
||||
|
||||
// Verify allocation stats increased
|
||||
if afterAllocStats.Mallocs <= initialStats.Mallocs {
|
||||
t.Errorf("Expected Mallocs to increase from %d, got %d", initialStats.Mallocs, afterAllocStats.Mallocs)
|
||||
}
|
||||
|
||||
if afterAllocStats.TotalAlloc <= initialStats.TotalAlloc {
|
||||
t.Errorf("Expected TotalAlloc to increase from %d, got %d", initialStats.TotalAlloc, afterAllocStats.TotalAlloc)
|
||||
}
|
||||
|
||||
if afterAllocStats.Alloc <= initialStats.Alloc {
|
||||
t.Errorf("Expected Alloc to increase from %d, got %d", initialStats.Alloc, afterAllocStats.Alloc)
|
||||
}
|
||||
|
||||
// Verify Alloc and HeapAlloc are the same
|
||||
if afterAllocStats.Alloc != afterAllocStats.HeapAlloc {
|
||||
t.Errorf("Expected Alloc (%d) to equal HeapAlloc (%d)", afterAllocStats.Alloc, afterAllocStats.HeapAlloc)
|
||||
}
|
||||
|
||||
// Perform GC with controlled roots - keep only half the allocations
|
||||
env.enableMockMode()
|
||||
keepCount := len(allocations) / 2
|
||||
for i := 0; i < keepCount; i++ {
|
||||
env.addRoot(allocations[i])
|
||||
}
|
||||
|
||||
freedBytes := env.runMockGC()
|
||||
t.Logf("GC freed %d bytes", freedBytes)
|
||||
|
||||
// Check stats after GC
|
||||
afterGCStats := ReadGCStats()
|
||||
t.Logf("After GC - Mallocs: %d, Frees: %d, TotalAlloc: %d, Alloc: %d",
|
||||
afterGCStats.Mallocs, afterGCStats.Frees, afterGCStats.TotalAlloc, afterGCStats.Alloc)
|
||||
|
||||
// Verify GC stats
|
||||
if afterGCStats.Frees <= afterAllocStats.Frees {
|
||||
t.Errorf("Expected Frees to increase from %d, got %d", afterAllocStats.Frees, afterGCStats.Frees)
|
||||
}
|
||||
|
||||
// TotalAlloc should not decrease (cumulative)
|
||||
if afterGCStats.TotalAlloc != afterAllocStats.TotalAlloc {
|
||||
t.Errorf("Expected TotalAlloc to remain %d after GC, got %d", afterAllocStats.TotalAlloc, afterGCStats.TotalAlloc)
|
||||
}
|
||||
|
||||
// Alloc should decrease (freed objects)
|
||||
if afterGCStats.Alloc >= afterAllocStats.Alloc {
|
||||
t.Errorf("Expected Alloc to decrease from %d after GC, got %d", afterAllocStats.Alloc, afterGCStats.Alloc)
|
||||
}
|
||||
|
||||
// Verify heap statistics consistency
|
||||
if afterGCStats.HeapSys != afterGCStats.HeapInuse+afterGCStats.HeapIdle {
|
||||
t.Errorf("Expected HeapSys (%d) to equal HeapInuse (%d) + HeapIdle (%d)",
|
||||
afterGCStats.HeapSys, afterGCStats.HeapInuse, afterGCStats.HeapIdle)
|
||||
}
|
||||
|
||||
// Verify live objects calculation
|
||||
expectedLiveObjects := afterGCStats.Mallocs - afterGCStats.Frees
|
||||
t.Logf("Live objects: %d (Mallocs: %d - Frees: %d)", expectedLiveObjects, afterGCStats.Mallocs, afterGCStats.Frees)
|
||||
|
||||
// The number of live objects should be reasonable (we kept half the allocations plus some overhead)
|
||||
if expectedLiveObjects < uint64(keepCount) {
|
||||
t.Errorf("Expected at least %d live objects, got %d", keepCount, expectedLiveObjects)
|
||||
}
|
||||
|
||||
// Test stack statistics
|
||||
if afterGCStats.StackInuse > afterGCStats.StackSys {
|
||||
t.Errorf("StackInuse (%d) should not exceed StackSys (%d)", afterGCStats.StackInuse, afterGCStats.StackSys)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMockGCCircularReferences(t *testing.T) {
|
||||
env := createMockGCEnv()
|
||||
env.setupMockGC()
|
||||
defer env.restoreOriginalGC()
|
||||
|
||||
type Node struct {
|
||||
data [3]uintptr
|
||||
next uintptr
|
||||
}
|
||||
|
||||
// Create a circular linked list
|
||||
nodes := make([]*Node, 5)
|
||||
for i := range nodes {
|
||||
nodes[i] = (*Node)(Alloc(unsafe.Sizeof(Node{})))
|
||||
nodes[i].data[0] = uintptr(i) // Store index as data
|
||||
}
|
||||
|
||||
// Link them in a circle
|
||||
for i := range nodes {
|
||||
nextIdx := (i + 1) % len(nodes)
|
||||
nodes[i].next = uintptr(unsafe.Pointer(nodes[nextIdx]))
|
||||
}
|
||||
|
||||
t.Logf("Created circular list of %d nodes", len(nodes))
|
||||
|
||||
// Initially all should be allocated
|
||||
for i, node := range nodes {
|
||||
addr := uintptr(unsafe.Pointer(node))
|
||||
block := blockFromAddr(addr)
|
||||
state := gcStateOf(block)
|
||||
if state != blockStateHead {
|
||||
t.Errorf("Node %d at %x has state %d, expected %d", i, addr, state, blockStateHead)
|
||||
}
|
||||
}
|
||||
|
||||
// Test 1: With root references - objects should NOT be freed
|
||||
env.enableMockMode()
|
||||
// Add the first node as root (keeps entire circle reachable)
|
||||
env.addRoot(unsafe.Pointer(nodes[0]))
|
||||
|
||||
freeBytes := env.runMockGC()
|
||||
t.Logf("GC with root reference freed %d bytes", freeBytes)
|
||||
|
||||
// All nodes should still be allocated since they're reachable through the root
|
||||
for i, node := range nodes {
|
||||
addr := uintptr(unsafe.Pointer(node))
|
||||
block := blockFromAddr(addr)
|
||||
state := gcStateOf(block)
|
||||
if state != blockStateHead {
|
||||
t.Errorf("Node %d at %x should still be allocated, but has state %d", i, addr, state)
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: Without root references - all circular objects should be freed
|
||||
env.clearRoots() // Remove all root references
|
||||
|
||||
freeBytes = env.runMockGC()
|
||||
t.Logf("GC without roots freed %d bytes", freeBytes)
|
||||
|
||||
// All nodes should now be freed since they're not reachable from any roots
|
||||
expectedFreed := uintptr(len(nodes)) * ((unsafe.Sizeof(Node{}) + bytesPerBlock - 1) / bytesPerBlock) * bytesPerBlock
|
||||
|
||||
if freeBytes < expectedFreed {
|
||||
t.Errorf("Expected at least %d bytes freed, got %d", expectedFreed, freeBytes)
|
||||
}
|
||||
|
||||
// Verify all nodes are actually freed
|
||||
for i, node := range nodes {
|
||||
addr := uintptr(unsafe.Pointer(node))
|
||||
block := blockFromAddr(addr)
|
||||
state := gcStateOf(block)
|
||||
if state != blockStateFree {
|
||||
t.Errorf("Node %d at %x should be freed, but has state %d", i, addr, state)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we can allocate new objects in the freed space
|
||||
newPtr := Alloc(unsafe.Sizeof(Node{}))
|
||||
if newPtr == nil {
|
||||
t.Error("Failed to allocate after freeing circular references")
|
||||
}
|
||||
}
|
||||
33
runtime/internal/runtime/z_defer_gc.go
Normal file
33
runtime/internal/runtime/z_defer_gc.go
Normal file
@@ -0,0 +1,33 @@
|
||||
//go:build !nogc && !baremetal
|
||||
|
||||
/*
|
||||
* 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))
|
||||
}
|
||||
}
|
||||
32
runtime/internal/runtime/z_defer_nogc.go
Normal file
32
runtime/internal/runtime/z_defer_nogc.go
Normal file
@@ -0,0 +1,32 @@
|
||||
//go:build nogc || baremetal
|
||||
|
||||
/*
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
//go:build !nogc
|
||||
// +build !nogc
|
||||
//go:build !nogc && !baremetal
|
||||
|
||||
/*
|
||||
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
|
||||
|
||||
35
runtime/internal/runtime/z_gc_baremetal.go
Normal file
35
runtime/internal/runtime/z_gc_baremetal.go
Normal file
@@ -0,0 +1,35 @@
|
||||
//go:build !nogc && 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 (
|
||||
"unsafe"
|
||||
|
||||
"github.com/goplus/llgo/runtime/internal/runtime/tinygogc"
|
||||
)
|
||||
|
||||
// AllocU allocates uninitialized memory.
|
||||
func AllocU(size uintptr) unsafe.Pointer {
|
||||
return tinygogc.Alloc(size)
|
||||
}
|
||||
|
||||
// AllocZ allocates zero-initialized memory.
|
||||
func AllocZ(size uintptr) unsafe.Pointer {
|
||||
return tinygogc.Alloc(size)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
48
runtime/internal/runtime/z_signal.go
Normal file
48
runtime/internal/runtime/z_signal.go
Normal 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)))))
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
12
ssa/di.go
12
ssa/di.go
@@ -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)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
128
ssa/eh.go
128
ssa/eh.go
@@ -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
|
||||
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)
|
||||
offset++
|
||||
}
|
||||
for i := 0; i < len(args); i++ {
|
||||
args[i] = b.getField(data, i+offset)
|
||||
}
|
||||
b.Call(fn, args...)
|
||||
b.free(ptr)
|
||||
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()})
|
||||
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(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
53
ssa/eh_loop_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
__stack = ORIGIN(dram_seg) + LENGTH(dram_seg);
|
||||
__MIN_STACK_SIZE = 0x1000;
|
||||
_stack_top = __stack;
|
||||
_heapEnd = ORIGIN(dram_seg) + LENGTH(dram_seg);
|
||||
|
||||
/* Default entry point */
|
||||
ENTRY(_start)
|
||||
@@ -94,6 +92,12 @@ SECTIONS
|
||||
_iram_end = .;
|
||||
} > iram_seg
|
||||
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
. += 16K;
|
||||
__stack = .;
|
||||
} > dram_seg
|
||||
|
||||
/**
|
||||
* This section is required to skip .iram0.text area because iram0_0_seg and
|
||||
* dram0_0_seg reflect the same address space on different buses.
|
||||
@@ -148,7 +152,7 @@ SECTIONS
|
||||
} > dram_seg
|
||||
|
||||
/* Check if data + heap + stack exceeds RAM limit */
|
||||
ASSERT(_end <= __stack - __MIN_STACK_SIZE, "region DRAM overflowed by .data and .bss sections")
|
||||
ASSERT(_end <= _heapEnd, "region DRAM overflowed by .data and .bss sections")
|
||||
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
@@ -193,3 +197,8 @@ SECTIONS
|
||||
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
|
||||
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
|
||||
}
|
||||
|
||||
_globals_start = _data_start;
|
||||
_globals_end = _end;
|
||||
_heapStart = _end;
|
||||
_stack_top = __stack;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
__stack = ORIGIN(dram_seg) + LENGTH(dram_seg);
|
||||
__MIN_STACK_SIZE = 0x2000;
|
||||
_heapEnd = ORIGIN(dram_seg) + LENGTH(dram_seg);
|
||||
|
||||
ENTRY(_start)
|
||||
SECTIONS
|
||||
@@ -26,6 +25,14 @@ SECTIONS
|
||||
the same address within the page on the next page up. */
|
||||
. = ALIGN (CONSTANT (MAXPAGESIZE)) - ((CONSTANT (MAXPAGESIZE) - .) & (CONSTANT (MAXPAGESIZE) - 1)); . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE));
|
||||
|
||||
.stack (NOLOAD) :
|
||||
{
|
||||
_stack_end = .;
|
||||
. = ALIGN(16);
|
||||
. += 16K;
|
||||
__stack = .;
|
||||
}
|
||||
|
||||
|
||||
.rodata :
|
||||
{
|
||||
@@ -116,7 +123,7 @@ SECTIONS
|
||||
. = DATA_SEGMENT_END (.);
|
||||
|
||||
/* Check if data + heap + stack exceeds RAM limit */
|
||||
ASSERT(. <= __stack - __MIN_STACK_SIZE, "region DRAM overflowed by .data and .bss sections")
|
||||
ASSERT(. <= _heapEnd, "region DRAM overflowed by .data and .bss sections")
|
||||
|
||||
/* Stabs debugging sections. */
|
||||
.stab 0 : { *(.stab) }
|
||||
@@ -165,4 +172,7 @@ SECTIONS
|
||||
|
||||
_sbss = __bss_start;
|
||||
_ebss = _end;
|
||||
|
||||
_globals_start = _data_start;
|
||||
_globals_end = _end;
|
||||
_heapStart = _end;
|
||||
_stack_top = __stack;
|
||||
|
||||
@@ -19,8 +19,8 @@ MEMORY
|
||||
/* 64k at the end of DRAM, after ROM bootloader stack
|
||||
* or entire DRAM (for QEMU only)
|
||||
*/
|
||||
dram_seg (RW) : org = 0x3FFF0000 ,
|
||||
len = 0x10000
|
||||
dram_seg (RW) : org = 0x3ffae000 ,
|
||||
len = 0x52000
|
||||
}
|
||||
|
||||
INCLUDE "targets/esp32.app.elf.ld";
|
||||
|
||||
326
test/defer_test.go
Normal file
326
test/defer_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user