Summary
LLVM emission fails when a pointer-valued PHI walks a relocation-backed pointer table stored in writable __DATA by a fixed record stride. The PHI is initialized from a local-symbol relocation-backed table base and carries the recurrence p_next = p + 0x8.
The current failure is:
error: med_llvm_emitter: ambiguous reachable read-only table-base PHI X19.2 in _main; refusing stale-address fallback
The fixture proves that this pointer-induction address form fails while the same table and loop succeed with invariant-base plus scalar-index addressing. It does not by itself prove that tryResolveWritableData() is the sole missing owner: load dispatch tries writable-data resolution, generic pointer-table resolution, and only then the fail-closed read-only audit.
This is separate from the existing read-only relocated pointer-table induction issue. Here the pointer slots are in writable __DATA; the control's generated IR uses a pointer-table mirror, while the failing PHI is rejected before any specialized pointer-table owner produces a result. Exact rejection ownership needs resolver-level tracing.
Source location
lib/backend/llvm/emit/MedLLVMOpEmitter.cpp:679-697: load resolution tries writable-data and code-pointer-table ownership before tryResolveReadOnlyDataPtr().
lib/backend/llvm/data/MedLLVMWritableData.cpp:50-191,193-537: writableDataSegOf() and tryResolveWritableData() discover writable segments and emit mutable-data mirrors; exact rejection reason for this pointer-table PHI is not established by the fixture alone.
lib/backend/llvm/resolve/MedLLVMCodePtrResolve.cpp:285-476,478-837: ptrTableUniqueSegment() and tryResolveCodePtrTablePtr() inspect DataPtrRelocSlots; proveAddressRole() validates pointer-table versus scalar roles and address models.
lib/backend/llvm/data/MedLLVMSymbolizedPtr.cpp:265-287: read-only data-pointer dispatch receives the address after writable-data and pointer-table resolution return no pointer.
lib/backend/llvm/data/MedLLVMLiteralTable.cpp:279-1383: generic select-merge audit; its PHI walker is at 939-996, and fail-closed fallback is at 1255-1302.
lib/backend/llvm/data/MedLLVMIndexedGlobal.cpp:29-897,916-1042: induction and indexed-global resolvers handle read-only table bases after dispatch arbitration.
lib/backend/llvm/resolve/MedLLVMAddrResolve.cpp:658-967,1129-1136,1237-1381,1554-1837: shared recurrence/PHI diagnostic, relocation-backed target recovery, and indexed-base provenance helpers.
Minimal reproducer
Verified relocation-only AArch64 assembly fixture:
.section __TEXT,__text,regular,pure_instructions
.globl _main
.p2align 2
_main:
adrp x19, _table@PAGE
add x19, x19, _table@PAGEOFF
mov w20, #0
mov w21, #0
Lloop:
ldr x8, [x19]
ldr w0, [x8]
add w21, w21, w0
add x19, x19, #8
add w20, w20, #1
cmp w20, #2
b.lo Lloop
mov w0, w21
ret
.section __DATA,__data
.p2align 3
_table:
.quad _entry0
.quad _entry1
.section __TEXT,__const
.p2align 2
_entry0:
.long 3
_entry1:
.long 4
Fixture source: /tmp/neverd-macho-writable-induction-ptrtable.s. It uses only local _table, _entry0, and _entry1 symbol relocations. The pointer table is explicitly in writable __DATA, not __DATA_CONST. The source contains no fixed virtual address, hardcoded image-layout offset, or IR rewrite.
The relevant MedIR shape is:
COPY X19.1 0x100004000 ; relocated writable __DATA table base
PHI X19.2 = ... ; pointer-valued induction PHI
COPY t1.2 X19.2
LOAD t2.2 X19.2 ; load relocated table entry
LOAD t2.3 t2.2 ; dereference local entry
COPY t2.4 0x8
INT_ADD X19.3 X19.2 0x8 ; fixed-stride backedge
Reproduction
xcrun clang -arch arm64 /tmp/neverd-macho-writable-induction-ptrtable.s \
-o /tmp/neverd-macho-writable-induction-ptrtable.arm64
for i in 1 2 3 4 5; do
/usr/bin/perl -e 'alarm 2; exec @ARGV' \
/tmp/neverd-macho-writable-induction-ptrtable.arm64 >/dev/null 2>&1
echo "native[$i]=$?"
done
neverd lift --dump-med \
/tmp/neverd-macho-writable-induction-ptrtable.arm64 \
> /tmp/neverd-macho-writable-induction-ptrtable.med
neverd lift --no-opt \
/tmp/neverd-macho-writable-induction-ptrtable.arm64 \
-o /tmp/neverd-macho-writable-induction-ptrtable.ll
Observed result:
native[1]=7
native[2]=7
native[3]=7
native[4]=7
native[5]=7
dump-med: status 0
lift: status 1
error: med_llvm_emitter: ambiguous reachable read-only table-base PHI X19.2 in _main; refusing stale-address fallback
LLVM IR: not generated
The fixture object contains only _table page/page-offset relocations and _entry0/_entry1 UNSIGND data relocations. No imported symbol is needed for the reproducer.
Isolation control
Use the same writable pointer table and the same two-iteration loop, but keep the table base invariant and carry the scalar index instead:
adrp x19, _table@PAGE
add x19, x19, _table@PAGEOFF
mov w20, #0
mov w21, #0
Lloop:
ldr x8, [x19, x20, lsl #3]
ldr w0, [x8]
add w21, w21, w0
add w20, w20, #1
cmp w20, #2
b.lo Lloop
mov w0, w21
Control source: /tmp/neverd-macho-writable-induction-ptrtable-index-control.s. It keeps the same _table/_entry0/_entry1 relocations, writable section, table contents, loop count, and data-dependent native result. Only pointer induction changes to invariant-base plus scalar-index addressing.
Observed control result:
native: 7 7 7 7 7
dump-med: status 0
lift: status 0
canonical LLVM compile: status 0
Darwin relink: status 0
rebuilt normal-ASLR runtime: 7 7 7 7 7
This isolates the trigger to the writable-segment pointer-induction address form, while preserving pointer-table relocation, distinct loaded values, and indexed access in the control. The control's generated LLVM uses @__nd_codeptr_100004000 and %cptptr, showing that the accepted path is the generic code/data pointer-table mirror, not necessarily embedWritableRun().
Validation artifact: /tmp/neverd-macho-writable-induction-ptrtable-index-control.ll; the mirror global is at line 5 and the %cptptr access is at line 31.
Expected behavior
A proven relocation-backed pointer table in a writable segment, accessed through a stable fixed-stride pointer PHI, should be claimed by the generic code/data pointer-table mirror or by an explicit writable owner when writes are reachable. The recurrence proof must preserve pointer-slot segment identity and relocation target model instead of falling into the read-only fallback.
The implementation must distinguish this from an ordinary writable scalar value. It must continue rejecting unknown pointer bases, mixed raw and symbolized pointer arms, distinct writable segments, unresolved pointer relocations, narrowing conversions, and unproved stride expressions.
Actual behavior
Dispatch tries tryResolveWritableData(), then tryResolveCodePtrTablePtr(), and only then tryResolveReadOnlyDataPtr(). The failing fixture reaches the generic read-only audit and emits the PHI diagnostic after the specialized paths decline the address. Current evidence does not identify whether the decisive rejection is writable-segment discovery, pointer-table role proof, recurrent-PHI handling, or relocation-model classification.
The control's generated LLVM contains @__nd_codeptr_100004000 and %cptptr, proving that the accepted invariant-base form is handled by the generic code/data pointer-table mirror. A resolver-level trace is still needed to identify why the equivalent pointer-induction form does not reach that owner. The failure is fail-closed, so no LLVM IR is generated even though native execution is data-dependent and valid.
Summary
LLVM emission fails when a pointer-valued PHI walks a relocation-backed pointer table stored in writable
__DATAby a fixed record stride. The PHI is initialized from a local-symbol relocation-backed table base and carries the recurrencep_next = p + 0x8.The current failure is:
The fixture proves that this pointer-induction address form fails while the same table and loop succeed with invariant-base plus scalar-index addressing. It does not by itself prove that
tryResolveWritableData()is the sole missing owner: load dispatch tries writable-data resolution, generic pointer-table resolution, and only then the fail-closed read-only audit.This is separate from the existing read-only relocated pointer-table induction issue. Here the pointer slots are in writable
__DATA; the control's generated IR uses a pointer-table mirror, while the failing PHI is rejected before any specialized pointer-table owner produces a result. Exact rejection ownership needs resolver-level tracing.Source location
lib/backend/llvm/emit/MedLLVMOpEmitter.cpp:679-697: load resolution tries writable-data and code-pointer-table ownership beforetryResolveReadOnlyDataPtr().lib/backend/llvm/data/MedLLVMWritableData.cpp:50-191,193-537:writableDataSegOf()andtryResolveWritableData()discover writable segments and emit mutable-data mirrors; exact rejection reason for this pointer-table PHI is not established by the fixture alone.lib/backend/llvm/resolve/MedLLVMCodePtrResolve.cpp:285-476,478-837:ptrTableUniqueSegment()andtryResolveCodePtrTablePtr()inspectDataPtrRelocSlots;proveAddressRole()validates pointer-table versus scalar roles and address models.lib/backend/llvm/data/MedLLVMSymbolizedPtr.cpp:265-287: read-only data-pointer dispatch receives the address after writable-data and pointer-table resolution return no pointer.lib/backend/llvm/data/MedLLVMLiteralTable.cpp:279-1383: generic select-merge audit; its PHI walker is at939-996, and fail-closed fallback is at1255-1302.lib/backend/llvm/data/MedLLVMIndexedGlobal.cpp:29-897,916-1042: induction and indexed-global resolvers handle read-only table bases after dispatch arbitration.lib/backend/llvm/resolve/MedLLVMAddrResolve.cpp:658-967,1129-1136,1237-1381,1554-1837: shared recurrence/PHI diagnostic, relocation-backed target recovery, and indexed-base provenance helpers.Minimal reproducer
Verified relocation-only AArch64 assembly fixture:
Fixture source:
/tmp/neverd-macho-writable-induction-ptrtable.s. It uses only local_table,_entry0, and_entry1symbol relocations. The pointer table is explicitly in writable__DATA, not__DATA_CONST. The source contains no fixed virtual address, hardcoded image-layout offset, or IR rewrite.The relevant MedIR shape is:
Reproduction
Observed result:
The fixture object contains only
_tablepage/page-offset relocations and_entry0/_entry1UNSIGNDdata relocations. No imported symbol is needed for the reproducer.Isolation control
Use the same writable pointer table and the same two-iteration loop, but keep the table base invariant and carry the scalar index instead:
Control source:
/tmp/neverd-macho-writable-induction-ptrtable-index-control.s. It keeps the same_table/_entry0/_entry1relocations, writable section, table contents, loop count, and data-dependent native result. Only pointer induction changes to invariant-base plus scalar-index addressing.Observed control result:
This isolates the trigger to the writable-segment pointer-induction address form, while preserving pointer-table relocation, distinct loaded values, and indexed access in the control. The control's generated LLVM uses
@__nd_codeptr_100004000and%cptptr, showing that the accepted path is the generic code/data pointer-table mirror, not necessarilyembedWritableRun().Validation artifact:
/tmp/neverd-macho-writable-induction-ptrtable-index-control.ll; the mirror global is at line5and the%cptptraccess is at line31.Expected behavior
A proven relocation-backed pointer table in a writable segment, accessed through a stable fixed-stride pointer PHI, should be claimed by the generic code/data pointer-table mirror or by an explicit writable owner when writes are reachable. The recurrence proof must preserve pointer-slot segment identity and relocation target model instead of falling into the read-only fallback.
The implementation must distinguish this from an ordinary writable scalar value. It must continue rejecting unknown pointer bases, mixed raw and symbolized pointer arms, distinct writable segments, unresolved pointer relocations, narrowing conversions, and unproved stride expressions.
Actual behavior
Dispatch tries
tryResolveWritableData(), thentryResolveCodePtrTablePtr(), and only thentryResolveReadOnlyDataPtr(). The failing fixture reaches the generic read-only audit and emits the PHI diagnostic after the specialized paths decline the address. Current evidence does not identify whether the decisive rejection is writable-segment discovery, pointer-table role proof, recurrent-PHI handling, or relocation-model classification.The control's generated LLVM contains
@__nd_codeptr_100004000and%cptptr, proving that the accepted invariant-base form is handled by the generic code/data pointer-table mirror. A resolver-level trace is still needed to identify why the equivalent pointer-induction form does not reach that owner. The failure is fail-closed, so no LLVM IR is generated even though native execution is data-dependent and valid.