POST-MORTEM: MetaABI Local Shadow Variables

· nat's blog


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):

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 #

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

last updated: