COMPILER.md — sun compile Pipeline & Cache Model¶
Living reference for the compilation subsystem (internal/compiler). Tier-1
authority remains docs/ARCHITECTURE.md §5.2; this file documents the concrete
implementation contracts that later sprints (S5 in particular) depend on.
Binary management (solcmgr.go)¶
- The solc-tron version is pinned in
sail.toml([solc] version); the binary lives at<SAIL_HOME>/bin/solc-<version>[.exe]. - Download source:
https://github.com/tronprotocol/solidity/releases/download/tv_<version>/<asset>with one asset per OS —solc-static-linux,solc-macos(universal x86_64+arm64),solc-windows.exe. TheSAIL_SOLC_URLenvironment variable overrides the full URL (mirrors, air-gapped setups, hermetic tests). - Checksum before execution, always. The host platform's SHA-256 digest
must be pinned under
[solc.checksums]keyed"<goos>/<goarch>". The digest is verified while streaming the download (a failing transfer is never installed) and re-verified on everyEnsureSolccall. No digest → no execution, with an instructive error. There are no exceptions. - Concurrent
sunprocesses are serialized by a lock file (solc-<version>.lock,O_CREATE|O_EXCL) with stale-lock recovery (10 min).
Invocation (pipeline.go)¶
Modern solc (Standard JSON, filesystem resolution):
solc --standard-json --base-path . --include-path lib, executed with the project root as working directory.sourcesreferences only the unit's root file viaurls; solc's Host Filesystem Loader resolves every transitive import. solc is the only Solidity parser in the system — no regular expressions, no import scanning anywhere ininternal/compiler.settings.remappingscomes fromsail.toml.outputSelection(the S5 contract — never trimmed):abi,metadata,evm.bytecode,evm.deployedBytecode.sourceMap.
Old solc-tron fallback (no --include-path):
- Capability is detected from the observed runtime error (the flag rejection on stderr), never from the version string, and memoized per process.
- Inline-retry loop: the root source is sent as literal
content; everySource "X" not founddiagnostic makes the pipeline readXfrom disk (project root, thenlib/) and retry with it added. Converges in O(import-graph depth), hard-capped at 256 iterations.
Compilation units & parallelism (compile.go)¶
- Roots: every
*.solundersrc/,test/, andscript/(Foundry parity). Files underlib/participate only as imports and are never roots. - Independent units run under
golang.org/x/sync/errgroupbounded byGOMAXPROCS. All artifact/depfile writes are atomic (temp file + rename) and each unit owns its own paths, so the cache is safe under concurrency. - The solc binary is resolved (and checksum-verified) only when at least one unit is stale — a fully cached run never touches solc at all.
Depfile & incremental cache (depgraph.go, cache.go)¶
Per-unit depfile at out/.cache/<target-with-__-separators>.json
(e.g. src__Counter.sol.json):
{
"solcVersion": "0.8.23",
"settingsHash": "<sha256 of the canonical settings JSON>",
"sources": { "src/Counter.sol": "<sha256>", "...": "..." },
"artifacts": [ "out/Counter.sol/Counter.json" ]
}
sourcesenumerates every participating file as reported by solc's output — the import graph is never derived by SunSail itself.- Cache hit (solc not executed) requires all of: same solc version, same settings hash, every recorded source hash intact, every recorded artifact present on disk. Anything else is a miss.
- Transitive invalidation is structural: an import's hash is recorded in the depfile of every unit that consumed it, so touching one file stales exactly its dependent closure — no graph walk needed. Recompiling refreshes the depfile.
- Deleting
out/removes artifacts and cache state together (full rebuild).
Artifacts (artifacts.go)¶
Layout: out/<File>.sol/<Contract>.json, one JSON per contract defined in
the unit's root file (each project file is its own unit, so every artifact has
exactly one writer; lib/ sources get no artifacts of their own):
{
"abi": [ ... ],
"bytecode": "6080...",
"deployedSourceMap": "...",
"metadata": "{...solc metadata JSON string...}"
}
bytecodeis the creation bytecode object.deployedSourceMapis the source map of the deployed bytecode — this is the exact fieldrunner/trace.goconsumes in S5.
Testing¶
make check/make e2eare hermetic: unit tests inject an in-process fake Runner; the e2e harness builds a deterministic solc stub (test/e2e/testdata/solcstub) that emulates modern and legacy solc, logs every invocation, and is installed pre-verified under a tempSAIL_HOME(withSAIL_SOLC_URLpointed at an unroutable guard address).- The real download + real solc compile of the scaffolded project runs under
//go:build differential(make differential).