Skip to content

Black/GJPersistent.cpp: Matching -- carve ??_R0 into the TU split - #157

Draft
Marklepolo wants to merge 1 commit into
openblack:mainfrom
Marklepolo:pr-persistent
Draft

Black/GJPersistent.cpp: Matching -- carve ??_R0 into the TU split#157
Marklepolo wants to merge 1 commit into
openblack:mainfrom
Marklepolo:pr-persistent

Conversation

@Marklepolo

Copy link
Copy Markdown
Contributor

The Persistent base class is defined in GJPersistent.cpp, confirmed by the FILE string baked at .data:0x00BEF87C which this unit's assert path emits. Lift its four virtuals (VirtualFunc / OnLoaded / DefineProperties / ~Persistent), all no-ops, and carve the unit's RTTI type descriptor ??_R0?AVPersistent@@ into GJPersistent's own .data split (0x00BEF860..0x00BEF8A0, covering the ??_R0, the FILE string, and trailing pad up to the next TU).

The object now DEFINES its own ??_R0 (dumpbin: SECT, not UNDEF) rather than having it resolved from another translation unit -- per review feedback on the earlier scope:weak approach.

exact_gate (relocs masked): all functions byte-exact. objdiff: the four methods at 100%.

Comment thread src/Black/GJPersistent.cpp Outdated
@@ -0,0 +1,28 @@
#include "Persistent.h"

// TU GJPersistent.cpp (confirmed by the __FILE__ string baked at .data:0x00BEF87C, which the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need these comments. All details should be in the PR description

Comment thread src/Black/GJPersistent.cpp
@bwrsandman

Copy link
Copy Markdown
Member

Still failing tests

Comment thread configure.py
# has no body, so the link fails on it. Redirect ??_E -> ??_G: the vtable then relocates to the
# real ??_G, byte-identical to retail. One entry per polymorphic Matching class (Persistent is
# the first). See docs / memory baw-matching-tu-link-fixes.
"/alternatename:??_EPersistent@@UAEPAXI@Z=??_GPersistent@@UAEPAXI@Z",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might have to fix lld for this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Black/GJPersistent.cpp: Matching

Lifts the Persistent base class as a byte-exact Matching TU.

TU identity — the source file is GJPersistent.cpp (it defines class Persistent), confirmed two ways: the Windows FILE string baked at .data:0x00BEF87C, and the Mac symbol __sinit_GJPersistent_cpp (the per-file static initializer) in the CodeWarrior build. The four methods (VirtualFunc, OnLoaded, DefineProperties, ~Persistent) match the Mac symbols by name.

??_R0 carved into the split (per review) — the RTTI type descriptor ??_R0?AVPersistent@@ is defined by this TU via a .data split carve 0x00BEF860..0x00BEF8A0 (the even-aligned block: ??_R0 + the FILE string + pad, up to the next TU's ??_R0). The object now defines its own descriptor (DUMPBIN /SYMBOLS shows SECT, not UNDEF) rather than borrowing it via scope:weak.

Link — as the first polymorphic Matching TU, this is the first to hit our c1xx emitting the vector deleting dtor ??_E in the vtable's dtor slot where retail used the scalar ??_G (which we define byte-exact). ??_E has no body, so the full link (--map) needs /alternatename:??_EPersistent@@UAEPAXI@Z=??_GPersistent@@UAEPAXI@Z (added to config.ldflags).

Verification — exact_gate (relocs masked): all functions byte-exact; objdiff: the four methods 100%.

@Marklepolo Marklepolo Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I force pushed changes and the above is standing in for the comments, but yes, right now it seems I cannot get any func to 100% matching with out ldflag hacks, c1xx and the original retail just won't emit the same idioms

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I had done experiments prior that solved this issue just by using different compiler flags

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which ones? I have big trouble now with dtors that have a run-once guard, but different flags did not work

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's the tricky part

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is most likely wrong and the declaration of the virtual destructor in the header probably never was explicitly written.
The vector detector is probably implicitly added due to a global static array of a subclass of Persistent.
The real destructor might be unlabeled still.
This is the case for the Base class

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in #172 so you can remove this flag

@bwrsandman bwrsandman left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs equivalent with 1.0 and 1.1

@Marklepolo

Copy link
Copy Markdown
Contributor Author

Needs equivalent with 1.0 and 1.1

1.1 I will push

1.0 folds the no-op virtuals (VF=OL=0x4017F0, DP=0x4017C0); only ??_G is unique. How do you want folded virtuals represented in a Matching unit — scope:weak COMDATs, given you rejected scope:weak for the ??_R0?"

@bwrsandman

Copy link
Copy Markdown
Member

1.0 is the only version that used /OPT:REF so maybe that's why you see folding. Maybe there's another undiscovered /OPT:

@Marklepolo

Copy link
Copy Markdown
Contributor Author

1.0 is the only version that used /OPT:REF so maybe that's why you see folding. Maybe there's another undiscovered /OPT:

OPT:ICF hat's massive identical-function folding`, maybe?

@Marklepolo
Marklepolo marked this pull request as draft June 13, 2026 19:03
@Marklepolo
Marklepolo force-pushed the pr-persistent branch 2 times, most recently from 34fc1eb to a00d89f Compare June 14, 2026 07:03
@Marklepolo
Marklepolo marked this pull request as ready for review June 14, 2026 07:03
@Marklepolo

Copy link
Copy Markdown
Contributor Author

Lift Persistent (Matching, byte-exact). Real ~Persistent frees the refcounted name-string at this+8; added the members + a PersistentName COW-string struct so the compiler emits the real dtor + matching ??_G/vtable/RTTI. No manual split — symbol-driven placement. /alternatename ??_E→??_G (our cl emits the vector deleting dtor). Verified byte-exact: dtk shasum -c config/BW1E142/build.sha1 → all files OK.

@Marklepolo
Marklepolo requested a review from bwrsandman June 14, 2026 07:36
Comment thread src/Black/GJPersistent.h
char* data;
int length;
int capacity;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The convention is to include a comment above every function in a header with the address on windows (only version 1.41) and on mac.

You can look at any other header to see examples but it is in this format exactly:

    // win1.41 {08x:win_addr} mac {08x:mac_addr} {mac_function_name_demangled} 
    // win1.41 0041a2b0 mac 1007c6d0 Animal::SetStateSpeed(void)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to be done for every function in every header.

Comment thread src/Black/GJPersistent.h

struct PersistentName
{
int field_0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No clue what this is?

Comment thread src/Black/GJPersistent.h
#ifndef BW1_DECOMP_GJPERSISTENT_INCLUDED_H
#define BW1_DECOMP_GJPERSISTENT_INCLUDED_H

void __cdecl operator delete(void* block);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be in its own header and I don't think block is the right name.

Comment thread src/Black/GJPersistent.h
Comment on lines +20 to +29
char* shared = data - 1;
char count = *shared;
if (count == 0 || count == -1)
{
::operator delete(shared);
}
else
{
*shared = count - 1;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like custom memory allocator code and I doubt that this is unique to this class.

Comment thread src/Black/GJPersistent.h
Comment on lines +46 to +47
PersistentOwner* m_owner;
PersistentName* m_name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use the m_ convention. This is invented.

Comment thread src/Black/GJPersistent.h
virtual ~Persistent();
virtual void DefineProperties(PropertyList* param_1);

private:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason to use private

Comment thread src/Black/GJPersistent.h
Comment on lines +50 to +55
class FloatProvider : public Persistent
{
public:
virtual ~FloatProvider();
virtual void DefineProperties(PropertyList* param_1);
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class declaration doesn't have any definition in this PR. Why is it there?

@Marklepolo
Marklepolo marked this pull request as draft June 14, 2026 12:13
@Marklepolo
Marklepolo force-pushed the pr-persistent branch 3 times, most recently from 4f5a128 to 2dfba21 Compare June 20, 2026 19:25
The real ~Persistent frees a refcounted name-string member at this+8, so give
Persistent its members and a PersistentName COW-string struct. The compiler then
emits the real ~Persistent (set vptr; delete m_name -> refcount the buffer at
data[-1], free or decrement; zero data/length/capacity) plus the matching ??_G,
vtable and RTTI. No manual split needed -- symbol-driven placement is correct.
ldflag /alternatename ??_E -> ??_G (our cl emits the vector deleting dtor).

Verified byte-exact: dtk shasum -c config/BW1E142/build.sha1 -> all files OK.
@bwrsandman
bwrsandman changed the base branch from dtk-template to main June 27, 2026 11:49
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