8.6 KiB
Comgr Project Conventions
Conventions for working in amd/comgr/. Apply these to all changes
under this directory.
This file is the single source of truth for general Comgr project
conventions. The agent-tool wrappers (amd/comgr/CLAUDE.md,
.cursor/rules/comgr.mdc) point here so updates only need to be made
once.
For hotswap-subsystem-specific conventions (patch-pass authoring,
B0/A0 rewrite invariants, hotswap test driver), see
src/hotswap/HOTSWAP_CONVENTIONS.md.
1. Code reuse — Comgr first, LLVM second, custom never
Reuse existing Comgr APIs before writing new ones. Notable hits:
parseTargetIdentifier()(src/comgr.cpp:231) — parses an ISA string (amdgcn-amd-amdhsa--gfx1250:sramecc+) into arch / vendor / OS / environ / processor / features. Don't re-implement string parsing for ISA names.ensureLLVMInitialized()(src/comgr.cpp:275) — initializes the AMDGPU target stack with thread-safety. Don't roll your ownstd::call_onceoverLLVMInitializeAMDGPU*calls; reuse this.DisassemblyInfo::create()(src/comgr-disassembly.cpp:25) — sets up the full MC stack (Target, MRI, MAI, MCII, STI, MCContext, MCDisassembler, MCInstPrinter). If a new feature needs a similar bundle, refactorDisassemblyInfoto share, don't duplicate.- Small refactors of existing Comgr APIs are preferred over parallel implementations. If extracting a shared helper enables your reuse story, do that.
Reuse existing LLVM APIs second, especially the MC layer:
MCCodeEmitter::encodeInstructionfor instruction encoding.MCRegisterInfo::regsOverlapfor register overlap checks.llvm::AMDGPU::*fromllvm/TargetParser/TargetParser.hfor AMDGPU target queries (parseArchAMDGCN,getArchNameAMDGCN, etc.).llvm::object::ELFFile<>for ELF parsing — don't hand-roll section or symbol iteration.llvm/Support/Compiler.hmacros for portable attributes.
For upstream LLVM APIs that need small reworks to be usable here:
add a TODO comment in the Comgr code and file a GitHub issue to
fix upstream. Do not implement a parallel version inside Comgr.
2. Code quality
- Follow LLVM coding guidelines (
BasedOnStyle: LLVMin.clang-format). Runclang-formaton changed files before submitting. - Apply upstream LLVM code review standards (small focused commits, meaningful commit messages, no unrelated changes).
- Avoid Windows-hostile code:
- Use
LLVM_ATTRIBUTE_WEAK(fromllvm/Support/Compiler.h), not__attribute__((weak)). MSVC does not understand the GCC syntax and will fail to build. - No GCC/Clang-only attributes without an LLVM-portable wrapper.
- Use
- All assembly / disassembly goes through the MC layer (e.g.,
assembleSingleInst,assembleInstructions,parseAsmToMCInsts). No hardcoded instruction opcodes or encoded byte sequences — let the asm parser resolve them, and round-trip throughMCCodeEmitter::encodeInstructionfor any modification. - When invoking the asm parser, register the SourceMgr with
MCContextviaMCContext::initInlineSourceManager()so error diagnostics on bad input don't crash withEither SourceMgr should be available. - Mnemonic identity is asm-level, not tablegen-level.
MCInstrInfo::getName(Opcode)returns the tablegen pseudo name — on gfx1250 the assembledv_nophas opcode nameV_NOP_e32_gfx12, notV_NOP_e32. Resolve opcodes once at init via the asm parser, cache the resolved opcode /MCInst, and compare against the cached value. Verify any new mnemonic comparison withllvm-mc -show-instagainst the target you care about.
Style. Comgr follows the canonical LLVM coding conventions; for the full reference Read these source files (in this monorepo):
Comgr-specific deviations and items recurring in code review that aren't covered upstream:
- Avoid
auto. Comgr leans stricter than upstream LLVM here — spell types out, including iterator types. - ASCII only in source comments — no box-drawing dividers, no smart quotes, no em-dashes.
- Pass
MCRegister(notunsigned) until you need the encoded id.
Error-return policy. Don't mix signaling styles within one PR.
- Public C API:
amd_comgr_status_t. - Internal helpers:
boolonly when there is one meaningful failure mode; otherwisestd::optional<T>orllvm::Expected<T>. - No silent returns on failure. Every failure path emits a specific
log()message naming what was attempted and why it failed. - Don't return a count where 0 conflates "no candidates" with "found candidates but couldn't process them" — that distinction matters to downstream callers.
3. Testing
Comgr has three test suites:
test/— legacy CTest C-based tests.test-lit/— LIT integration tests withcomgr-sources/tool binaries.test-unit/— newer gtest-based unit tests.
Prefer LIT tests over gtests where the public API is reachable.
Compile a kernel with %clang, pipe through a LIT tool driver, verify
with %llvm-objdump / %llvm-readelf / %FileCheck.
- LIT inputs should be compiled with
%clangdirectly, not through Comgr actions (AMD_COMGR_ACTION_ASSEMBLE_SOURCE_TO_RELOCATABLEetc.). Going through Comgr actions implicitly tests the Comgr compiler pipeline alongside whatever the test is checking, which makes failures harder to attribute. - Reuse existing tools in
test-lit/comgr-sources/rather than adding parallel drivers. Extend an existing tool if needed (e.g., adding a new flag). Add a new tool only when an existing one is genuinely a bad fit. - When extending a shared driver, unknown-flag handling rejects with an error. Silent pass-through turns typoed RUN lines into false negatives.
- gtests in
test-unit/are appropriate for:- Pure functions with bit-level edge cases (e.g., encoding limits).
- Internal helpers not reachable through any public API path.
make check-comgr runs all three suites; the per-suite targets
(make test, make test-lit, make test-unit) run them individually.
Verify under AddressSanitizer before submitting. Comgr builds with
-DADDRESS_SANITIZER=On (see CMakeLists.txt). Re-run make check-comgr against the ASAN build to catch use-after-free, leaks,
and other memory bugs that won't surface in a normal release build.
4. PR workflow
One feature per PR. Split by file-of-truth — one new
comgr-*-X.cpp per PR. Refactors of pre-existing Comgr code go in
their own PR; don't bundle a refactor (e.g. of DisassemblyInfo) into
a feature PR.
Tests required. Each PR is accompanied by tests aiming for 100% code coverage of the change being added.
Keep the branch rebased. Resolve conflicts before requesting review.
Deferral protocol. When a reviewer asks for a structural change that would require touching code outside the PR scope:
- Acknowledge the direction.
- File a tracking issue and link it inline in the comment thread.
- State the trigger condition for picking it up.
Deferring without a tracker is the failure mode — the concern gets silently lost.
5. Working as an agent
These items address fingerprints that have surfaced in code review. Self-audit your diff before pushing.
Cite, don't claim. Don't make "verified empirically" or "I
confirmed" claims about LLVM internals (parser behavior, codegen
choices, ABI specifics) without a citation — a link to an MC test, a
TableGen .td, or actual llvm-mc output. If you can't produce one,
downgrade the claim to "would need to verify" and verify before
letting it become an architectural justification.
Self-audit for over-decomposition. Grep your diff for one-line
helpers that wrap a single call (a 4-byte memcpy wrapped as a named
function; a printInst with one caller). If the helper has one caller
and is one operation, inline it.
Self-audit for defensive checks against impossible conditions.
"Defensive against tablegen / decoder corruption" is not a justifiable
guard — if the disassembler is producing garbage, the patch can't
recover. REQUIRES: LIT gates that can never fail are dead code.
Delete checks that can't fire.
Self-audit for duplicated llvm/ content. Before adding a
constant, table, X-macro, or helper, grep
llvm/include/llvm/{Object,BinaryFormat,Support,MC}/ and
llvm/lib/Target/AMDGPU/Utils/ for the same content. If it exists
and is reachable, use it; if it exists but is target-internal, file a
tracking issue and add a TODO linking it.