Compare commits

...

68 Commits

Author SHA1 Message Date
xgopilot
04ef1b0767 build: remove incorrect TODO comment
Remove TODO(lijie) comment as requested - the syscall.init weak stub is intentional.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-16 09:16:36 +00:00
xgopilot
9b71b3f2dc build: apply review feedback on main module generation
- Remove sizeTypeForArch, use prog.Uintptr() directly
- Replace charPtrType with prog.CStr()
- Add back TODO(lijie) comment for syscall patch workaround

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-16 05:28:10 +00:00
xgopilot
9926c4ed6a build: apply review feedback on main module generation
- Change pkg.PkgPath to pkg.ID for better debugging support
- Add comment explaining syscall.init weak stub declaration
- Simplify sizeTypeForArch to use prog.Uintptr() directly
- Remove unused runtime import

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-16 04:35:41 +00:00
xgopilot
7b7d7f9cdb build: remove error return from genMainModule
The genMainModule function never returns an error, so simplified
its signature to return only Package.

Updated:
- genMainModule signature: (Package, error) -> Package
- Call site in build.go to not handle error
- Both test cases to remove error checking

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-14 22:03:36 +00:00
xgopilot
131fe2c504 fix: separate stdout and stderr null checks in emitStdioNobuf
The original code incorrectly used the stdout null check condition
for both stdout and stderr pointer selection. This caused incorrect
behavior when stderr is null but stdout is not, or vice-versa.

This fix separates the null checks for stdout and stderr into
independent conditions, ensuring each stream is properly selected
based on its own null status.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: cpunion <8459+cpunion@users.noreply.github.com>
2025-11-14 15:57:00 +00:00
Li Jie
33a53b6e64 build: refactor main module generation 2025-11-14 23:39:56 +08:00
xushiwei
2f65c98eb4 Merge pull request #1348 from cpunion/feature/defer-loop
defer: enable loop lowering
2025-11-13 17:59:26 +08:00
Li Jie
aeb5d82d3e ssa: remove unreachable switch case 2025-11-13 16:15:36 +08:00
Li Jie
317de80b42 test: add nested defer/loop/branch/recover tests and fix mixed branch recover mapping 2025-11-13 16:15:33 +08:00
Li Jie
0455ad4443 ssa: Add test for >64 conditional defers; cover panic at eh.go:252-254 2025-11-13 15:48:50 +08:00
Li Jie
e459ca928b runtime: remove overlays for defer workaround 2025-11-13 15:48:50 +08:00
Li Jie
21fef123d2 di: clean unreachable code 2025-11-13 15:48:50 +08:00
Li Jie
983a189c18 test: cover defer recover and nested loops 2025-11-13 15:48:49 +08:00
Li Jie
cb173f91d0 ssa: rely on runtime thread defer TLS 2025-11-13 15:48:49 +08:00
Li Jie
30bde9f6b5 tls: stub handle for go test 2025-11-13 15:48:49 +08:00
Li Jie
3307860d33 ssa: avoid redundant thread defer update 2025-11-13 15:48:49 +08:00
Li Jie
16709411a0 defer: enable loop lowering 2025-11-13 15:48:46 +08:00
xushiwei
3f8c95cf87 Merge pull request #1334 from goplus/dependabot/go_modules/github.com/goplus/gogen-1.19.5
chore(deps): bump github.com/goplus/gogen from 1.19.3 to 1.19.5
2025-11-13 07:49:29 +08:00
xushiwei
0dbe528e6d Merge pull request #1304 from MeteorsLiu/impl-stacksave
feat: implement `llgo.stackSave`
2025-11-13 07:48:01 +08:00
dependabot[bot]
91f6ad9bfa chore(deps): bump github.com/goplus/gogen from 1.19.3 to 1.19.5
Bumps [github.com/goplus/gogen](https://github.com/goplus/gogen) from 1.19.3 to 1.19.5.
- [Release notes](https://github.com/goplus/gogen/releases)
- [Commits](https://github.com/goplus/gogen/compare/v1.19.3...v1.19.5)

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

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

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

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

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

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

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

Fixes #1385

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

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

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

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

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

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <51194195+luoliwoshang@users.noreply.github.com>
2025-10-31 02:43:06 +00:00
Haolan
bf6f785988 Merge branch 'main' of https://github.com/goplus/llgo into impl-stacksave 2025-10-30 13:48:13 +08:00
xushiwei
86cafff113 Merge pull request #1375 from goplus/dependabot/github_actions/actions/download-artifact-6
chore(deps): bump actions/download-artifact from 5 to 6
2025-10-27 16:37:07 +08:00
xushiwei
3c88949557 Merge pull request #1376 from goplus/dependabot/github_actions/actions/upload-artifact-5
chore(deps): bump actions/upload-artifact from 4 to 5
2025-10-27 16:36:57 +08:00
dependabot[bot]
533ba9ebd8 chore(deps): bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 00:17:07 +00:00
dependabot[bot]
f34062166b chore(deps): bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 00:17:03 +00:00
xushiwei
c036243b3f Merge pull request #1352 from goplus/xgopilot/claude/issue-1351-1760682793
Remove redundant CompareString from llgo:skip directive
2025-10-27 07:18:05 +08:00
xushiwei
e16fc69ce3 Merge pull request #1349 from goplus/xgopilot/claude/issue-1346-1760499310
fix: set build.Default.Compiler to gc in init function
2025-10-27 07:16:15 +08:00
xushiwei
a2c81327ea Merge pull request #1355 from goplus/feature/reflect-indirect
feat(reflect): add Indirect function
2025-10-27 07:13:54 +08:00
xgopilot
9b397725da Remove redundant CompareString from llgo:skip directive
The CompareString in the llgo:skip directive is redundant because
the overlay mechanism automatically handles symbol skipping when
an overlay package defines a symbol.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-17 06:36:46 +00:00
xgopilot
420ad8e010 refactor: simplify go/build patch - use build.Default directly
Remove unnecessary go:linkname, runtime import, and unsafe import.
Access build.Default.Compiler directly instead of via linkname.

- Removed go:linkname directive
- Removed runtime and unsafe imports
- Removed unnecessary _ = runtime.Compiler line
- Simplified from 27 lines to 19 lines
- Same functionality, cleaner code

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 13:28:54 +00:00
xgopilot
6e41cc702f refactor: simplify go/build patch using init function
Use go:linkname to access build.Default and modify Compiler in init function.
This is much simpler than patching defaultContext() and all its dependencies.

- Reduced from 137 lines to 27 lines
- Removed zcgo.go (no longer needed)
- Removed complex parseGoVersion, defaultContext, and helper functions
- Now just sets build.Default.Compiler = "gc" in init()

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 13:09:32 +00:00
xgopilot
0a94a54772 test: enhance demo to test multiple go/build public functions
Enhanced the demo test to cover comprehensive import scenarios:
- Test 1: build.Default context with GOOS/GOARCH validation
- Test 2: Standard library package import (fmt)
- Test 3: Nested standard library package (os/exec)
- Test 4: Internal package import (internal/cpu)
- Test 5: Runtime package with Dir validation
- Test 6: ImportDir with current directory
- Test 7: IsLocalImport with multiple path formats
- Test 8: Context GOPATH/GOROOT validation
- Test 9: Import with AllowBinary flag
- Test 10: Release tags validation

All tests include proper result validation to ensure go/build
patches work correctly across different use cases.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 11:55:02 +00:00
xgopilot
d09ce613c8 fix: remove trailing newline in build.go
Fixed formatting issue that was causing CI to fail.
The file had an extra blank line at the end which go fmt removed.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 11:36:49 +00:00
xgopilot
e614edfab4 test: enhance demo to test multiple go/build public functions
Enhanced the demo test to cover more go/build functionality:
- build.Import (existing)
- build.ImportDir (new)
- build.IsLocalImport (new)
- build.Default context verification (existing, improved)

This ensures the go/build patches work correctly across different
public functions, not just Import.

Also ran go fmt to fix formatting issues reported by CI.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 11:22:19 +00:00
xgopilot
29504f2560 refactor: address final review comments
- Created zcgo.go with defaultCGO_ENABLED constant (Go 1.24 pattern)
- Updated parseGoVersion to panic on unexpected input instead of fallback
- Added runtime.(*Func).Name stub to symtab.go for Go 1.21/1.22 compatibility
- Removed go1.23 build constraint from demo
- Removed internal/buildcfg package (replaced by zcgo.go)

These changes ensure the demo works across Go 1.21+ versions.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 11:07:02 +00:00
xgopilot
ee49fad4a4 refactor: follow Go stdlib conventions for build.go
- Created internal/buildcfg package with DefaultCGO_ENABLED constant
- Updated CGO_ENABLED handling to use buildcfg.DefaultCGO_ENABLED
- Added original Go stdlib comment for release tags
- Simplified toolTags usage by using it directly instead of wrapper function

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 10:39:14 +00:00
xgopilot
946a4bf990 refactor: link internal/buildcfg.ToolTags and enhance demo test coverage
- Link internal/buildcfg.ToolTags variable instead of hardcoding tool tags
- Add comprehensive test cases in demo.go to verify:
  - build.Default.Compiler is correctly patched to "gc"
  - ToolTags are properly populated from internal/buildcfg
  - ReleaseTags are correctly generated
- Validates the go/build patches work as expected

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 08:27:39 +00:00
xgopilot
8b61831b0d refactor: address review comments on build.go
- Remove /llgo from .gitignore (unnecessary)
- Move cgoSupported linkname to top with other linknames
- Fix goVersion initialization timing by making it local variable

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 07:48:28 +00:00
xgopilot
e96625cf07 feat: add go:linkname directives for defaultToolTags and defaultReleaseTags
Add linkname directives and stdlib comments for defaultToolTags and
defaultReleaseTags variables to match Go standard library pattern. This
allows external packages (like gopherjs) to access these internal variables
via linkname, maintaining compatibility with packages that depend on this
behavior.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 07:37:13 +00:00
xgopilot
8ac7ada7f9 refactor: use go:linkname to link internal/platform.CgoSupported
Instead of creating a custom platform package, use the go:linkname
directive to directly link to Go's standard library internal/platform.CgoSupported
function. This approach:

- Eliminates the need to maintain a copy of platform support data
- Uses Go's canonical platform information directly
- Reduces code duplication and maintenance burden
- Follows Go's linkname pattern for accessing internal packages

Changes:
- Added import _ "unsafe" to enable linkname
- Added //go:linkname directive for cgoSupported function
- Removed custom runtime/internal/lib/internal/platform package
- Updated function call from platform.CgoSupported to cgoSupported

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 06:05:54 +00:00
xgopilot
0b00e06185 refactor: update platform package to match original Go structure
Updated runtime/internal/lib/internal/platform/platform.go to match
the original Go internal/platform package structure:

- Added OSArch struct with GOOS and GOARCH fields
- Added osArchInfo struct with CgoSupported, FirstClass, and Broken fields
- Created distInfo map using OSArch as keys
- Updated CgoSupported function to use map lookup (matches original implementation)

This change makes the package structure more consistent with Go's
standard library and follows the established pattern more closely.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 05:53:20 +00:00
xgopilot
c4fdb1edc0 refactor: move cgoSupported to internal/platform package
Created runtime/internal/lib/internal/platform package to house the
CgoSupported function, following the established pattern used by other
internal packages like internal/itoa.

Changes:
- Created runtime/internal/lib/internal/platform/platform.go
- Moved cgoSupported function to platform.CgoSupported
- Updated runtime/internal/lib/go/build/build.go to import and use
  the new package

This refactoring improves code organization by separating
platform-specific utilities into their own package, making the code
more maintainable and reusable.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 04:05:58 +00:00
xgopilot
224e3b9440 fix: add Go 1.23+ build constraint to gobuild demo
Add //go:build go1.23 constraint to skip compilation on Go 1.21/1.22
where runtime.(*Func).Name is not implemented in llgo, causing linker
errors when internal/bisect is pulled in as a dependency.

The demo works correctly on Go 1.23+ where the dependency chain or
internal/bisect behavior avoids calling the unimplemented method.

Fixes compatibility issue reported in PR review.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 03:14:32 +00:00
xgopilot
7fbcc8cd10 refactor: use runtime.Version() instead of hardcoded goVersion
Replaced hardcoded goVersion constant with parseGoVersion() function
that dynamically extracts the Go version from runtime.Version().
This eliminates the need to update the version manually.

🤖 Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-16 02:33:53 +00:00
xgopilot
938f883be9 refactor: use hasAltPkg with minimal implementation (94% reduction)
Learned from other AltPkg implementations to create a truly minimal patch:
- Moved from 2073-line overlay to 127-line hasAltPkg implementation
- Replaced internal package dependencies:
  * internal/buildcfg → runtime.GOARCH/GOOS
  * internal/goversion → hardcoded constant (24)
  * internal/platform.CgoSupported → simplified implementation
  * internal/buildcfg.ToolTags → simplified buildToolTags()
- Used type alias (Context = build.Context) to reference stdlib types
- Added go/build to hasAltPkg map
- Removed overlay entirely

This follows the pattern used by other AltPkg packages and achieves
the minimal patching approach requested.

Demo verified working with llgo.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-15 14:54:29 +00:00
xgopilot
d145967c35 revert: restore full overlay approach for go/build
After investigating the hasAltPkg mechanism, determined that it's not
suitable for go/build.defaultContext() because:

1. hasAltPkg works well for providing additional/alternative functions
2. But defaultContext() needs to REPLACE an existing function that depends
   on internal/buildcfg, internal/goversion, and internal/platform
3. These internal packages cannot be imported from runtime/internal/lib

The full overlay approach (2073 lines) works correctly. Seeking guidance
on whether this is acceptable or if there's an alternative approach.

Demo verified working:
- runtime.Compiler = "llgo"
- go/build.Import() works correctly
- No "unknown compiler" error

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-15 13:11:22 +00:00
xgopilot
2e0fc5fb7f refactor: reduce go/build overlay to minimal 87-line patch
Reduced the go/build overlay from 2073 lines to just 87 lines by only
including the patched defaultContext() function and its direct dependencies
(envOr, defaultGOPATH, and related variables).

The overlay system works by merging with the standard library, so we only
need to provide the functions we're modifying. Unpatched functions
automatically fall back to the Go standard library implementation.

This makes the patch much more maintainable and clearly shows what's
being modified: just the Compiler field assignment in defaultContext().

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-15 12:33:48 +00:00
xgopilot
d8cf93a6cd refactor: move go/build overlay to runtime/internal for minimal patching
- Moved full overlay file from runtime/_overlay/go/build/build.go to
  runtime/internal/go/build/build.go to make it clearer what's being overridden
- Updated runtime/overlay.go to embed from new location
- Created demo under _demo/go/gobuild to demonstrate go/build package working with llgo
- The overlay still contains the full file but is now organized under runtime/internal
  following the project's pattern for patched standard library packages

Fixes #1346

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-15 12:17:38 +00:00
xgopilot
01ada11b74 fix: override go/build.defaultContext() to use gc compiler
Fixes #1346 by creating an overlay for go/build/build.go that sets
Compiler to "gc" in defaultContext(). This allows user code using
go/build package to work with llgo while preserving runtime.Compiler
as "llgo" for identification purposes.

The overlay replaces the entire go/build/build.go file with a modified
version where line 342 changes from 'c.Compiler = runtime.Compiler' to
'c.Compiler = "gc"'. This minimal change ensures go/build recognizes
llgo-compiled binaries without requiring changes to user code.

Generated with [codeagent](https://github.com/qbox/codeagent)
Co-authored-by: luoliwoshang <luoliwoshang@users.noreply.github.com>
2025-10-15 11:58:36 +00:00
Haolan
c3dbc580aa chore: update stacksave signature commets 2025-09-28 09:20:50 +08:00
Haolan
93e660a2b0 fix: amd64 getsp demo 2025-09-28 09:20:50 +08:00
Haolan
2ec5653f5e feat: implement llgo.stackSave 2025-09-28 09:20:50 +08:00
56 changed files with 2738 additions and 5848 deletions

View File

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

View File

@@ -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
@@ -52,7 +52,7 @@ jobs:
with:
llvm-version: ${{matrix.llvm}}
- name: Download model artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v6
with:
name: llama2-model
path: ./_demo/c/llama2-c/

View File

@@ -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
@@ -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: .

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
;

View File

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

View File

@@ -0,0 +1 @@
;

View File

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

View File

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

View File

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

View File

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

View File

@@ -98,18 +98,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
@@ -263,6 +264,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 +273,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 +285,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 +379,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 +399,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 +794,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)
@@ -846,7 +857,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 +866,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)
}

View File

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

View File

@@ -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

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

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

4
go.mod
View File

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

8
go.sum
View File

@@ -6,10 +6,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/goplus/cobra v1.9.12 h1:0F9EdEbeGyITGz+mqoHoJ5KpUw97p1CkxV74IexHw5s=
github.com/goplus/cobra v1.9.12/go.mod h1:p4LhfNJDKEpiGjGiNn0crUXL5dUPA5DX2ztYpEJR34E=
github.com/goplus/gogen v1.19.3 h1:sMTe7xME8lWFdPL6NcULykdJtFs9CtXkNACRbaAKTiQ=
github.com/goplus/gogen v1.19.3/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/lib v0.3.0 h1:y0ZGb5Q/RikW1oMMB4Di7XIZIpuzh/7mlrR8HNbxXCA=
github.com/goplus/lib v0.3.0/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/gogen v1.19.5 h1:YWPwpRA1PusPhptv9jKg/XiN+AQGiAD9r6I86mJ3lR4=
github.com/goplus/gogen v1.19.5/go.mod h1:owX2e1EyU5WD+Nm6oH2m/GXjLdlBYcwkLO4wN8HHXZI=
github.com/goplus/lib v0.3.1 h1:Xws4DBVvgOMu58awqB972wtvTacDbk3nqcbHjdx9KSg=
github.com/goplus/lib v0.3.1/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0=
github.com/goplus/llvm v0.8.5 h1:DUnFeYC3Rco622tBEKGg8xkigRAV2fh5ZIfBCt7gOSs=
github.com/goplus/llvm v0.8.5/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4=
github.com/goplus/mod v0.17.1 h1:ITovxDcc5zbURV/Wrp3/SBsYLgC1KrxY6pq1zMM2V94=

View File

@@ -780,11 +780,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
@@ -914,118 +914,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"
}

View File

@@ -0,0 +1,213 @@
//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
import (
"go/token"
"go/types"
"github.com/goplus/llgo/internal/packages"
llvm "github.com/goplus/llvm"
llssa "github.com/goplus/llgo/ssa"
)
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")
// Define syscall.init as a weak stub to allow linking even when syscall package is not imported
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
}
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.Goarch)
}
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
}
func emitStdioNobuf(b llssa.Builder, pkg llssa.Package, goarch string) {
prog := pkg.Prog
streamType := prog.VoidPtr()
streamPtrType := prog.Pointer(streamType)
stdout := declareExternalPtrGlobal(pkg, "stdout", streamType)
stderr := declareExternalPtrGlobal(pkg, "stderr", streamType)
stdoutAlt := declareExternalPtrGlobal(pkg, "__stdout", streamType)
stderrAlt := declareExternalPtrGlobal(pkg, "__stderr", streamType)
sizeType := prog.Uintptr()
setvbuf := declareSetvbuf(pkg, streamPtrType, prog.CStr(), prog.Int32(), sizeType)
stdoutSlot := b.AllocaT(streamPtrType)
b.Store(stdoutSlot, stdout)
condOut := b.BinOp(token.EQL, stdout, prog.Nil(streamPtrType))
b.IfThen(condOut, func() {
b.Store(stdoutSlot, stdoutAlt)
})
stdoutPtr := b.Load(stdoutSlot)
stderrSlot := b.AllocaT(streamPtrType)
b.Store(stderrSlot, stderr)
condErr := b.BinOp(token.EQL, stderr, prog.Nil(streamPtrType))
b.IfThen(condErr, func() {
b.Store(stderrSlot, stderrAlt)
})
stderrPtr := b.Load(stderrSlot)
mode := prog.IntVal(2, prog.Int32())
zeroSize := prog.Zero(sizeType)
nullBuf := prog.Nil(prog.CStr())
b.Call(setvbuf.Expr, stdoutPtr, nullBuf, mode, zeroSize)
b.Call(setvbuf.Expr, stderrPtr, nullBuf, mode, zeroSize)
}
func declareExternalPtrGlobal(pkg llssa.Package, name string, valueType llssa.Type) llssa.Expr {
ptrType := pkg.Prog.Pointer(valueType)
global := pkg.NewVarEx(name, ptrType)
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]},
)
}

View 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)
}
}

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,7 @@ var hasAltPkg = map[string]none{
"crypto/sha256": {},
"crypto/sha512": {},
"crypto/subtle": {},
"go/build": {},
"go/parser": {},
"hash/crc32": {},
"hash/maphash": {},

View File

@@ -0,0 +1,34 @@
//go:build !llgo
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tls
// Handle is a no-op TLS handle used when building without the llgo tag.
type Handle[T any] struct{}
// Alloc returns a stub TLS handle that ignores all operations.
func Alloc[T any](func(*T)) Handle[T] { return Handle[T]{} }
// Get always returns the zero value.
func (Handle[T]) Get() (zero T) { return zero }
// Set is a no-op.
func (Handle[T]) Set(T) {}
// Clear is a no-op.
func (Handle[T]) Clear() {}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package runtime
import "github.com/goplus/llgo/runtime/internal/clite/tls"
var deferTLS = tls.Alloc[*Defer](func(head **Defer) {
if head != nil {
*head = nil
}
})
// SetThreadDefer associates the current thread with the given defer chain.
func SetThreadDefer(head *Defer) {
deferTLS.Set(head)
}
// GetThreadDefer returns the current thread's defer chain head.
func GetThreadDefer() *Defer {
return deferTLS.Get()
}
// ClearThreadDefer resets the current thread's defer chain to nil.
func ClearThreadDefer() {
deferTLS.Clear()
}

View File

@@ -0,0 +1,33 @@
//go:build !nogc
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package runtime
import (
"unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/bdwgc"
)
// FreeDeferNode releases a defer argument node allocated from the Boehm heap.
func FreeDeferNode(ptr unsafe.Pointer) {
if ptr != nil {
bdwgc.Free(c.Pointer(ptr))
}
}

View File

@@ -0,0 +1,32 @@
//go:build nogc
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package runtime
import (
"unsafe"
c "github.com/goplus/llgo/runtime/internal/clite"
)
// FreeDeferNode releases the defer node when GC integration is disabled.
func FreeDeferNode(ptr unsafe.Pointer) {
if ptr != nil {
c.Free(ptr)
}
}

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
//go:build !wasm && !baremetal
/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package runtime
import (
c "github.com/goplus/llgo/runtime/internal/clite"
"github.com/goplus/llgo/runtime/internal/clite/signal"
)
const (
// SIGSEGV is signal number 11 on all Unix-like systems (Linux, Darwin, BSD, etc.)
// Using a hardcoded constant avoids importing the syscall package, which would
// introduce dependencies on errors and internal/reflectlite packages that cause
// linking issues in c-shared and c-archive build modes.
SIGSEGV = c.Int(0xb)
)
// This file contains platform-specific runtime initialization for non-wasm targets.
// The SIGSEGV signal handler enables Go-style panic recovery for nil pointer dereferences
// instead of immediate process termination.
//
// For wasm platform compatibility, signal handling is excluded via build tags.
// See PR #1059 for wasm platform requirements.
func init() {
signal.Signal(SIGSEGV, func(v c.Int) {
if v == SIGSEGV {
panic(errorString("invalid memory address or nil pointer dereference"))
}
var buf [20]byte
panic(errorString("unexpected signal value: " + string(itoa(buf[:], uint64(v)))))
})
}

View File

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

View File

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

View File

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

128
ssa/eh.go
View File

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

@@ -0,0 +1,53 @@
//go:build !llgo
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ssa_test
import (
"strings"
"testing"
"github.com/goplus/llgo/ssa"
"github.com/goplus/llgo/ssa/ssatest"
)
func TestDeferInLoopIR(t *testing.T) {
prog := ssatest.NewProgram(t, nil)
pkg := prog.NewPackage("foo", "foo")
callee := pkg.NewFunc("callee", ssa.NoArgsNoRet, ssa.InGo)
cb := callee.MakeBody(1)
cb.Return()
cb.EndBuild()
fn := pkg.NewFunc("main", ssa.NoArgsNoRet, ssa.InGo)
b := fn.MakeBody(1)
fn.SetRecover(fn.MakeBlock())
// Ensure entry block has a terminator like real codegen
b.Return()
b.SetBlockEx(fn.Block(0), ssa.BeforeLast, true)
b.Defer(ssa.DeferInLoop, callee.Expr)
b.EndBuild()
ir := pkg.Module().String()
if !strings.Contains(ir, "icmp ne ptr") {
t.Fatalf("expected loop defer condition in IR, got:\n%s", ir)
}
}

View File

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

View File

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

View File

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

326
test/defer_test.go Normal file
View File

@@ -0,0 +1,326 @@
/*
* Copyright (c) 2025 The GoPlus Authors (goplus.org). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package test
import (
"reflect"
"strconv"
"testing"
)
// runLoopDefers exercises a defer statement inside a loop and relies on
// defers executing after the loop but before the function returns.
func runLoopDefers() (result []int) {
for i := 0; i < 3; i++ {
v := i
defer func() {
result = append(result, v)
}()
}
return
}
func runLoopDeferCount(n int) (count int) {
for i := 0; i < n; i++ {
defer func() {
count++
}()
}
return
}
func runDeferRecover() (recovered any, ran bool) {
defer func() {
recovered = recover()
ran = true
}()
panic("defer recover sentinel")
}
func runNestedLoopDeferOrder() (order []string) {
outerNestedLoop(&order)
return
}
func outerNestedLoop(order *[]string) {
for i := 0; i < 3; i++ {
v := i
label := "outer:" + strconv.Itoa(v)
defer func(label string) {
*order = append(*order, label)
}(label)
}
middleNestedLoop(order)
}
func middleNestedLoop(order *[]string) {
for i := 0; i < 2; i++ {
v := i
label := "middle:" + strconv.Itoa(v)
defer func(label string) {
*order = append(*order, label)
}(label)
}
innerNestedLoop(order)
}
func innerNestedLoop(order *[]string) {
for i := 0; i < 4; i++ {
v := i
label := "inner:" + strconv.Itoa(v)
defer func(label string) {
*order = append(*order, label)
}(label)
}
}
func TestDeferInLoopOrder(t *testing.T) {
got := runLoopDefers()
want := []int{2, 1, 0}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unexpected defer order: got %v, want %v", got, want)
}
}
func TestDeferLoopStress(t *testing.T) {
const n = 1_000_000
if got := runLoopDeferCount(n); got != n {
t.Fatalf("unexpected count: got %d, want %d", got, n)
}
}
func TestDeferRecoverHandlesPanic(t *testing.T) {
got, ran := runDeferRecover()
want := "defer recover sentinel"
if !ran {
t.Fatalf("recover defer not executed")
}
str, ok := got.(string)
if !ok {
t.Fatalf("recover returned %T, want string", got)
}
if str != want {
t.Fatalf("unexpected recover value: got %q, want %q", str, want)
}
}
func TestNestedDeferLoops(t *testing.T) {
got := runNestedLoopDeferOrder()
want := []string{
"inner:3", "inner:2", "inner:1", "inner:0",
"middle:1", "middle:0",
"outer:2", "outer:1", "outer:0",
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("unexpected nested order: got %v, want %v", got, want)
}
}
func runNestedConditionalDeferWithRecover() (order []string, recovered any) {
defer func() { recovered = recover() }()
nestedCondOuter(&order)
return
}
func nestedCondOuter(order *[]string) {
for i := 0; i < 3; i++ {
v := i
label := "outer:" + strconv.Itoa(v)
if v%2 == 0 {
defer func(label string) {
*order = append(*order, label)
}(label)
}
nestedCondMiddle(order, v)
}
}
func nestedCondMiddle(order *[]string, v int) {
for j := 0; j < 3; j++ {
u := j
label := "middle:" + strconv.Itoa(u)
if u < 2 {
defer func(label string) {
*order = append(*order, label)
}(label)
}
nestedCondInner(order)
}
if v == 1 {
panic("nested-conditional-boom")
}
}
func nestedCondInner(order *[]string) {
for k := 0; k < 2; k++ {
w := k
label := "inner:" + strconv.Itoa(w)
defer func(label string) {
*order = append(*order, label)
}(label)
}
}
func TestNestedConditionalDeferWithRecover(t *testing.T) {
gotOrder, gotRecovered := runNestedConditionalDeferWithRecover()
wantRecovered := "nested-conditional-boom"
if s, ok := gotRecovered.(string); !ok || s != wantRecovered {
t.Fatalf("unexpected recover value: got %v, want %q", gotRecovered, wantRecovered)
}
wantOrder := []string{
"inner:1", "inner:0",
"inner:1", "inner:0",
"inner:1", "inner:0",
"middle:1", "middle:0",
"inner:1", "inner:0",
"inner:1", "inner:0",
"inner:1", "inner:0",
"middle:1", "middle:0",
"outer:0",
}
if !reflect.DeepEqual(gotOrder, wantOrder) {
t.Fatalf("unexpected nested conditional order: got %v, want %v", gotOrder, wantOrder)
}
}
func callWithRecover(fn func()) (recovered any) {
defer func() { recovered = recover() }()
fn()
return
}
func loopBranchEven(order *[]string, i int) {
label := "even:" + strconv.Itoa(i)
defer func() { *order = append(*order, label) }()
}
func loopBranchOddNoRecover(order *[]string, i int) {
label := "odd-wrap:" + strconv.Itoa(i)
defer func() { *order = append(*order, label) }()
panic("odd-no-recover")
}
func loopBranchOddLocalRecover(order *[]string, i int) {
label := "odd-local:" + strconv.Itoa(i)
defer func() { *order = append(*order, label) }()
defer func() { _ = recover() }()
panic("odd-local-recover")
}
func runLoopBranchRecoverMixed(n int) (order []string, recoveredVals []any) {
for i := 0; i < n; i++ {
if i%2 == 0 {
loopBranchEven(&order, i)
} else if i%4 == 3 {
rec := callWithRecover(func() { loopBranchOddNoRecover(&order, i) })
recoveredVals = append(recoveredVals, rec)
} else {
loopBranchOddLocalRecover(&order, i)
}
}
return
}
func TestLoopBranchRecoverMixed(t *testing.T) {
order, recovered := runLoopBranchRecoverMixed(6)
wantOrder := []string{
"even:0",
"odd-local:1",
"even:2",
"odd-wrap:3",
"even:4",
"odd-local:5",
}
if !reflect.DeepEqual(order, wantOrder) {
t.Fatalf("unexpected loop/branch order: got %v, want %v", order, wantOrder)
}
if len(recovered) != 1 {
t.Fatalf("unexpected recovered count: got %d, want %d", len(recovered), 1)
}
if s, ok := recovered[0].(string); !ok || s != "odd-no-recover" {
t.Fatalf("unexpected recovered value: got %v, want %q", recovered[0], "odd-no-recover")
}
}
func deepInner(order *[]string) {
for i := 0; i < 2; i++ {
idx := i
label := "inner:" + strconv.Itoa(idx)
defer func(label string) {
*order = append(*order, label)
}(label)
if idx == 0 {
continue
}
panic("deep-boom")
}
}
func deepMiddle(order *[]string) {
for i := 0; i < 2; i++ {
idx := i
label := "middle:" + strconv.Itoa(idx)
defer func(label string) {
*order = append(*order, label)
}(label)
if idx == 0 {
continue
}
defer func() {
if rec := recover(); rec != nil {
panic(rec)
}
}()
deepInner(order)
}
}
func deepOuter(order *[]string) (recovered any) {
for i := 0; i < 2; i++ {
idx := i
label := "outer:" + strconv.Itoa(idx)
defer func(label string) {
*order = append(*order, label)
}(label)
if idx == 0 {
continue
}
defer func() {
if rec := recover(); rec != nil {
recovered = rec
}
}()
deepMiddle(order)
}
return
}
func TestPanicCrossTwoFunctionsRecover(t *testing.T) {
var order []string
recovered := deepOuter(&order)
if s, ok := recovered.(string); !ok || s != "deep-boom" {
t.Fatalf("unexpected recovered value: got %v, want %q", recovered, "deep-boom")
}
wantOrder := []string{
"inner:1", "inner:0",
"middle:1", "middle:0",
"outer:1", "outer:0",
}
if !reflect.DeepEqual(order, wantOrder) {
t.Fatalf("unexpected cross-function defer order: got %v, want %v", order, wantOrder)
}
}