This document contains analysis comments for key functions within the AttributeGraph framework, based on a macOS binary with image base 0x1B16F2000 as reported by IDA Pro. Offsets are relative to this base.
Mangled Name: __ZN2AG16LayoutDescriptor7CompareclEPKhS3_S3_mmj
Relative Offset: 0x1C2C8
Overall Purpose:
Compares two data buffers based on a layout command stream. This is the core comparison engine.
Important Note: This function expects layout_opcode_stream_ptr (a2) to be a valid, dereferenceable pointer to an opcode stream. It does not internally handle special sentinel values like nullptr or ValueLayoutTrivial ((const unsigned char*)1). If called with such values, it will crash when it attempts to read the first opcode from the stream (e.g., attempting to dereference address 0x0 or 0x1). The responsibility for handling nil or trivial layouts (e.g., by falling back to a bitwise comparison) lies with higher-level calling functions (like the static AG::LayoutDescriptor::compare).
Parameters (from original decompilation, names might vary):
a1 (this_ptr):AG::LayoutDescriptor::Compare*- Thethispointer for the Compare object instance. Contains state like the enum processing stack.a2 (layout_opcode_stream_ptr):const char*- Pointer to the current position in the layout command stream (opcodes).a3 (lhs_data_ptr_base):const unsigned char*- Base pointer to the first data buffer (LHS).a4 (rhs_data_ptr_base):const unsigned char*- Base pointer to the second data buffer (RHS).a5 (current_data_offset_val):uint64_t- Initial offset within the data buffers from which to start comparison for this call.a6 (data_length_from_offset_val):int64_t- Length of data to compare frominitial_data_offset, or -1 for unbounded (process until end of layout or data).a7 (comparison_flags):uint32_t- Flags controlling comparison behavior (e.g., report failures, copy-on-write for enums).
Local Variable Aliases / Key Stack Variables (from decompilation):
v91 / rhs_data_base_cached: Storesa4.v92 / lhs_data_base_cached: Storesa3.v79 / this_ptr_cached1: Storesa1.v89[1] / this_ptr_cached2_on_stack: Also storesa1.v80 / initial_enum_stack_depth: Loaded fromthis_ptr + 0x208.v9 / current_data_offset: Running offset withinlhs_data_ptr_baseandrhs_data_ptr_base, initialized froma5.v90 / data_offset_limit_cached: Calculated froma5 + a6, upper bound forcurrent_data_offset.v89[0] / processed_flags:comparison_flagswith MSB cleared. MSB likely controls failure reporting.v85 / use_copied_payloads_for_enum: Boolean, true if(comparison_flags & 0x100) == 0. Controls enum payload copying.v7 / current_opcode_or_status: Holds the current opcode byte or the function's return status (1 for equal, 0 for unequal).
Initial Setup (around 0x1C2C8 + entry_point_offset to 0x1C2C8 + ~0x7D):
- Caches parameters
a3(lhs_data) anda4(rhs_data) into local stack variables (v92,v91). - Caches
thispointer (a1) intov79andv89[1]. - Loads
this->enum_stack_depth_member(offset0x208) intov80. - Calculates
data_offset_limit(v90) froma5 + a6. Ifa6is -1, limit is effectively unbounded. - Initializes
return_status_flag(v7) to1(true/equal). - If
data_offset_limit <= a5(no data to compare), jumps to final cleanup. - Initializes
current_data_offset(v9) witha5. - Sets
processed_flags(v89[0]) froma7 & 0x7FFFFFFF. - Sets
use_copied_payloads_for_enum(v85) based on(a7 & 0x100) == 0.
Main Loop (starts around 0x1C2C8 + ~0x7D):
- Continuously processes opcodes from
layout_opcode_stream_ptr(a2). current_opcode_or_status(v7) is loaded with the current byte from the stream.
Opcode Handling - Simple Byte Skips (opcodes 0x40-0x7F):
- (Around
0x1C2C8 + ~0x8Dto0x1C2C8 + ~0xA3) - If opcode is in range [0x40, 0x7F]:
skip_count = opcode & 0x3F.current_data_offset += (skip_count + 1).layout_opcode_stream_ptradvances by 1.- If
current_data_offsetexceedsdata_offset_limit, comparison ends (returns true). - Otherwise, loop continues.
- If opcode not in this range, breaks to the main switch statement.
Main Switch Statement (jump table at 0x1C2C8 + ~0xC1):
-
Opcode 0 (End of Layout):
- (Branch from switch to
0x1C2C8 + ~0x603) - Sets
return_status_flagto1(true). - Jumps to final cleanup.
- Comment: Signifies successful comparison up to the end of the layout stream.
- (Branch from switch to
-
Opcode 1 (Equals - Swift Equatable):
- (Starts around
0x1C2C8 + ~0x393) - Reads
swift::metadata* type_metadataandswift::equatable_witness_table* witness_tablefrom the layout stream (next 16 bytes). - Calculates
item_size = type_metadata->vw_size(). next_data_offset = current_data_offset + item_size.- If
next_data_offset <= data_offset_limit(enough data):- Calls
_AGDispatchEquatable(lhs_data_at_offset, rhs_data_at_offset, type_metadata, witness_table). (Offset0x35084) - If false, calls
failed()and setsreturn_status_flag = 0.
- Calls
- Else (not enough data):
- Calls
AG::LayoutDescriptor::compare_bytes(lhs_data_at_offset, rhs_data_at_offset, remaining_data_length, &mismatch_offset, nullptr). (Offset0x1BBA4) - If false, calls
failed()and setsreturn_status_flag = 0.
- Calls
- Advances
layout_opcode_stream_ptrby 17. Advancescurrent_data_offset. - Comment: Compares Swift Equatable types. Uses witness table if full data available, otherwise byte-compares remainder.
- (Starts around
-
Opcode 2 (Indirect):
- (Starts around
0x1C2C8 + ~0x350) - Reads
swift::metadata* target_type_metadataandValueLayout* layout_cache_in_stream_valfrom stream. container_metadatais derived from the parent enum on theCompareobject's internal enum stack (e.g.,this_ptr->enum_stack[depth-1].type).field_size = container_metadata->vw_size().next_data_offset = current_data_offset + field_size.- Calls
AG::LayoutDescriptor::compare_indirect(&layout_cache_in_stream_val, container_metadata, target_type_metadata, processed_flags, lhs_data_at_offset, rhs_data_at_offset). (Offset0x1BC40) - The (potentially updated)
layout_cache_in_stream_valis written back to the layout stream. - If false, calls
failed()and setsreturn_status_flag = 0. - Advances
layout_opcode_stream_ptrandcurrent_data_offset. - Comment: Compares indirect types. Resolves indirection using container metadata, then compares target data using target metadata and its layout (possibly cached in stream).
- (Starts around
-
Opcode 3 (Existential):
- (Starts around
0x1C2C8 + ~0x2E2) - Reads
swift::existential_type_metadata* existential_metafrom stream. item_size = existential_meta->vw_size().next_data_offset = current_data_offset + item_size.- Calls
AG::LayoutDescriptor::compare_existential_values(existential_meta, lhs_data_at_offset, rhs_data_at_offset, processed_flags). (Offset0x1BED8) - If false, calls
failed()and setsreturn_status_flag = 0. - Advances
layout_opcode_stream_ptrandcurrent_data_offset. - Comment: Compares Swift existential types (e.g.,
any P). Projects contained values and compares them using their dynamic types.
- (Starts around
-
Opcodes 4, 5 (HeapRef, Function/WeakRef):
- (Starts around
0x1C2C8 + ~0x57D) - Reads
lhs_object_ptrandrhs_object_ptrfrom data buffers (each 8 bytes). - If pointers differ, calls
AG::LayoutDescriptor::compare_heap_objects(lhs_object_ptr, rhs_object_ptr, processed_flags, is_opcode_5_or_function_flag). (Offset0x1CBA0) - If false, calls
failed()and setsreturn_status_flag = 0. - Advances
current_data_offsetby 8.layout_opcode_stream_ptradvances by 1. - Comment: Compares Swift heap-allocated objects (classes, functions). Opcode 5 might denote special handling for weak/unowned or functions.
- (Starts around
-
Opcode 6 (Nested Layout - Varint Length):
- (Starts around
0x1C2C8 + ~0x1C5) - Reads
nested_layout_stream_ptr(absolute pointer) from current stream. - Reads
nested_layout_length(ULEB128) from current stream. - Calculates
comparison_length = min(nested_layout_length, remaining_data_in_buffers). - Recursively calls
this->operator()(nested_layout_stream_ptr, lhs_data_base_cached, rhs_data_base_cached, current_data_offset, comparison_length, processed_flags). - If false, sets
return_status_flag = 0. - Advances
layout_opcode_stream_ptr(past pointer and ULEB128). Advancescurrent_data_offsetbycomparison_length. - Comment: Processes a nested layout. The nested layout stream itself is pointed to from the current stream.
- (Starts around
-
Opcode 7 (Compact Nested Layout - Fixed Offset/Length):
- (Starts around
0x1C2C8 + ~0x589) - Reads
fixed_offset(int32) andfixed_length(uint16) from current stream. nested_layout_stream_ptr = AG::LayoutDescriptor::base_address + fixed_offset.- Calculates
comparison_length = min(fixed_length, remaining_data_in_buffers). - Recursively calls
this->operator()(nested_layout_stream_ptr, ...). - If false, sets
return_status_flag = 0. - Advances
layout_opcode_stream_ptr(by 7 bytes). Advancescurrent_data_offsetbycomparison_length. - Comment: Processes a nested layout. The nested layout stream is at a fixed offset from
AG::LayoutDescriptor::base_address.
- (Starts around
-
Opcode 8 (Enum Tag - ULEB128):
- (Starts around
0x1C2C8 + ~0x571) - Reads
enum_tag_value(ULEB128) from stream. This becomescurrent_opcode_or_status. - Jumps to common enum processing logic (
LABEL_23at0x1C2C8 + ~0x554). - Comment: Start of an enum case block, tag read as ULEB128.
- (Starts around
-
Opcodes 9, 10, 11 (Enum Start - Fixed Tags 0, 1, 2):
- (Starts around
0x1C2C8 + ~0x52A) current_opcode_or_statusis adjusted (opcode - 9) to be 0, 1, or 2 (internalEnum::Mode).- (Common Enum Logic at
LABEL_23,0x1C2C8 + ~0x554):- Reads
swift::metadata* enum_type_metadatafrom stream. - Gets
get_enum_tag_funcfromenum_type_metadata->VWT. - Calls
get_enum_tag_funcfor both LHS and RHS data atcurrent_data_offsetto getlhs_enum_tag,rhs_enum_tag. - If tags differ, calls
failed()and setsreturn_status_flag = 0. - If tags match:
- Prepares payload buffers: if
use_copied_payloads_for_enumis true, allocates temporary buffers (stack or heap based on size) and setsneeds_free_flag. Otherwise, uses original data pointers. - Constructs
AG::LayoutDescriptor::Compare::Enumobject onthis_ptr's internal enum stack. (Constructor at0x1BFD4)- Stores:
enum_type_metadata,use_copied_payloads_for_enum(as mode),rhs_enum_tag,current_data_offset, original LHS/RHS data pointers, payload buffer pointers,needs_free_flag. - If copying, calls VWT
initializeBufferWithCopyOfBufferanddestructiveProjectEnumData.
- Stores:
- Increments
this_ptr->enum_stack_depth_member. - Adjusts
lhs_data_base_cachedandrhs_data_base_cachedto point to the (potentially copied and projected) enum payloads for subsequent opcodes within this enum case. current_data_offsetis effectively reset to 0 relative to these new base payload pointers.
- Prepares payload buffers: if
- Advances
layout_opcode_stream_ptr.
- Reads
- Comment: Start of an enum case block. Compares enum tags. If match, pushes context onto enum stack and adjusts data pointers to enum payload.
- (Starts around
-
Opcode 12 (Enum Continue Tag - ULEB128):
- (Starts around
0x1C2C8 + ~0x2A5) - Reads
enum_tag_value(ULEB128) from stream. This becomescurrent_opcode_or_status. - Jumps to common enum processing logic (
LABEL_53at0x1C2C8 + ~0x28A). - Comment: Subsequent enum case, tag read as ULEB128.
- (Starts around
-
Opcodes 13-21 (Enum Continue - Fixed Tags 0-8):
- (Starts around
0x1C2C8 + ~0x533) current_opcode_or_statusis adjusted (opcode - 13) to be 0-8.- (Common Enum Logic at
LABEL_54,0x1C2C8 + ~0x280):- Retrieves top
Enumobject fromthis_ptr's internal enum stack. - If
current_opcode_or_status(adjusted tag from current layout opcode) does not match theenum_tagstored in the topEnumobject (which was the tag of the actual data), then it skips subsequent layout opcodes until the next enum-related opcode (another continue, or end).
- Retrieves top
- Comment: Subsequent enum case. If this case's tag doesn't match data's tag, skip its layout.
- (Starts around
-
Opcode 22 (Enum End):
- (Starts around
0x1C2C8 + ~0x1F3) - Pops an
Enumobject fromthis_ptr's internal enum stack. - Restores
lhs_data_base_cachedandrhs_data_base_cachedfrom the poppedEnumobject (if payloads were copied). current_data_offsetis updated topopped_enum_object.original_offset + enum_type_size.- Decrements
this_ptr->enum_stack_depth_member. - Calls destructor for the popped
Enumobject (AG::LayoutDescriptor::Compare::Enum::~Enumat0x1C0C0). - Comment: End of an enum case block. Pops context from enum stack, restores data pointers and offset.
- (Starts around
-
Default Opcodes (includes 0x80-0xFF for byte comparison):
- (Starts around
0x1C2C8 + ~0x5E5) - If
opcode >= 0(i.e., 0x00-0x3F, as 0x40-0x7F handled earlier): This path is for simple byte skips ofopcode + 1bytes.current_data_offset += (opcode + 1). - If
opcode < 0(i.e., 0x80-0xFF):compare_length = (opcode & 0x7F) + 1.actual_compare_length = min(compare_length, remaining_data_in_buffers).- Calls
AG::LayoutDescriptor::compare_bytes(lhs_data_at_offset, rhs_data_at_offset, actual_compare_length, &mismatch_offset, nullptr). - If false, calls
failed()(passingmismatch_offset + current_data_offsetas failure point) and setsreturn_status_flag = 0. - Advances
current_data_offsetbyactual_compare_length.
- Comment: Handles simple byte skips (positive opcodes not otherwise handled) or direct byte comparisons (negative opcodes).
- (Starts around
Final Cleanup (around 0x1C2C8 + ~0x6A0):
- Loops while
this_ptr->enum_stack_depth_member > initial_enum_stack_depth. - In each iteration, pops an
Enumobject from the stack and calls its destructor. - Ensures all pushed enum contexts are cleaned up.
- Returns
current_opcode_or_status(which holds the final 1 or 0). - Comment: Ensures all enum processing contexts are unwound and destroyed before returning the comparison result.
Mangled Name: __ZN2AG16LayoutDescriptor5fetchEPKNS_5swift8metadataEji
Relative Offset: 0x1EC20 (Absolute in IDA: 0x1B1710C20 with base 0x1B16F2000)
Overall Purpose:
This is a static function that serves as the primary public entry point for obtaining a ValueLayout.
It ensures a singleton TypeDescriptorCache is initialized and then delegates the actual fetching logic to AG::anonymous namespace::TypeDescriptorCache::fetch.
It passes the type_metadata, options, priority, and an explicit HeapMode(0) to the cache's fetch method.
Key Behavior from Decompilation:
- Uses a
dispatch_oncelike mechanism forTypeDescriptorCache::_shared_cache. - Directly returns the result of
AG::anonymous namespace::TypeDescriptorCache::fetch(...). - This top-level
fetchdoes not itself translateValueLayoutTrivial (1)intonullptr. IfTypeDescriptorCache::fetchreturns1, this function will also return1.
Mangled Name: __ZN2AG12_GLOBAL__N_119TypeDescriptorCache5fetchEPKNS_5swift8metadataEjNS_16LayoutDescriptor8HeapModeEi
Relative Offset: 0x1EC90 (Absolute in IDA: 0x1B1710C90 with base 0x1B16F2000)
Parameters (effective): (TypeDescriptorCache* this, const swift::metadata* type, AGComparisonOptions options, LayoutDescriptor::HeapMode heap_mode, uint32_t priority)
Overall Purpose:
Manages a cache of ValueLayouts. If a layout is cached, returns it. Otherwise, generates it, caches it, and returns it. Handles synchronous and asynchronous layout generation.
Key Behavior from Decompilation:
- Cache Key: Derived from
typemetadata pointer and the lower byte ofoptions. - Cache Lookup: Uses an
os_unfair_lockand anUntypedTablefor caching. - Synchronous vs. Asynchronous Path:
- Determined by a global atomic flag and bit
9(0x200) of theoptionsargument (likelyAGComparisonOptionsFetchLayoutsSynchronously). - Synchronous Path (if
options & 0x200is true, or global flag dictates):- Calls
AG::LayoutDescriptor::make_layout(type, (uint8_t)options, heap_mode). - The result from
make_layout(which can benullptr,ValueLayoutTrivial (1), or a valid layout pointer) is inserted into the cache and returned directly.
- Calls
- Asynchronous Path:
- Inserts
nullptrinto the cache as a placeholder. - Queues the layout creation request.
- Dispatches
TypeDescriptorCache::drain_queueto a global queue. - Returns
nullptrimmediately.
- Inserts
- Determined by a global atomic flag and bit
Observed Behavior with Accessor:
- When called without the synchronous flag (
0x200):- For
Int.self(options0),TestPoint.self(options2), andClosureHolder.self(options2), this function (and thus the top-levelfetch) returnednullptr. This was likely due to taking the asynchronous path.
- For
- When called with the synchronous flag (
options | 0x200):- For
Int.self(options0 | 0x200), it still returnednullptr. - For
TestPoint.self(options2 | 0x200), it still returnednullptr. - For
ClosureHolder.self(options2 | 0x200), it returned a valid layout pointer (e.g.,0x000000013780dc03), which decodes to[0x87, 0x05, 0x87, 0x00]. - Interpretation of
nullptrreturn in synchronous mode (for Int/TestPoint): This meansAG::LayoutDescriptor::make_layoutitself is returningnullptr. This likely happens whenmake_layout(viatype.visit(builder)) determines that no specific layout stream is necessary for that type/option combination, and a simpler comparison (e.g., bitwise) is sufficient, signaled by returningnullptr. - Layout for Non-Equatable Enums: For
NonEquatableTestEnum(options0 | 0x200),fetchgenerated[0x09, 0x87, 0x0e, 0x8f, 0x0f, 0x8f, 0x16, 0x00]. This omitted the.stringCaseand used padded comparison sizes for payloads. - Layout for
EquatableTypes: ForMyEquatableStructandTestAssocEnum(options3 | 0x200),fetchgenerated[0x01 (Equals), 0x00]. - Layout for Structs with PODs (
SimpleFieldsStruct, bitwise opts):fetchreturnedValueLayoutTrivial (1). - Layout for Structs with Class/Weak Refs (
StructWithClassField,StructWithWeakField, bitwise opts):fetchreturnedValueLayoutTrivial (1). Opcodes0x04(HeapRef) or0x05(for weak ref field) were not generated. - Layout for Indirect Enums (
NonEquatableIndirectEnum, bitwise opts):fetchgenerated a structural enum layout comparing the indirect case's pointer with0x87, not Opcode0x02. - Layout for Enums with Many Simple Cases (
ManyCasesEnum, bitwise opts):fetchreturnednil. ULEB128 enum tag opcodes (0x08,0x0C) were not generated. - Layout for Struct with Closure (
ViewWithAdvancedClosure, options2 | 0x200):fetchgenerated[0x87 (Int field), 0x87 (closure func_ptr), 0x05 (closure context_ptr), 0x00]. - Opcode
0x05(Function/WeakRef): Observed inClosureHolder's andViewWithAdvancedClosure's layout for the closure context pointer. Not generated for simpleweak varfields in structs that AG deemed bitwise comparable.
- For
-
Opcode
0x01(Equals - Swift Equatable):- Generated by
fetchforMyEquatableStructandTestAssocEnumwhenEquatableAlwaysoptions were used.
- Generated by
-
Opcode
0x02(Indirect):- Not observed in the layout for
NonEquatableIndirectEnum(bitwise options); the indirect case's pointer was compared with0x87. This suggests Opcode0x02is for more complex indirect scenarios or different comparison options.
- Not observed in the layout for
-
Opcode
0x03(Existential):- Testing was skipped due to complexity of getting metadata for
any P. Generation conditions remain unknown from these tests.
- Testing was skipped due to complexity of getting metadata for
-
Opcode
0x04(HeapRef):- Not observed in the layout for
StructWithClassField(bitwise options);fetchreturnedValueLayoutTrivial. This suggests Opcode0x04is for specific heap object types or scenarios not covered by a simple class field in a POD-like struct under bitwise comparison.
- Not observed in the layout for
-
Opcode
0x05(Function/WeakRef):- Observed in the fetched layouts for
ClosureHolderandViewWithAdvancedClosure, specifically for their closure context pointer fields. - When processing this opcode,
operator()reads an 8-byte context pointer from both LHS and RHS data buffers. It then callsAG::LayoutDescriptor::compare_heap_objects(lhs_ctx_ptr, rhs_ctx_ptr, ..., is_function_type=true). - Behavior of
compare_heap_objectsfor function contexts (is_function_type=true):- If
lhs_ctx_ptr == rhs_ctx_ptr(pointers are identical, including both beingnil), returnstrue. - Else if one pointer is
niland the other is not, returnsfalse. - Else (pointers are different but both non-
nil): It then compares the memory content of the context objects pointed to bylhs_ctx_ptrandrhs_ctx_ptr. If the content is identical (e.g., viamemcmpof a size determined by the context object's type, or a fixed size for simple captures), it returnstrue. Otherwise, it returnsfalse.
- If
- This means two distinct closure instances (different context object addresses) can still compare as equal via Opcode
0x05if their function pointers match (handled by a preceding opcode like0x87) AND the content of their capture contexts is identical. - Not observed for the
weak varfield inStructWithWeakField(bitwise options);fetchreturnedValueLayoutTrivial. This implies Opcode0x05is specialized for function/closure contexts or other specific reference types that might involve content comparison, not generalweakreferences if the containing struct is simple and compared bitwise.
- Observed in the fetched layouts for
-
Opcodes
0x06,0x07(Nested Layouts):- Not specifically targeted with types expected to generate them. Conditions for their generation remain to be explored.
-
Opcode
0x08(Enum Tag - ULEB128),0x0C(Enum Continue Tag - ULEB128):- Not observed in the layout for
ManyCasesEnum(11 no-payload cases, bitwise options);fetchreturnednil. This suggests these ULEB tag opcodes are for enums with many payload-bearing cases requiring distinct layouts, not just a high number of simple cases. NonEquatableTestEnumused fixed tag opcodes (0x09, 0x0E, 0x0F).
- Not observed in the layout for
-
Opcode
0x09(Enum Start Fixed Tag 0):- Indicates the start of enum processing, expecting data tag 0.
- Followed by 8 bytes in the layout stream for the
swift::metadata*of the enum type. operator()compares the actual tag of the LHS/RHS data. If it matches this opcode's tag (0), it proceeds to the payload layout for this case. Otherwise, it should skip to the nextEnum ContinueorEnum End.- Pushes an
Enumcontext onto its internal stack.
-
Opcode
0x0E(Enum Continue Fixed Tag 1),0x0F(Enum Continue Fixed Tag 2):0x0D + N. Indicates layout for case with tagN.operator()checks if the data's actual tag (stored in the topEnumcontext) matches this case's tagN.- If it matches, it processes the subsequent payload layout.
- If it does not match, it skips opcodes in the layout stream until the next enum control opcode (another
Enum ContinueorEnum End).
-
Opcode
0x16(Enum End):- Signifies the end of all case layouts for the current enum being processed.
operator()pops the currentEnumcontext from its internal stack, restoring data pointers and offsets to what they were before this enum block started, adjusted by the enum type's size.
This markdown file should serve as a good reference for the analyzed functions.