Skip to content

refactor: keep existing function directives on declaration objects - #2572

Draft
zhouguangyuan0718 wants to merge 15 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/source-contracts-20260912
Draft

zhouguangyuan0718 wants to merge 15 commits into
xgo-dev:mainfrom
zhouguangyuan0718:codex/source-contracts-20260912

Conversation

@zhouguangyuan0718

@zhouguangyuan0718 zhouguangyuan0718 commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Existing function directives are stored in separate program-wide tables and looked up again during lowering. This refactor gives each source function or method a declaration object and carries that object with the frontend function.

The declaration owns the existing //llgo:env, //go:wasmimport, //go:nointerface, function linkname and export information. Parsing creates the object first, then sets its properties. The dedicated env, wasm-import and nointerface tables are removed. Function links and exports move out of the shared tables when their declarations are bound; variable and unresolved-symbol entries retain their existing lookup paths.

The existing frontend-to-backend association now stores an aFunction wrapper containing the Go SSA function, its source declaration and its backend entry. Declaration identity includes the package and source location. Two declarations that link to the same LLVM symbol keep independent metadata, while the existing backend function table still resolves symbols and checks closure-environment ABI compatibility. Function constructors keep their existing signatures.

Generic instances retain their origin declaration, including private functions omitted from export-only preload. Both preloaded builds and the one-shot compilation entry point explicitly bind package-patch replacement declarations; original declarations retain their own information. The one-shot path selects replacements from the alternate SSA package, including declared methods, because its source files also contain the original declarations. Prepared declarations contain no LLVM state and are shared read-only by backend Programs.

Validation on macOS arm64 with Go 1.27.0 and LLVM 22.1.8:

  • Complete cl, ssa, and internal/build suites rerun after the one-shot patch-binding fix.
  • Complete cl suite with LLGO_BUILD_CACHE=off, plus complete ssa and internal/build suites. These include existing env ABI, wasm import, nointerface, export and locality/linkname coverage.
  • Execution tests for ordinary builds and full LTO with caching enabled after invalidating stale development artifacts.
  • Regression tests for independent aliases and package variants, pending method declarations, generic origin binding after export-only preload, patched imported intrinsics, and one-shot patch binding for ordinary functions, generic functions, and value/pointer receiver methods.
  • Runtime tests at -O2 with default GC and nogc.
  • Cross-package cache checks with a generic caller: first build, cache hit, linkname-only retargeting, restoration and another cache hit. Retargeting recompiles the library and changes the executable result; restoration reverses it.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Source-level function contracts

This is a large, well-engineered feature adding //llgo:attribute source contracts that lower to LLVM attributes and llvm.assume facts, surviving large-aggregate and C-ABI transformations. The source model is cleanly separated from the LLVM encoding, test coverage is broad (parsing, ABI survival across amd64/arm64/386/wasm, invoke edges, JSON round-trips, optimization folding), and doc/Function-attributes.md is accurate against the parser and validator (verified selector-by-selector, including the prototype-spelling normalizations).

The findings below are refinements, not correctness bugs I can prove. The most notable is a build-reproducibility concern from map-order IR emission.

Positive observations

  • Careful soundness handling: signed-interval range lowered via subtraction in source width; large-array GEP uses target-pointer-width indices to avoid a valid 64-bit index going negative; invoke facts split onto a normal edge with PHI repair.
  • Merge deterministically sorts and deep-clones mutable operands, with a test proving callers can't mutate merged state.
  • Declaration-vs-definition strength is guarded: imported declarations are widened uniformly under GC roots / cooperative safepoints, and unsupported signature-changing invoke conversions are rejected rather than silently mis-attributed.
  • Docs and code comments match the implementation; no material documentation discrepancies found.

Comment thread internal/funcattrs/values.go Outdated
Comment on lines +226 to +236
for fn, plan := range plans {
if fn.IsDeclaration() {
continue
}
b.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
for _, contract := range plan.Contracts {
if contract.Target.Parameter >= 0 {
value := extractValue(b, fn.Param(contract.Target.Parameter), contract.Target.Path)
emitValueFact(b, value, contract, plan.PointerBits)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Nondeterministic IR emission from map iteration order

MaterializeValueContracts iterates for fn, plan := range plans (a map[llvm.Value]valuePlan) to emit entry llvm.assume/extractvalue facts. Go map iteration order is randomized, so the order in which entry facts are created across functions is nondeterministic. For a compiler this can defeat reproducible builds and undercuts the doc's promise that annotation-only cache hits "preserve the same contracts and optimization behavior." The idempotency test only covers a single function's byte-stable output, not cross-function ordering. Suggest collecting functions into a slice in module order (as the code already does for calls below) before emitting.

Comment thread internal/abi/large.go Outdated
Comment on lines +129 to +141
switch typ.TypeKind() {
case llvm.StructTypeKind:
gepIndices = append(gepIndices, llvm.ConstInt(ctx.Int32Type(), uint64(index), false))
offset += l.td.ElementOffset(typ, int(index))
typ = typ.StructElementTypes()[index]
case llvm.ArrayTypeKind:
// Array indices are signed GEP operands. Use the target pointer
// width so a valid large 64-bit array index cannot become negative.
gepIndices = append(gepIndices, llvm.ConstInt(ctx.IntType(l.td.PointerSize()*8), uint64(index), false))
typ = typ.ElementType()
offset += uint64(index) * l.td.TypeAllocSize(typ)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Missing default case in scalarizeLoadProjections GEP walk

The per-index switch typ.TypeKind() handles only StructTypeKind and ArrayTypeKind. If a traversal step ever lands on another aggregate kind (e.g. a vector or an unexpected type), the loop appends no GEP index and does not advance typ, then loads extract.Type() from a wrong/short address — a silent bad load rather than a panic. Not clearly reachable today (large-aggregate leaves are struct/array), but the sibling paths added in this PR (e.g. the invoke guard) fail loudly on unexpected shapes. Recommend a default: that bails out (fall back to the whole-value load) to make the invariant explicit.

Comment thread internal/funcattrs/effects.go Outdated
Comment on lines +234 to +239
effects := NativeMemoryEffects(attr.Memory, HasHiddenPointerRoots(fn))
// A contract assume models an already-required condition, not
// an observable storage access. LLVM's intrinsic-only control
// dependency does not widen the enclosing memory contract.
fn.AddFunctionAttr(ctx.CreateEnumAttribute(llvm.AttributeKindID("memory"), effects))
if HasHiddenPointerRoots(fn) && attr.Memory.Args&^attr.Memory.Other != 0 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] HasHiddenPointerRoots(fn) computed twice per memory contract

In ApplyEffects, HasHiddenPointerRoots(fn) is called once for NativeMemoryEffects(...) and again immediately in the if HasHiddenPointerRoots(fn) && ... guard. It walks every parameter type and recurses through aggregate fields; the result can't change between the two calls. Hoist it into a local hidden := HasHiddenPointerRoots(fn).

Comment thread ssa/function_attributes.go Outdated
Comment on lines +86 to +91
var owners []string
for source := range data.functionAttributes {
if source == name || resolve(source) == resolved {
owners = append(owners, source)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] functionAttributes() scans the whole attribute map on every newFunc

functionAttributes(name) is called from newFunc for every function created in a package, including those with no contracts, and it iterates the entire functionAttributes map plus a sort.Strings and slice allocations on each call. Contracts are sparse so this isn't quadratic-in-module-size, but the common (no-contract) case still pays a full map walk in a heavily-annotated package like the runtime. Suggest an early if len(data.functionAttributes) == 0 { return nil, nil } and short-circuiting the alias scan when name is the only owner; the reverse linkname lookup could also be a precomputed resolved -> []owner index.

Comment thread internal/funcattrs/llvm.go Outdated
Comment on lines +74 to +83
func RemapFunction(from, to llvm.Value, m ABIMapping) error {
remapValues(m, from.GetEnumAttributeAtIndex, to.AddAttributeAtIndex, to.RemoveEnumAttributeAtIndex)
RemapFunctionEffects(from, to, m)
return nil
}

func RemapCall(from, to llvm.Value, m ABIMapping) error {
// ABI call replacements are new instructions and do not blanket-copy
// returned attributes, so dropping an incompatible one needs no removal.
remapValues(m, from.GetCallSiteEnumAttribute, to.AddCallSiteAttribute, nil)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] RemapCall/RemapFunction return an error that is always dead + panicked on

RemapCall/RemapFunction are declared to return an error, but both return nil unconditionally, and every call site does if err := ...; err != nil { panic(err) } (large.go, cabi.go). The error return is dead API surface and the panic is unreachable, while the new hard-failures in these passes are bare string panics and CheckInstrumentation error-value panics — three styles in adjacent code. It's broadly consistent with the panic-based legacy ABI layer, but a malformed contract reaching this stage aborts with a stack trace instead of a located diagnostic (the generics test has to recover() and type-assert to convert). Consider dropping the unused error returns, or threading real errors and wrapping failures in the positioned Attribute.Error(...) form.

@codecov

codecov Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.79832% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cl/function.go 93.75% 3 Missing ⚠️
cl/import.go 94.00% 2 Missing and 1 partial ⚠️
ssa/function_declaration.go 96.34% 3 Missing ⚠️
ssa/package_syntax.go 90.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

LLGo WebAssembly build benchmarks

5cc8078c391e | workflow run | long-term charts

WebAssembly output sizes

Profile and compiler Wasm module vs base Generated JS glue vs base
ec32/LLGo 112905 B 0 B / +0.0% 70736 B 0 B / +0.0%
ec64/LLGo 118667 B 0 B / +0.0% 74033 B 0 B / +0.0%
js/Go 1895533 B 0 B / +0.0% 0 B 0 B / 0.0%
js/LLGo 66392 B 0 B / +0.0% 68511 B 0 B / +0.0%
wasip1/Go 1909947 B 0 B / +0.0% 0 B 0 B / 0.0%
wasip1/LLGo 72689 B 0 B / +0.0% 0 B 0 B / 0.0%
wc32/LLGo 117007 B 0 B / +0.0% 0 B 0 B / 0.0%

LLGo WebAssembly build measurements

Profile Build vs base
ec32 5.238 s -79.38 ms / -1.5% (better)
ec64 4.882 s +10.94 ms / +0.2% (worse)
js 4.306 s -77.12 ms / -1.8% (better)
wasip1 2.967 s -16.55 ms / -0.6% (better)
wc32 3.645 s -19.57 ms / -0.5% (better)

Compared with 2db247e43848 measured in the same runner job.

@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

LLGo baseline benchmarks

5cc8078c391e | workflow run | long-term charts

Program measurements

Platform Workload File size vs base Text size vs base Build vs base Run vs base
Linux cprintf 19832 B 0 B / +0.0% 387 B 0 B / +0.0% 464.922 ms -44.5 ms / -8.7% (better) 1.289 ms +45.03 us / +3.6% (worse)
Linux cprintf-lto 19584 B 0 B / +0.0% 368 B 0 B / +0.0% 472.564 ms -15.04 ms / -3.1% (better) 1.256 ms -86.75 us / -6.5% (better)
Linux fmtprintf 1635576 B 0 B / +0.0% 486024 B 0 B / +0.0% 3.745 s -15.1 ms / -0.4% (better) 3.098 ms -579.3 us / -15.8% (better)
Linux fmtprintf-lto 1474944 B 0 B / +0.0% 421849 B 0 B / +0.0% 10.495 s -109.4 ms / -1.0% (better) 2.956 ms -1.262 ms / -29.9% (better)
Linux println 61968 B 0 B / +0.0% 14668 B 0 B / +0.0% 470.532 ms -117.5 ms / -20.0% (better) 1.648 ms -3.403 us / -0.2% (better)
Linux println-lto 54040 B 0 B / +0.0% 12115 B 0 B / +0.0% 663.618 ms -1.81 ms / -0.3% (better) 1.569 ms +8.411 us / +0.5% (worse)
macOS cprintf 84480 B 0 B / +0.0% 17101 B 0 B / +0.0% 1.123 s -411.7 ms / -26.8% (better) 8.785 ms +1.743 ms / +24.7% (worse)
macOS cprintf-lto 84288 B 0 B / +0.0% 12865 B 0 B / +0.0% 1.113 s -216.6 ms / -16.3% (better) 4.144 ms -4.122 ms / -49.9% (better)
macOS fmtprintf 1483408 B 0 B / +0.0% 859208 B 0 B / +0.0% 5.278 s +762.3 ms / +16.9% (worse) 8.409 ms -15.79 us / -0.2% (better)
macOS fmtprintf-lto 1175808 B 0 B / +0.0% 830484 B 0 B / +0.0% 11.718 s +1.015 s / +9.5% (worse) 7.662 ms +2.042 ms / +36.3% (worse)
macOS println 114672 B 0 B / +0.0% 34668 B 0 B / +0.0% 912.885 ms -79.04 ms / -8.0% (better) 4.525 ms -3.276 ms / -42.0% (better)
macOS println-lto 118720 B 0 B / +0.0% 32112 B 0 B / +0.0% 1.131 s -1.822 ms / -0.2% (better) 6.202 ms +118.8 us / +2.0% (worse)
Windows MinGW cprintf 19456 B 0 B / +0.0% 4550 B 0 B / +0.0% 1.197 s -3.309 ms / -0.3% (better) 3.469 ms -612.1 us / -15.0% (better)
Windows MinGW cprintf-lto 17920 B 0 B / +0.0% 4486 B 0 B / +0.0% 1.341 s +107.2 ms / +8.7% (worse) 3.459 ms -74.2 us / -2.1% (better)
Windows MinGW fmtprintf 1909248 B 0 B / +0.0% 588134 B 0 B / +0.0% 4.300 s -44.47 ms / -1.0% (better) 8.421 ms -996.7 us / -10.6% (better)
Windows MinGW fmtprintf-lto 1930240 B 0 B / +0.0% 534406 B 0 B / +0.0% 10.934 s -2.67 ms / -0.02441% (better) 9.709 ms +136.7 us / +1.4% (worse)
Windows MinGW println 71168 B 0 B / +0.0% 23718 B 0 B / +0.0% 1.169 s -130.2 ms / -10.0% (better) 6.888 ms -307.2 us / -4.3% (better)
Windows MinGW println-lto 65024 B 0 B / +0.0% 20406 B 0 B / +0.0% 1.419 s +4.271 ms / +0.3% (worse) 7.153 ms -758 us / -9.6% (better)
Windows MinGW 386 cprintf 42496 B 0 B / +0.0% 5326 B 0 B / +0.0% 1.158 s +60.01 ms / +5.5% (worse) 5.184 ms +29.7 us / +0.6% (worse)
Windows MinGW 386 cprintf-lto 20992 B 0 B / +0.0% 5094 B 0 B / +0.0% 1.135 s -26.29 ms / -2.3% (better) 5.148 ms -126.3 us / -2.4% (better)
Windows MinGW 386 fmtprintf 1871872 B 0 B / +0.0% 462462 B 0 B / +0.0% 4.261 s +165.4 ms / +4.0% (worse) 10.572 ms -881.9 us / -7.7% (better)
Windows MinGW 386 fmtprintf-lto 2148864 B 0 B / +0.0% 440142 B 0 B / +0.0% 9.775 s +122.3 ms / +1.3% (worse) 10.274 ms -130.6 us / -1.3% (better)
Windows MinGW 386 println 90624 B 0 B / +0.0% 19830 B 0 B / +0.0% 1.096 s +5.14 ms / +0.5% (worse) 8.526 ms -157.1 us / -1.8% (better)
Windows MinGW 386 println-lto 69120 B 0 B / +0.0% 17742 B 0 B / +0.0% 1.316 s +26.8 ms / +2.1% (worse) 8.618 ms +20.7 us / +0.2% (worse)
Windows MinGW ARM64 cprintf 18944 B 0 B / +0.0% 4408 B 0 B / +0.0% 1.351 s -19.2 ms / -1.4% (better) 5.943 ms -151.9 us / -2.5% (better)
Windows MinGW ARM64 cprintf-lto 17920 B 0 B / +0.0% 4340 B 0 B / +0.0% 1.399 s -6.255 ms / -0.4% (better) 6.165 ms +234.7 us / +4.0% (worse)
Windows MinGW ARM64 fmtprintf 1797120 B 0 B / +0.0% 500676 B 0 B / +0.0% 4.106 s +14.68 ms / +0.4% (worse) 12.181 ms -3.8 us / -0.03119% (better)
Windows MinGW ARM64 fmtprintf-lto 1854464 B 0 B / +0.0% 465220 B 0 B / +0.0% 9.234 s -31.37 ms / -0.3% (better) 12.031 ms -856.2 us / -6.6% (better)
Windows MinGW ARM64 println 68096 B 0 B / +0.0% 22376 B 0 B / +0.0% 1.361 s +3.997 ms / +0.3% (worse) 10.370 ms +41.1 us / +0.4% (worse)
Windows MinGW ARM64 println-lto 63488 B 0 B / +0.0% 19444 B 0 B / +0.0% 1.578 s +8.749 ms / +0.6% (worse) 10.695 ms +91.2 us / +0.9% (worse)
Windows MSVC cprintf 120320 B 0 B / +0.0% 65782 B 0 B / +0.0% 955.172 ms +17.33 ms / +1.8% (worse) 3.503 ms -521.5 us / -13.0% (better)
Windows MSVC cprintf-lto 119808 B 0 B / +0.0% 65718 B 0 B / +0.0% 960.439 ms -6.419 ms / -0.7% (better) 3.514 ms +103.6 us / +3.0% (worse)
Windows MSVC fmtprintf 1624064 B 0 B / +0.0% 683654 B 0 B / +0.0% 3.846 s +187 ms / +5.1% (worse) 9.711 ms +777.5 us / +8.7% (worse)
Windows MSVC fmtprintf-lto 1613824 B 0 B / +0.0% 635046 B 0 B / +0.0% 9.368 s +233.2 ms / +2.6% (worse) 10.402 ms +924.9 us / +9.8% (worse)
Windows MSVC println 192512 B 0 B / +0.0% 119142 B 0 B / +0.0% 938.978 ms +8.191 ms / +0.9% (worse) 7.991 ms +926.3 us / +13.1% (worse)
Windows MSVC println-lto 189952 B 0 B / +0.0% 116374 B 0 B / +0.0% 1.123 s +13.42 ms / +1.2% (worse) 7.740 ms +564.2 us / +7.9% (worse)
Windows MSVC 386 cprintf 9728 B 0 B / +0.0% 3931 B 0 B / +0.0% 905.315 ms -997.9 us / -0.1% (better) 5.273 ms +69.4 us / +1.3% (worse)
Windows MSVC 386 cprintf-lto 9216 B 0 B / +0.0% 3853 B 0 B / +0.0% 1.116 s +132.2 ms / +13.4% (worse) 6.896 ms -268.8 us / -3.8% (better)
Windows MSVC 386 fmtprintf 1186304 B 0 B / +0.0% 445829 B 0 B / +0.0% 3.680 s -46.5 ms / -1.2% (better) 11.726 ms +411.9 us / +3.6% (worse)
Windows MSVC 386 fmtprintf-lto 1221632 B 0 B / +0.0% 416841 B 0 B / +0.0% 8.404 s +55.23 ms / +0.7% (worse) 11.832 ms +623.4 us / +5.6% (worse)
Windows MSVC 386 println 34304 B 0 B / +0.0% 18673 B 0 B / +0.0% 903.188 ms -51.22 ms / -5.4% (better) 9.009 ms -49.8 us / -0.5% (better)
Windows MSVC 386 println-lto 32256 B 0 B / +0.0% 16855 B 0 B / +0.0% 1.089 s +22.1 ms / +2.1% (worse) 8.906 ms -159 us / -1.8% (better)
Windows MSVC ARM64 cprintf 11264 B 0 B / +0.0% 3976 B 0 B / +0.0% 2.186 s +107.2 ms / +5.2% (worse) 7.938 ms +1.097 ms / +16.0% (worse)
Windows MSVC ARM64 cprintf-lto 10752 B 0 B / +0.0% 3868 B 0 B / +0.0% 2.158 s +39.27 ms / +1.9% (worse) 7.557 ms +293.4 us / +4.0% (worse)
Windows MSVC ARM64 fmtprintf 1371648 B 0 B / +0.0% 501620 B 0 B / +0.0% 6.666 s +119.9 ms / +1.8% (worse) 14.364 ms -1.362 ms / -8.7% (better)
Windows MSVC ARM64 fmtprintf-lto 1388032 B 0 B / +0.0% 468132 B 0 B / +0.0% 15.724 s -335.5 ms / -2.1% (better) 14.988 ms -618.4 us / -4.0% (better)
Windows MSVC ARM64 println 41472 B 0 B / +0.0% 21624 B 0 B / +0.0% 2.108 s -20.72 ms / -1.0% (better) 14.338 ms +883.5 us / +6.6% (worse)
Windows MSVC ARM64 println-lto 39424 B 0 B / +0.0% 19372 B 0 B / +0.0% 2.431 s -41.1 ms / -1.7% (better) 13.599 ms +395.1 us / +3.0% (worse)
Core language and compiler benchmarks
Platform Benchmark ns/op vs base
Linux BenchmarkLookupPCRandom 14.510 ns/op -0.18 ns/op / -1.2% (better)
Linux BenchmarkMergeCompilerFlags 209.200 ns/op +7.4 ns/op / +3.7% (worse)
Linux BenchmarkMergeLinkerFlags 133.200 ns/op -0.6 ns/op / -0.4% (better)
Linux BenchmarkChannelBuffered 55.050 ns/op -0.29 ns/op / -0.5% (better)
Linux BenchmarkChannelHandoff 12910 ns/op -7029 ns/op / -35.3% (better)
Linux BenchmarkDefer 46.480 ns/op -1.16 ns/op / -2.4% (better)
Linux BenchmarkDirectCall 1.170 ns/op +0.005 ns/op / +0.4% (worse)
Linux BenchmarkGlobalRead 1.163 ns/op -0.011 ns/op / -0.9% (better)
Linux BenchmarkGlobalWrite 7.764 ns/op +0.002 ns/op / +0.02577% (worse)
Linux BenchmarkGoroutine 21218 ns/op -8488 ns/op / -28.6% (better)
Linux BenchmarkInterfaceCall 5.909 ns/op -0.011 ns/op / -0.2% (better)
Linux BenchmarkRuntimeGetG 2.431 ns/op -0.004 ns/op / -0.2% (better)
macOS BenchmarkLookupPCRandom 21.770 ns/op +4.35 ns/op / +25.0% (worse)
macOS BenchmarkMergeCompilerFlags 144.100 ns/op -23.2 ns/op / -13.9% (better)
macOS BenchmarkMergeLinkerFlags 80.900 ns/op -24 ns/op / -22.9% (better)
macOS BenchmarkChannelBuffered 24.710 ns/op -10.39 ns/op / -29.6% (better)
macOS BenchmarkChannelHandoff 8030 ns/op -4359 ns/op / -35.2% (better)
macOS BenchmarkDefer 36.140 ns/op -13.56 ns/op / -27.3% (better)
macOS BenchmarkDirectCall 1.170 ns/op -0.186 ns/op / -13.7% (better)
macOS BenchmarkGlobalRead 1.116 ns/op -0.37 ns/op / -24.9% (better)
macOS BenchmarkGlobalWrite 1.070 ns/op -0.642 ns/op / -37.5% (better)
macOS BenchmarkGoroutine 87495 ns/op +27446 ns/op / +45.7% (worse)
macOS BenchmarkInterfaceCall 3.811 ns/op -2.621 ns/op / -40.7% (better)
macOS BenchmarkRuntimeGetG 2.080 ns/op -0.583 ns/op / -21.9% (better)
Windows MinGW BenchmarkLookupPCRandom 12.340 ns/op -0.04 ns/op / -0.3% (better)
Windows MinGW BenchmarkMergeCompilerFlags 541.400 ns/op +6.6 ns/op / +1.2% (worse)
Windows MinGW BenchmarkMergeLinkerFlags 465.500 ns/op +4.4 ns/op / +1.0% (worse)
Windows MinGW BenchmarkChannelBuffered 31.410 ns/op +0.08 ns/op / +0.3% (worse)
Windows MinGW BenchmarkChannelHandoff 1399 ns/op -2 ns/op / -0.1% (better)
Windows MinGW BenchmarkDefer 55.610 ns/op +1.46 ns/op / +2.7% (worse)
Windows MinGW BenchmarkDirectCall 1.748 ns/op +0.003 ns/op / +0.2% (worse)
Windows MinGW BenchmarkGlobalRead 1.747 ns/op 0 ns/op / +0.0%
Windows MinGW BenchmarkGlobalWrite 2.788 ns/op +0.002 ns/op / +0.1% (worse)
Windows MinGW BenchmarkGoroutine 70645 ns/op +337 ns/op / +0.5% (worse)
Windows MinGW BenchmarkInterfaceCall 9.094 ns/op +0.005 ns/op / +0.1% (worse)
Windows MinGW BenchmarkRuntimeGetG 1.811 ns/op -0.002 ns/op / -0.1% (better)
Windows MinGW 386 BenchmarkLookupPCRandom 26.550 ns/op +0.03 ns/op / +0.1% (worse)
Windows MinGW 386 BenchmarkMergeCompilerFlags 745.300 ns/op -21.8 ns/op / -2.8% (better)
Windows MinGW 386 BenchmarkMergeLinkerFlags 706.100 ns/op +71 ns/op / +11.2% (worse)
Windows MinGW 386 BenchmarkChannelBuffered 39.330 ns/op -0.07 ns/op / -0.2% (better)
Windows MinGW 386 BenchmarkChannelHandoff 992.200 ns/op -20.8 ns/op / -2.1% (better)
Windows MinGW 386 BenchmarkDefer 43.370 ns/op +1.51 ns/op / +3.6% (worse)
Windows MinGW 386 BenchmarkDirectCall 1.548 ns/op +0.001 ns/op / +0.1% (worse)
Windows MinGW 386 BenchmarkGlobalRead 1.859 ns/op -0.009 ns/op / -0.5% (better)
Windows MinGW 386 BenchmarkGlobalWrite 7.775 ns/op 0 ns/op / +0.0%
Windows MinGW 386 BenchmarkGoroutine 84690 ns/op -2631 ns/op / -3.0% (better)
Windows MinGW 386 BenchmarkInterfaceCall 8.153 ns/op -0.008 ns/op / -0.1% (better)
Windows MinGW 386 BenchmarkRuntimeGetG 1.925 ns/op -0.002 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkLookupPCRandom 12.020 ns/op +0.02 ns/op / +0.2% (worse)
Windows MinGW ARM64 BenchmarkMergeCompilerFlags 576.400 ns/op +5.1 ns/op / +0.9% (worse)
Windows MinGW ARM64 BenchmarkMergeLinkerFlags 537.700 ns/op +3.1 ns/op / +0.6% (worse)
Windows MinGW ARM64 BenchmarkChannelBuffered 38.720 ns/op -0.05 ns/op / -0.1% (better)
Windows MinGW ARM64 BenchmarkChannelHandoff 2272 ns/op -74 ns/op / -3.2% (better)
Windows MinGW ARM64 BenchmarkDefer 53.070 ns/op +0.26 ns/op / +0.5% (worse)
Windows MinGW ARM64 BenchmarkDirectCall 0.589 ns/op -0.0001 ns/op / -0.01696% (better)
Windows MinGW ARM64 BenchmarkGlobalRead 0.737 ns/op -0.0002 ns/op / -0.02714% (better)
Windows MinGW ARM64 BenchmarkGlobalWrite 0.663 ns/op 0 ns/op / +0.0%
Windows MinGW ARM64 BenchmarkGoroutine 51895 ns/op +441 ns/op / +0.9% (worse)
Windows MinGW ARM64 BenchmarkInterfaceCall 4.337 ns/op -0.002 ns/op / -0.04609% (better)
Windows MinGW ARM64 BenchmarkRuntimeGetG 1.771 ns/op -0.023 ns/op / -1.3% (better)
Windows MSVC BenchmarkLookupPCRandom 12.640 ns/op +0.2 ns/op / +1.6% (worse)
Windows MSVC BenchmarkMergeCompilerFlags 543.200 ns/op +1.1 ns/op / +0.2% (worse)
Windows MSVC BenchmarkMergeLinkerFlags 466.300 ns/op +2.5 ns/op / +0.5% (worse)
Windows MSVC BenchmarkChannelBuffered 29.170 ns/op -0.59 ns/op / -2.0% (better)
Windows MSVC BenchmarkChannelHandoff 1311 ns/op +172 ns/op / +15.1% (worse)
Windows MSVC BenchmarkDefer 55.190 ns/op -0.78 ns/op / -1.4% (better)
Windows MSVC BenchmarkDirectCall 1.747 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC BenchmarkGlobalRead 1.772 ns/op -0.094 ns/op / -5.0% (better)
Windows MSVC BenchmarkGlobalWrite 2.791 ns/op -0.001 ns/op / -0.03582% (better)
Windows MSVC BenchmarkGoroutine 63256 ns/op -3177 ns/op / -4.8% (better)
Windows MSVC BenchmarkInterfaceCall 8.917 ns/op +0.169 ns/op / +1.9% (worse)
Windows MSVC BenchmarkRuntimeGetG 1.812 ns/op -0.028 ns/op / -1.5% (better)
Windows MSVC 386 BenchmarkLookupPCRandom 26.640 ns/op -0.08 ns/op / -0.3% (better)
Windows MSVC 386 BenchmarkMergeCompilerFlags 760.400 ns/op -21.1 ns/op / -2.7% (better)
Windows MSVC 386 BenchmarkMergeLinkerFlags 679 ns/op -3.9 ns/op / -0.6% (better)
Windows MSVC 386 BenchmarkChannelBuffered 40.570 ns/op +0.23 ns/op / +0.6% (worse)
Windows MSVC 386 BenchmarkChannelHandoff 898.500 ns/op -8.5 ns/op / -0.9% (better)
Windows MSVC 386 BenchmarkDefer 46.610 ns/op +0.09 ns/op / +0.2% (worse)
Windows MSVC 386 BenchmarkDirectCall 1.549 ns/op 0 ns/op / +0.0%
Windows MSVC 386 BenchmarkGlobalRead 1.861 ns/op +0.002 ns/op / +0.1% (worse)
Windows MSVC 386 BenchmarkGlobalWrite 7.762 ns/op -0.009 ns/op / -0.1% (better)
Windows MSVC 386 BenchmarkGoroutine 84594 ns/op +2865 ns/op / +3.5% (worse)
Windows MSVC 386 BenchmarkInterfaceCall 8.098 ns/op -0.003 ns/op / -0.03703% (better)
Windows MSVC 386 BenchmarkRuntimeGetG 2.171 ns/op -0.001 ns/op / -0.04604% (better)
Windows MSVC ARM64 BenchmarkLookupPCRandom 12.060 ns/op +0.08 ns/op / +0.7% (worse)
Windows MSVC ARM64 BenchmarkMergeCompilerFlags 575.400 ns/op +16.6 ns/op / +3.0% (worse)
Windows MSVC ARM64 BenchmarkMergeLinkerFlags 549.900 ns/op +20.7 ns/op / +3.9% (worse)
Windows MSVC ARM64 BenchmarkChannelBuffered 37.730 ns/op +0.02 ns/op / +0.1% (worse)
Windows MSVC ARM64 BenchmarkChannelHandoff 2466 ns/op -954 ns/op / -27.9% (better)
Windows MSVC ARM64 BenchmarkDefer 67.750 ns/op +1.14 ns/op / +1.7% (worse)
Windows MSVC ARM64 BenchmarkDirectCall 0.589 ns/op -0.0009 ns/op / -0.2% (better)
Windows MSVC ARM64 BenchmarkGlobalRead 0.666 ns/op +0.0006 ns/op / +0.1% (worse)
Windows MSVC ARM64 BenchmarkGlobalWrite 3.758 ns/op -0.003 ns/op / -0.1% (better)
Windows MSVC ARM64 BenchmarkGoroutine 56969 ns/op -1116 ns/op / -1.9% (better)
Windows MSVC ARM64 BenchmarkInterfaceCall 4.137 ns/op -0.002 ns/op / -0.04832% (better)
Windows MSVC ARM64 BenchmarkRuntimeGetG 1.807 ns/op -0.419 ns/op / -18.8% (better)

Timer runtime benchmarks

Platform Operation and runtime ns/op vs base
Linux AfterFuncZeroDelivery/Go 902.500 ns/op +0.5 ns/op / +0.1% (worse)
Linux AfterFuncZeroDelivery/LLGo 35148 ns/op +3560 ns/op / +11.3% (worse)
Linux CreateStop/Go 289.300 ns/op -1.6 ns/op / -0.6% (better)
Linux CreateStop/LLGo 1641 ns/op -85 ns/op / -4.9% (better)
Linux RearmStopped/Go 117.600 ns/op +1.8 ns/op / +1.6% (worse)
Linux RearmStopped/LLGo 1315 ns/op -52 ns/op / -3.8% (better)
Linux ResetActive/Go 69.280 ns/op +0.68 ns/op / +1.0% (worse)
Linux ResetActive/LLGo 801 ns/op +109.2 ns/op / +15.8% (worse)
Linux ResetHeap1024/Go 67.090 ns/op +0.03 ns/op / +0.04474% (worse)
Linux ResetHeap1024/LLGo 177.500 ns/op +0.4 ns/op / +0.2% (worse)
macOS AfterFuncZeroDelivery/Go 553.500 ns/op -175.3 ns/op / -24.1% (better)
macOS AfterFuncZeroDelivery/LLGo 117669 ns/op +11831 ns/op / +11.2% (worse)
macOS CreateStop/Go 222.100 ns/op +38.5 ns/op / +21.0% (worse)
macOS CreateStop/LLGo 815.200 ns/op +241.7 ns/op / +42.1% (worse)
macOS RearmStopped/Go 73.640 ns/op +0.33 ns/op / +0.5% (worse)
macOS RearmStopped/LLGo 757 ns/op +17.8 ns/op / +2.4% (worse)
macOS ResetActive/Go 53.840 ns/op +2.19 ns/op / +4.2% (worse)
macOS ResetActive/LLGo 177 ns/op -128.4 ns/op / -42.0% (better)
macOS ResetHeap1024/Go 53.670 ns/op -14.35 ns/op / -21.1% (better)
macOS ResetHeap1024/LLGo 88.630 ns/op -0.43 ns/op / -0.5% (better)
Windows MinGW AfterFuncZeroDelivery/Go 479.900 ns/op -17.6 ns/op / -3.5% (better)
Windows MinGW AfterFuncZeroDelivery/LLGo 128892 ns/op +2709 ns/op / +2.1% (worse)
Windows MinGW CreateStop/Go 116.500 ns/op -0.9 ns/op / -0.8% (better)
Windows MinGW CreateStop/LLGo 488.300 ns/op +34.2 ns/op / +7.5% (worse)
Windows MinGW RearmStopped/Go 31.520 ns/op +0.01 ns/op / +0.03174% (worse)
Windows MinGW RearmStopped/LLGo 288.100 ns/op +4.2 ns/op / +1.5% (worse)
Windows MinGW ResetActive/Go 19.060 ns/op -0.04 ns/op / -0.2% (better)
Windows MinGW ResetActive/LLGo 159.800 ns/op -6.8 ns/op / -4.1% (better)
Windows MinGW ResetHeap1024/Go 19.090 ns/op -0.05 ns/op / -0.3% (better)
Windows MinGW ResetHeap1024/LLGo 134.800 ns/op -1.3 ns/op / -1.0% (better)
Windows MinGW 386 AfterFuncZeroDelivery/Go 954.500 ns/op +6.2 ns/op / +0.7% (worse)
Windows MinGW 386 AfterFuncZeroDelivery/LLGo 187022 ns/op -1198 ns/op / -0.6% (better)
Windows MinGW 386 CreateStop/Go 191.800 ns/op +1.3 ns/op / +0.7% (worse)
Windows MinGW 386 CreateStop/LLGo 518 ns/op +22 ns/op / +4.4% (worse)
Windows MinGW 386 RearmStopped/Go 63.390 ns/op -0.03 ns/op / -0.0473% (better)
Windows MinGW 386 RearmStopped/LLGo 350.900 ns/op +3.3 ns/op / +0.9% (worse)
Windows MinGW 386 ResetActive/Go 39.030 ns/op -0.13 ns/op / -0.3% (better)
Windows MinGW 386 ResetActive/LLGo 992 ns/op +611.2 ns/op / +160.5% (worse)
Windows MinGW 386 ResetHeap1024/Go 39.500 ns/op +0.09 ns/op / +0.2% (worse)
Windows MinGW 386 ResetHeap1024/LLGo 191.800 ns/op +3.9 ns/op / +2.1% (worse)
Windows MinGW ARM64 AfterFuncZeroDelivery/Go 677 ns/op +6.2 ns/op / +0.9% (worse)
Windows MinGW ARM64 AfterFuncZeroDelivery/LLGo 141609 ns/op +460 ns/op / +0.3% (worse)
Windows MinGW ARM64 CreateStop/Go 201.500 ns/op +2 ns/op / +1.0% (worse)
Windows MinGW ARM64 CreateStop/LLGo 383.500 ns/op +25.9 ns/op / +7.2% (worse)
Windows MinGW ARM64 RearmStopped/Go 70.590 ns/op -0.05 ns/op / -0.1% (better)
Windows MinGW ARM64 RearmStopped/LLGo 253.600 ns/op -1.1 ns/op / -0.4% (better)
Windows MinGW ARM64 ResetActive/Go 30.900 ns/op -0.03 ns/op / -0.1% (better)
Windows MinGW ARM64 ResetActive/LLGo 124 ns/op +2.8 ns/op / +2.3% (worse)
Windows MinGW ARM64 ResetHeap1024/Go 30.980 ns/op -0.17 ns/op / -0.5% (better)
Windows MinGW ARM64 ResetHeap1024/LLGo 126 ns/op -0.7 ns/op / -0.6% (better)
Windows MSVC AfterFuncZeroDelivery/Go 472.900 ns/op -31.8 ns/op / -6.3% (better)
Windows MSVC AfterFuncZeroDelivery/LLGo 133520 ns/op +7428 ns/op / +5.9% (worse)
Windows MSVC CreateStop/Go 117.900 ns/op +0.5 ns/op / +0.4% (worse)
Windows MSVC CreateStop/LLGo 459.500 ns/op +18.8 ns/op / +4.3% (worse)
Windows MSVC RearmStopped/Go 31.490 ns/op -0.04 ns/op / -0.1% (better)
Windows MSVC RearmStopped/LLGo 272.200 ns/op 0 ns/op / +0.0%
Windows MSVC ResetActive/Go 19.090 ns/op +0.05 ns/op / +0.3% (worse)
Windows MSVC ResetActive/LLGo 154.300 ns/op -2 ns/op / -1.3% (better)
Windows MSVC ResetHeap1024/Go 19.060 ns/op -0.27 ns/op / -1.4% (better)
Windows MSVC ResetHeap1024/LLGo 130.500 ns/op +0.2 ns/op / +0.2% (worse)
Windows MSVC 386 AfterFuncZeroDelivery/Go 955.200 ns/op -0.9 ns/op / -0.1% (better)
Windows MSVC 386 AfterFuncZeroDelivery/LLGo 175130 ns/op -1574 ns/op / -0.9% (better)
Windows MSVC 386 CreateStop/Go 191.800 ns/op -2.3 ns/op / -1.2% (better)
Windows MSVC 386 CreateStop/LLGo 431.200 ns/op -50.3 ns/op / -10.4% (better)
Windows MSVC 386 RearmStopped/Go 63.330 ns/op -0.61 ns/op / -1.0% (better)
Windows MSVC 386 RearmStopped/LLGo 330.900 ns/op +7.6 ns/op / +2.4% (worse)
Windows MSVC 386 ResetActive/Go 39.060 ns/op +0.15 ns/op / +0.4% (worse)
Windows MSVC 386 ResetActive/LLGo 990.700 ns/op -3.1 ns/op / -0.3% (better)
Windows MSVC 386 ResetHeap1024/Go 39.380 ns/op -0.32 ns/op / -0.8% (better)
Windows MSVC 386 ResetHeap1024/LLGo 175.200 ns/op +2.1 ns/op / +1.2% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/Go 666.400 ns/op +14.3 ns/op / +2.2% (worse)
Windows MSVC ARM64 AfterFuncZeroDelivery/LLGo 141903 ns/op +4083 ns/op / +3.0% (worse)
Windows MSVC ARM64 CreateStop/Go 199 ns/op +1.2 ns/op / +0.6% (worse)
Windows MSVC ARM64 CreateStop/LLGo 381.400 ns/op -4.9 ns/op / -1.3% (better)
Windows MSVC ARM64 RearmStopped/Go 70.600 ns/op 0 ns/op / +0.0%
Windows MSVC ARM64 RearmStopped/LLGo 264.400 ns/op -8 ns/op / -2.9% (better)
Windows MSVC ARM64 ResetActive/Go 31.050 ns/op -0.03 ns/op / -0.1% (better)
Windows MSVC ARM64 ResetActive/LLGo 135.600 ns/op +1.6 ns/op / +1.2% (worse)
Windows MSVC ARM64 ResetHeap1024/Go 31.100 ns/op -0.05 ns/op / -0.2% (better)
Windows MSVC ARM64 ResetHeap1024/LLGo 136 ns/op -1.3 ns/op / -0.9% (better)

Compared with 2db247e43848 measured in the same runner job.

@zhouguangyuan0718 zhouguangyuan0718 changed the title feat: add source function contracts across ABI lowering feat: add function, parameter and result attributes Sep 12, 2026
@zhouguangyuan0718
zhouguangyuan0718 marked this pull request as draft September 14, 2026 08:07
@zhouguangyuan0718 zhouguangyuan0718 changed the title feat: add function, parameter and result attributes feat: add cold and noreturn function directives Sep 14, 2026
@zhouguangyuan0718 zhouguangyuan0718 changed the title feat: add cold and noreturn function directives refactor: keep existing function directives on declaration objects Sep 15, 2026
@zhouguangyuan0718
zhouguangyuan0718 force-pushed the codex/source-contracts-20260912 branch from 66ad7b6 to 1df08aa Compare September 16, 2026 10:28
@zhouguangyuan0718

Copy link
Copy Markdown
Collaborator Author

@fennoai please review the current PR head, 1df08aa, against main. The PR now only refactors existing function declaration metadata (env, wasmimport, nointerface, function linknames and exports); it no longer introduces source contract attributes. Please focus on correctness across preloaded and one-shot compilation, explicit package patches, generic origins, imported functions, and independent source declarations that share a linker symbol. Report reproducible regressions and missing tests without modifying code.

@fennoai

fennoai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

FennoAI is processing your request, please wait a moment...

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FennoAI review — PR #2572

Reviewed the head 1df08aa against main across preloaded and one-shot compilation, package patches, generic origins, imported functions, and independent source declarations that share a linker symbol. This is a well-structured refactor; the new FunctionDeclaration API is uniformly nil-safe and the concurrency model holds (shared declaration/packageSyntax state is mutated only during single-threaded syntax preparation under lock, while concurrent backends take read-only paths).

I traced the one candidate correctness regression — funcName skipping the p.prog.Linkname fallback when a declaration exists but reports no linkname — and confirmed it is safe: DeclareFunction adopts and deletes any pending side-table entry, and SetLinkname routes to the declaration whenever one exists, so a name's linkname lives in exactly one place. No blocking findings.

The remaining items are minor: one clarity nit, a few steady-state performance observations, and two doc-comment accuracy nits. Details inline.

Comment thread cl/import.go Outdated
source.declaration = p.functionDeclaration(fn)
}
declaration := source.declaration
v, linked := declaration.Linkname()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] funcName: dead-ordered nil handling reads as a latent nil-deref

declaration.Linkname() is evaluated before the if declaration == nil check that overwrites it. This is only correct because FunctionDeclaration.Linkname() has a nil-receiver guard returning ("", false). Verified safe (not a regression): when declaration != nil the skipped p.prog.Linkname(orgName) fallback is unreachable, because DeclareFunction adopts-and-deletes any pending side-table linkname and SetLinkname routes to the declaration when one exists — so the linkname lives in exactly one place. Still, reordering as if declaration != nil { ... } else { ... } would make intent clear and avoid relying on the callee's nil tolerance for control flow.

Comment thread cl/function.go
ret = &aFunction{Function: fn}
p.funcs[fn] = ret
}
if ret.declaration == nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] Negative functionDeclaration results are never cached

functionDeclaration(fn) returns nil for a large class of functions (synthetic wrappers, cgo intrinsics, imported funcs not in the tables). Because nil is indistinguishable from "not yet resolved", ret.declaration stays nil and every subsequent p.function(fn) re-runs the full resolution (fn.Origin(), fn.Syntax() assertion, astFuncName string building, and an RLock-guarded slice scan). funcName (import.go:772-773) repeats the same guard, so both recompute per visit during lowering. Consider a resolved bool field or a sentinel so a nil result is cached once.

Comment thread ssa/function_declaration.go Outdated

// FunctionDeclaration owns the source information for one function or method.
// Distinct declarations remain independent even when they link to one symbol.
// It contains no LLVM state and is shared read-only after syntax preparation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] FunctionDeclaration doc overstates "read-only after syntax preparation"

The doc says the object is "shared read-only after syntax preparation," but declarations are still mutated after that point: DeclareFunction back-fills pkg/fset/pos and adopts pending linkname/export, ReplaceFunctionDeclarations writes fn.replacement (via BindPackageFunctionDeclarations), and source.declaration is set lazily during lowering. The real invariant is "read-only once backends run concurrently" — the phrasing already used at cl/function.go:200-201. Consider aligning the type doc to that to avoid implying an immutability that doesn't hold between syntax prep and backend start.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One additional note that didn't fit inline in the prior review (title-length limit).

Comment thread ssa/locality.go
for name, target := range p.packageSyntax.linknames {
links[name] = strings.TrimPrefix(target, "go:")
}
for name, entries := range p.packageSyntax.functions {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] locality: scans all functions; name-keyed links may drop variants

Two related observations at this loop:

  1. Wider scan: these loops now iterate the whole packageSyntax.functions map (every declared function/method program-wide) instead of only the linknames entries as before. validateLocalities runs per package, so this becomes O(packages x all-functions); the body only cares about entries where fn.Linkname() is true, so most iterations are wasted on the common no-linkname case. Worth confirming acceptable for large builds.

  2. Name flattening: functions[name] can hold multiple declarations (original vs. patched variants), but links is keyed by the single name; if two entries under one key carried different linknames, the last one iterated wins and the others are dropped from the reachability graph. Same-name variants normally share a linkname (or the patched variant supersedes via Effective), so this is an edge case rather than a break — but it re-flattens a distinction the rest of the PR works to preserve.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant