Skip to content

템플릿 클래스의 PropertyInfo가 GCC/Clang에서 누락되는 문제 #25

Description

@Winteradio

🐞 [Bug] [Reflection/Build] 템플릿 클래스의 PropertyInfo가 GCC/Clang에서 누락되는 문제 (Dead Code Elimination)

📌 요약 (Summary)

  • GCC 및 Clang 환경에서 컴파일 시,
    ECS::ComponentContainer<T>와 같은 템플릿 클래스 내부PROPERTY() 매크로로
    등록된 PropertyInfo 객체들의 초기화가 완전히 생략되어 리플렉션 시스템에 등록되지 않음

  • 이는 곧 GC가 템플릿 컨테이너 내부의 포인터를 추적하지 못하게 만들어 치명적인 런타임 오류를 유발
    GC의 Marking 동작은 Reflection TypeInfo에 등록된 PropertyInfo를 보고 추적함,
    하지만, 해당 PropertyInfo의 생성이 생략되었기에 정상적이지 않은 GC Marking 발

🚩 영향 범위 (Scope of Impact)* 환경: Linux (GCC, Clang) 빌드 환경

  • 모듈: Reflection (PropertyInfo), Memory (GC 추적), 모든 템플릿 기반 ECS 컨테이너 및 노드.

📝 재현 경로 (Steps to Reproduce)

  1. GCC/Clang 환경에서 -O0 또는 -O2 최적화 옵션을 사용하여 프로젝트를 빌드
  2. World 초기화 단계에서 TypeInfo 초기화 로그를 확인
  3. 예상 결과: 모든 클래스의 TypeInfoPropertyInfo가 로그에 기록되어야 함
  4. 실제 결과: 일반 클래스(World, Monster)의 PropertyInfo는 등록되지만,
    ECS::NodeContainer<T> 또는 Memory::ObjectPtr<T>와 같은 템플릿 클래스 내부의 Property 등록 로그가 완전히 누락

🕵️ 근본 원인 분석 (Root Cause Analysis)

  1. 링커의 공격적인 최적화:
    GCC/Clang 링커는 기본적으로 **"사용되지 않는 정적 객체의 초기화 코드"**를 실행 파일 크기 최적화를 위해 제거 (Dead Code Elimination).
  2. Property 매크로의 동작:
    PROPERTY() 매크로는 다음과 같은 형태로 초기화 부작용에만 의존하는 정적 객체(s_registerProperty...)를 만듬
  3. 템플릿의 취약성: 일반 클래스는 TypeInfo가 살아있다는 간접적인 보장 덕분에 Property가 우연히 살아남았지만,
    템플릿 클래스는 해당 인스턴스 전체 코드가 사용되지 않는다고 판단되어 Property 등록 객체(s_registerProperty...)가 통째로 제거
#define PROPERTY( Property ) \
		struct RegisterProperty##Property \
		{ \
			RegisterProperty##Property() \
			{ \
				static_assert(!Reflection::Utils::IsReference<decltype(ThisType::Property)>::value, "Reflection::PROPERTY : The property cannot be a reference type."); \
				static std::string propertyName = std::string(#Property); \
				static const Reflection::PropertyInfo* property = Reflection::PropertyCreator<decltype(&ThisType::Property), &ThisType::Property>::Create(propertyName); \
				assert(nullptr != property && "The property is invalid."); \
			}; \
		}; \
		\
		static inline const RegisterProperty##Property s_registerProperty##Property __STATIC_USED__; \ 
		// 위의 **s_registerProperty... 객체의 생성자가 호출되면서 PropertyInfo가 등록이 됨
		// 하지만, 템플릿 클래스의 해당 변수 자체는 호출이 안되고, 미사용되는 객체이기에 GCC 컴파일러에선 삭제해버림
		// 그러다보니, Pool을 관리하는 템플릿 클래스의 PropertyInfo가 존재하지 않게 되고,
		// GC 추적이 불가능함

✅ 해결 방안 (Solution)
PROPERTY() 매크로가 생성하는 정적 객체를 링커가 제거하지 못하도록 명시적으로 강제 유지 속성을 부여

  1. 속성 정의: TypeAttribute.h 파일 등에 GCC/Clang 환경에 특화된 속성을 정의
#if defined(__GNUC__) || defined(__clang__)
#define WTR_USED __attribute__((used))
#else
#define WTR_USED
#endif
  1. PROPERTY 매크로 수정: Property 등록 객체에 WTR_USED 속성을 적용
#define PROPERTY( Property ) \
    /* ... Property 등록 로직 ... */ \
    \
    /* WTR_USED를 붙여 링커에게 이 객체를 제거하지 말 것을 명령 */ \
    static inline const RegisterProperty##Property s_registerProperty##Property WTR_USED; 

추가 코멘트:
MSVC 환경에서는 현재 문제가 없으나, /LTCG 옵션 활성화 시 동일한 문제가 발생할 수 있음
해당 문제 발생 시 Two-Pass 초기화 전략을 강화하거나 MSVC 링커 명령을 추가해야 함

Reflection을 사용하는 템플릿 객체에서만 발생한 문제이므로, 조금 더 명확한 해결 방법은 찾아야 할 것으로 보임

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions