Status: COMPLETE ✓ #
Problem #
In mulle-objc's MetaABI, method parameters are packed into a struct
passed as void *_param. The compiler synthesized _param->a
expressions for parameter references, which worked for compilation but
broke debugger inspection — print a failed, requiring print _param->a.
Solution Implemented #
Shadow VarDecls are created for each parameter field and injected as
DeclStmts at the start of the method body:
1// Generated at function entry:
2int a = _param->a;
3int b = _param->b;
4// User's body follows, 'a' and 'b' resolve to these locals
At -O2 the copies are eliminated entirely (2 instructions in assembly).
At -O0 -g they survive as named DWARF variables — debugger sees a and b.
What Changed #
Phase 1 — Shadow VarDecls (SemaDeclObjC.cpp) #
In ActOnStartOfObjCMethodDef, after pushing _param onto scope,
create an implicit VarDecl for each ParamRecord field with
initializer _param->field and push onto FnBodyScope.
Phase 2 — Remove old intercepts #
Three FindParamRecordField intercepts that synthesized _param->field
MemberExprs were removed (replaced with comment markers):
SemaDecl.cpp~919 (ClassifyName)SemaDecl.cpp~1317 (ActOnNameClassifiedAsNonType)SemaExpr.cpp~2798 (ActOnIdExpression)SemaExprObjC.cpp~5032 (LookupIvarInObjCMethod)
Normal scope lookup now finds the shadow VarDecls first.
Phase 3 — Inject DeclStmts into body (SemaDecl.cpp) #
In ActOnFinishFunctionBody, before MD->setBody(Body), prepend
DeclStmts for each shadow VarDecl to the CompoundStmt. This
causes codegen to emit them as IR allocas with llvm.dbg.declare,
making them visible to the debugger.
Phase 4 — Property setter codegen (CGObjC.cpp) #
The synthesized setter path bypassed sema and built MemberExprs
directly. Updated to look up the shadow VarDecl by name in the
method's DeclContext first, falling back to the old _param->field
path if not found.
Phase 5 — Verified #
-O2assembly: 2movslqinstructions, no shadow copies-O0 -gDWARF:"a"and"b"in string table asDW_TAG_variable
Files Modified #
All with @mulle-objc@ MetaABI shadow markers.
| File | Change |
|---|---|
clang/lib/Sema/SemaDeclObjC.cpp |
Create shadow VarDecls in ActOnStartOfObjCMethodDef; inject DeclStmts in ActOnFinishFunctionBody |
clang/lib/Sema/SemaDecl.cpp |
Remove FindParamRecordField intercepts (×2) |
clang/lib/Sema/SemaExpr.cpp |
Remove FindParamRecordField intercept |
clang/lib/Sema/SemaExprObjC.cpp |
Remove FindParamRecordField intercept |
clang/lib/CodeGen/CGObjC.cpp |
Use shadow VarDecl in setter codegen |
Gotcha: Parse-time vs Scope-time #
The body tokens are stashed during ParseObjCMethodDefinition and
re-parsed later in ParseLexedObjCMethodDefs. ActOnStartOfObjCMethodDef
runs just before the body is re-parsed, so shadows are on scope when
the body tokens are processed — name lookup finds them correctly.
However, the FindParamRecordField intercepts fired before normal
lookup returned, overriding the shadow. All intercepts needed to be
removed (not just guarded) to let normal lookup win.
Remaining FindParamRecordField Uses #
CGObjC.cpp ~2230 (setter) — kept as fallback, shadow lookup tried first.
SemaExprObjC.cpp ~2348 — checks if a name is a param field to avoid
ivar confusion; this check is still valid and was not removed.
Tests #
test-compiler/metaabi/shadow-locals-pass.m — multi-arg, verifies DeclRefExpr to shadow
test-compiler/metaabi/shadow-single-arg-pass.m — single void* param path (no struct)
test-compiler/metaabi/shadow-no-args-pass.m — no args, no shadow needed