홈으로 돌아가기

constexpr C++에서의 C 컴파일러

이 기사는 컴파일 타임 C++에서 C 서브셋 컴파일러 구현을 설명합니다. constexpr 제한 우회, 시그니처 매핑, 네이티브 함수 바인딩, lexer/parser 아키텍처를 다룹니다.

완전히 constexpr로 구현된 C 컴파일러
Advertisement 728x90

C++ 컴파일 타임에서 C 언어 컴파일러 구현하기

C 언어의 부분 집합을 위한 컴파일러가 C++의 constexpr 환경에서 개발되었습니다. 소스 코드는 런타임에 가상 머신에서 실행되도록 맞춤형 바이트코드로 변환됩니다. 이 접근 방식은 인터프리터 오버헤드가 전혀 없고 완전한 타입 안전성을 보장합니다.

장점으로는 바이너리에 컴파일러 코드가 포함되지 않으며, 런타임 컴파일이 필요 없고, 함수 시그니처 불일치로부터 보호됩니다. 단점: LLVM 수준의 최적화가 부족하고 핫 리로드가 불가능합니다.

constexpr 제약 극복하기

C++20은 동적 메모리 할당과 std::vector 지원으로 constexpr을 확장했지만, 결과를 직접 런타임으로 전달할 수는 없습니다. 템플릿 래퍼가 필요합니다.

Google AdInline article slot

템플릿을 통한 문자열 전달

문자열 리터럴은 const_string 템플릿 클래스를 통해 전달됩니다:

template<std::size_t N>  
struct const_string {  
  constexpr const_string() = default;  
  
  constexpr const_string(const char (&str)[N]) {
    std::copy_n(str, N, value);  
  }  
  
  constexpr operator std::string_view() const {  
    return {value, value + N - 1};  
  }  
  
  char value[N]{};  
  const std::size_t length = N;  
};

// 사용법:
template<const_string str>
auto very_smart_function(...) { /* ... */ }

constexpr에서 배열 추출하기

std::vector를 std::array로 변환하려면 람다 함수를 통해 컴파일 타임에 크기를 고정해야 합니다:

template<auto data_getter>
constexpr auto to_array() {  
  using value_type = typename decltype(data_getter())::value_type;  
  constexpr static std::size_t size = data_getter().size();  
  
  std::array<value_type, size> out;  
  auto in = data_getter();  
  for (std::size_t i = 0; i < size; ++i) {  
    out[i] = in[i];  
  }  
  return out;
}

template<const_string str>
constexpr auto lex() {
  constexpr static auto data_getter = [] constexpr {
    return lexer{static_cast<std::string_view>(str)}.lex();
  };
  return to_array<data_getter>();
}

컴파일 오류 처리

C++26 이전에는 static_assert에 문자열 리터럴이 필요합니다. 해결책은 오류 텍스트를 매개변수로 갖는 ErrorMessage 템플릿입니다:

Google AdInline article slot
template<const_string Msg>  
struct ErrorMessage {  
  static_assert(false, "템플릿 매개변수를 확인하세요");  
};

template<auto err_getter>  
consteval auto report_error() -> void {
#ifdef KORKA_FEATURE_FORMATTED_STATIC_ASSERT  
  static_assert(false, to_string(err_getter()));  
#else  
  constexpr auto msg = const_string_from_string_view<[] { return to_string(err_getter()); }>();  
  std::ignore = ErrorMessage<msg>{};    
#endif
}

C++26에서 출력: Lexer Error: 12번 줄에서 종료되지 않은 문자열.

함수 시그니처 매핑

문자열 이름과 시그니처 타입을 매치시키기 위해 해싱과 오버로딩이 사용됩니다:

template<auto function_info_getter, std::size_t... Is>
struct signature_mapper<function_info_getter, std::index_sequence<Is...>> {
  consteval static auto hash(auto &&v) -> std::size_t {
    return frozen::elsa<std::string_view>{}(v, 0);
  }
  
  constexpr static auto _overloaded = overloaded{
    ([] (unique_type<hash(function_info_getter(Is).name)>)
     -> const_function_info_to_signature_t<[] { return function_info_getter(Is); }> * {
      return nullptr;
    })...
  };
  
  template<const_string name>
  using get_signature_t = std::remove_pointer_t<decltype(_overloaded(unique_type<hash(name)>{}))>;
};

추출: compile_result.function<"fib">();.

Google AdInline article slot

네이티브 함수 바인딩

wrapped_function을 통해 C++ 함수와 스크립팅 언어를 연결합니다:

consteval auto wrap(std::string_view name) {  
  return wrapped_function<std::decay_t<decltype(func)>>{  
    binding_wrapper<func>,  
    name  
  };  
}

constexpr auto bindings = korka::make_bindings(  
  korka::wrap<fib>("cpp_fib"),  
  korka::wrap<print_n>("print_n")  
);

binding_wrapper는 VM에서 인수를 추출하고 결과를 반환하는 코드를 생성합니다.

컴파일러 아키텍처

세 단계로 나뉩니다:

  • 렉서: 문자 루프에서 토큰화
  • 파서: AST 구성
  • 바이트코드 생성기: 의미 분석과 함께 방출

렉서 예시:

constexpr auto scan_token() -> std::optional<std::expected<lex_token, error_t>> {  
  char c = advance();  
  switch (c) {  
    case '{': return make_token(lex_kind::kOpenBrace);  
    case '}': return make_token(lex_kind::kCloseBrace);  
    case '(': return make_token(lex_kind::kOpenParenthesis);  
    case ')': /* ... */
    case ' ': case '\r': case '\t': return std::nullopt;
  }
}

핵심 포인트

  • 컴파일러는 완전히 constexpr이며, 바이트코드는 바이너리에 내장됨
  • C++20 제약을 극복하기 위한 const_string과 람다 래퍼 사용
  • 이름 해시를 이용한 타입 안전 함수 추출
  • 자동 VM 코드 생성과 함께하는 C++ 함수 바인딩
  • 최적화 없이 기본적인 렉서/파서

— Editorial Team

Advertisement 728x90

다음 읽기