Skip to content

Conversation

vickiegpt
Copy link

High-Level Goals

  1. Variadic & indirect call lowering: Ensure every cir.call / indirect callable goes out as a legal llvm.call (or llvm.invoke later) with a fully converted LLVM function type (including varargs).
  2. Aggregate (record/array) constant lowering: Produce nested llvm.constant forms (struct literals, array literals) from CIR constants and global initializers.
  3. Eliminate lingering !cir.ptr in the post-conversion IR: No CIR pointer types should survive the CIR→MLIR lowering boundary; all must be llvm.ptr (or rewritten through memref paths if you keep that dual-mode strategy).

Recommended Phased Approach

Phase 0: Instrument & Guard (Short)

  • Add a verifier (or cheap post-pass assertion walker) that scans after conversion: fail fast if any !cir.ptr remains or if any cir.* op other than an explicitly whitelisted late op exists.
  • Gate new features behind a debug option (e.g. pass option --cir-lower-allow-varargs) while iterating.

Phase 1: Type System & Pointer Foundations

  • Extend the existing prepareTypeConverter():
    • Ensure all cir::PointerType (including function pointers) map deterministically to either:
      • llvm.ptr to properly typed LLVM function type or struct element, OR
      • (If absolutely needed) opaque llvm.ptr plus later bitcast to concrete pointer types (avoid if you can—untyped pointers make variadic lowering harder).
    • For cir::FuncType (if surfaced), add a conversion to mlir::LLVM::LLVMFunctionType.
    • For records: preserve packing, optionally name struct types if original AST name is available (avoid name collisions—apply a prefix, e.g. cir.struct.$mangled).
  • Introduce argument/result materializations (TypeConverter hooks):
    • Argument materialization: if a remnant CIR pointer sneaks through, wrap via unrealized_conversion_cast to allow staged replacement.
    • Target materialization: reject (emit error) for pointer flows that reach the boundary unexpectedly—forces earlier patterns to handle them.

Phase 2: Variadic Function Declaration & Call Lowering

  • Update CIRFuncOpLowering:
    • If fnType.isVarArg() create llvm.func with isVarArg = true.
    • Preserve linkage/visibility, add attributes (noreturn, alwaysinline, etc.) if already captured.
  • Update CIRCallOpLowering:
    • Classify call kind: direct vs indirect.
    • For direct varargs calls: split fixed vs variadic operands; ensure all variadic operands are already converted (insert necessary integer/float extensions per target ABI later—initially just pass through).
    • For indirect calls:
      • Ensure callee operand is (or is castable to) llvm.ptr to an LLVMFunctionType.
      • If currently opaque pointer: emit llvm.bitcast to concrete function pointer type before llvm.call.
  • Fallback diagnostics:
    • If argument type cannot be converted: emit op.emitRemark("vararg arg type unsupported; passing as i8* shell") and bitcast a pointer, not abort.
    • (Optional) Introduce a stats counter to quantify un-lowered adaptations.

Phase 3: Indirect Call Metadata & Attributes

  • Add support for propagating side-effect metadata (readonly, nounwind, willreturn) to llvm.call.
  • Defer exception handling (invoke) if not already present; keep simple llvm.call now.

Phase 4: Aggregate Constant Lowering

  • Introduce CIRAggConstantLowering pattern:
    • Handle cir.constant whose type is:
      • Record: recursively lower member constants.
      • Array: fold element list into ArrayAttr of lowered element types.
    • Emit a single mlir::LLVM::ConstantOp with the fully materialized LLVMStructType or LLVMArrayType.
    • For globals (cir.global initializers): replace initializer region body with a single llvm.mlir.global initializer value if fully constant.
  • Edge cases:
    • Empty struct: emit struct literal with zero elements or collapse to i8 0 if target disallows truly empty (depends on LLVM struct support).
    • Packed struct: ensure LLVMStructType::getLiteral(..., /isPacked/true).
    • Pointer fields: emit llvm.null (constant zero pointer).
    • Nested arrays of structs: depth-first recursion.
  • Introduce a small helper:
  • Degrade unsupported field kinds (e.g. unions if not yet modeled) with a per-field undef and emitRemark.

Phase 5: Final Pointer Purge

  • Add a pass (or extend the existing conversion driver):
    • Walk all ops after pattern application: if any operand/result has cir::PointerType, emit error (or perform last-resort conversion producing llvm.ptr and a remark).
    • Strip any residual unrealized_conversion_cast bridging pointer types (your earlier cleanup pattern for ptr already partially does this—extend it to cover multi-use benign cases).
  • Strengthen CIRPointerMaterializationCleanup:
    • Support multi-result casts (if appear after future expansions).
    • Track statistics (# cleaned, # skipped) when debug enabled.

Phase 6: Testing & Validation

Add targeted lit tests:

  1. varargs-basic.cir: direct call to printf-like with mixed int/double/ptr.
  2. indirect-call.cir: take address of function, call through pointer.
  3. record-constant.cir: nested struct + array initializer lowered into single llvm.constant.
  4. struct-copy.cir: ensure cir.copy -> llvm.memcpy with expected size.
  5. packed-struct.cir: verify packed flag preserved (layout difference).
  6. leftover-ptr-fail.cir: intentionally leave a cir.ptr and assert failure remark.

Each test: run cir-opt --cir-lower-to-mlir (or the pass pipeline) and FileCheck expected textual IR.

Phase 7 (Optional / Later)

  • ABI-correct integer/float promotions for varargs (align with target triple).
  • Exception semantics: model invokes for must-throw contexts.
  • Function pointer signature canonicalization (deduplicate bitcasts).
  • Support unions (flatten via maximum-sized member + reinterpret cast).

Key Helpers & Skeleton Snippets

Type Converter Materializations

Call Lowering (Indirect Case Excerpt)

Aggregate Constant Builder (Pseudo)

(Real code must respect MLIR’s expected attribute kinds: use AggregateAttr / ArrayAttr / numeric attrs; then feed to mlir::LLVM::ConstantOp.)


Edge Cases & Risks

Area Edge Case Mitigation
Variadic calls No variadic args but vararg prototype Still mark isVarArg; passes fine
Indirect calls Opaque ptr callee with missing prototype Emit bitcast + remark, treat as vararg fallback
Record constants Self-referential (recursive) types Detect recursion; emit undef + remark to avoid infinite expansion
Packed structs Alignment 1 semantics Ensure layout queries treat packed = 1 alignment
Copy lowering Scalable vector member Reject with remark until scalable size logic added
Leftover cir.ptr Hidden inside unused dead code Run DCE before pointer purge or ignore block if unreachable

Incremental Landing Order (Suggested)

  1. Strengthen type conversion + materializations.
  2. Direct + variadic call lowering.
  3. Indirect call lowering.
  4. CopyOp already added—extend tests.
  5. Aggregate constant lowering.
  6. Pointer purge verifier & cleanup pattern enhancements.
  7. Tests + docs + enable by default.
  8. ABI promotions & refinements.

Success Criteria (Definition of Done)

  • No !cir.ptr types in final IR after the conversion pass (verified by a checker).
  • All cir.call / cir.func replaced by LLVM or standard dialect equivalents.
  • At least one test each for: variadic, indirect, aggregate constant, packed struct, copy, leftover-ptr failure.
  • No unresolved UnrealizedConversionCastOp involving pointer types post-conversion.
  • Aggregate constants stable (no crash) for nested struct+array patterns.

Suggested Follow-Up Quality Extras

  • Add pass option --cir-lower-report-stats printing counts of: ptr bridges created/pruned, calls lowered (direct/indirect/vararg), aggregates materialized, fallbacks.
  • Introduce a “strict” mode that fails instead of remarking on fallbacks once coverage stabilizes.
  • Benchmark compile-time impact (pointer bridging removal could speed later canonicalization).

@bcardosolopes
Copy link
Member

Thanks for all the work, seems AI generated and I'm a bit unsure of several parts where "Fallback" goes in the lines of "let's continue otherwise it crashes". If you are willing to chop this up into incremental bits along side testcases, I'm happy to review, the current length and likely AI mistakes seems like a review time drag here :(

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.

2 participants