Компилятор EDG долго был закрытым: его фронтендчасть компилятора, которая разбирает исходный код и выполняет семантический анализ лицензировали производители компиляторов и инструментов, а исходный код видели единицы. Теперь фронтенд и сопутствующие компоненты лежат в открытом репозитории edgcpp/compiler. Разберём, что именно открыто, как устроены ключевые механизмы фронтенда и что это меняет для тех, кто пишет на C++ или делает инструменты вокруг него.
Что именно открыто
Основной фокус репозитория — C/C++ фронтенд. Помимо него в проект входят:
- бэкендчасть компилятора, которая превращает разобранное представление программы в выходной код, генерирующий C — им можно получать C-код для C++-программ;
- бэкенд, генерирующий C++ — он полезен для задач source-to-source трансформацийпреобразования программы из одного исходного вида в другой без изменения смысла;
- прелинкеринструмент, который обрабатывает автоматическую инстанциацию шаблонов до этапа компоновки;
- минимальная runtime-библиотека поддержки;
- утилиты для работы с промежуточным языкомвнутренним представлением программы, в которое фронтенд переводит исходный текст: запись в файл, чтение обратно и отображение в удобочитаемой форме;
- деманглер имёнинструмент, который превращает искажённые имена символов обратно в читаемые имена C++;
- набор специализированных инструментов разработки.
Тесты лежат в репозитории как каталог tests. Минимальная runtime-библиотека явно не включает «настоящие» библиотеки — например, для потокового ввода-вывода.
Для начала работы большинству людей рекомендуется partial cloneклонирование, при котором сохраняется полная история git, но скачивается только самая свежая версия каждого файла, а более старые версии git подтягивает по мере необходимости:
git clone --filter=blob:none [email protected]:edgcpp/compiler.gitКак устроен фронтенд: конвейер обработки
Файлы фронтенда покрывают разные фазы трансляции. expr.c отвечает за разбор выражений: scan_expr_full сканирует выражение и возвращает его в *result — это указатель, через который функция отдаёт наружу разобранное выражение; для связанной функции в C++ может дополнительно установить *bound_function_selector — указатель, через который возвращается селектор объекта связанной функции. templates.c содержит scan_template_declaration, которая сканирует объявление шаблона в области видимости пространства имён, вызывая для этого decl_specifiers и declarator. class_decl.c содержит scan_class_definition — обработку определения класса, включая частичные и нереальные классы. overload.c содержит select_overloaded_function, которая по списку аргументов определяет, какую из перегруженных функций следует вызвать. ifc_modules_read.c содержит process_ifc_declaration, которая пытается сформировать IL-сущностьвнутреннее представление объявления в промежуточном языке компилятора для объявления модуля IFC, выполняя три действия: обработку предварительных условийподготовительных шагов, которые должны быть выполнены до создания IL-сущности объявления , создание IL-сущности и отображение информации для ленивой загрузки.
Повторный проход по выражению (rescan)
Механизм rescan в expr.c нужен, чтобы повторно выполнить семантический анализ уже разобранного выражения в контексте вывода шаблонных аргументоввыяснения, какими типами и значениями должны быть параметры шаблона, чтобы он подошёл к конкретным аргументам . Внутренняя функция rescan_expr_with_substitution_internal заново прогоняет семантический анализ выражения, а внешняя обёртка rescan_expr_with_substitution возвращает копию выражения с выполненной подстановкой.
Перед повторным проходом push_expr_rescan_context_if_necessary сохраняет текущий контекст: стек выражений, время жизни объекта, регион IL и template_decl_info. Если мы на верхнем уровне, она переключается в область файла и создаёт новый template_decl_info. Между проходами кэшируется информация о позициях в исходном тексте: record_position_in_expr_for_rescan записывает начальную и конечную позиции выражения, record_type_operand_position_for_rescan делает то же для узлов-типов. Сохраняется и rescan-информация об операторах, а также флаги в rcblock->options, например CTWS_INSIDE_EXPR_RESCAN, чтобы не заходить в рекурсивный rescan повторно. После завершения pop_expr_rescan_context_if_necessary восстанавливает сохранённый контекст.
Кэш выражений
Кэш выражений — это initializer_cache, привязанный к текущему expr_stack. Он хранит уже просканированные init-компоненты: выражения или braced-init-list. Наличие закэшированного элемента проверяет cached_initializer_present(), а извлечение делает fetch_init_component_from_initializer_cache(expr_stack->initializer_cache).
При промахе поведение зависит от текущего токена. Если это tok_lbrace и list-инициализацияинициализация через фигурные скобки разрешена, сканируется braced-init-list через parse_braced_init_list. В остальных случаях сканируется выражение с запретом оператора запятая: scan_expr_as_init_component(bundle, EOPT_DISALLOW_COMMA_OPERATOR). get_braced_init_list работает так же: при наличии кэша берёт компонент оттуда, иначе сканирует список из исходника через parse_braced_init_list_full.
cache_expression сам кэш не читает: он сохраняет и восстанавливает expr_stack, выставляет consteval_call_need_not_fold = immediate_context и вызывает scan_expr_as_init_component. Функция cache_curr_token_sequence_entry к кэшу выражений отношения не имеет: она добавляет текущий токен в переданный кэш и возвращает IL-представление токена.
Шаблоны: инстанцирование и дедукция
Прототип-инстанцирование
Для шаблона класса прототип-инстанцирование выполняет prototype_instantiation_for_template: по символу шаблона класса или вложенного класса она получает supplementдополнительную структуру, в которой компилятор хранит сведения о шаблоне, берёт из него поле prototype_instantiation и возвращает тип этого символа.
Для функций решение принимает prototype_instantiation_should_be_done_for_function. Прототип-инстанцирование запускается, если включён флаг nonclass_prototype_instantiations, либо если шаблон вариадическийпринимающий переменное число аргументов через пакет параметров, либо — при разрешённых расширениях Microsoft — если шаблон generic.
Само прототип-инстанцирование функции делает function_prototype_instantiation. Оно помечает, что прототипная инстанциация начата, при необходимости инстанцирует спецификацию исключений, устанавливает referencing_namespace. Если функция не определена, не defaulted и не deleted, ставится опция PS_PROTOTYPE_INSTANTIATION (или PS_GENERIC_DEFINITION для generic), при необходимости добавляются PS_IS_SPECIALIZATION и PS_IS_GENERIC_LAMBDA. Затем через push_template_instantiation_scope и rescan_reusable_cache пересканируется тело и вызывается scan_function_body. Для аргументов по умолчанию есть отдельная default_arg_prototype_instantiation: она для каждого элемента списка заново активирует кэш токенов и вызывает delayed_scan_of_default_arg_expr.
Для переменных-шаблонов и статических данных-членов прототип-инстанцирование делает variable_template_prototype_instantiation. Оно пропускает повторную инстанциацию члена во время инстанциации охватывающего класса, а также пропускает работу, если не включены nonclass_prototype_instantiations, шаблон не вариадический и в кэше нет requires-токена.
Назначение прототип-инстанцирования указано прямо в комментариях: оно делается, чтобы обнаружить ошибки, диагностируемые на этапе определения шаблона, и записать информацию о независимых вызовах для последующего реального инстанцирования.
Дедукция аргументов шаблона
Дедукцию для вызова функции выполняет function_template_call_argument_deduction. Она проходит по списку параметров и аргументов, пропуская designatorименованные инициализаторы вида .поле = значение, и для каждого параметра вызывает deduce_one_parameter.
Пакеты параметров обрабатываются в deduce_one_parameter: при ptp->is_parameter_pack начинается контекст дедукции пакета, затем цикл итерирует по аргументам, а advance_to_next_deduced_element переходит к следующему элементу пакета. Для нетрейлинг пакетов в function_template_call_argument_deduction параметр пропускается как невыводимый контекст. Для параметров, являющихся результатом раскрытия нетрейлинг пакета, дедукция пакетов запрещается.
Невыводимые контексты обрабатываются так: если параметр не вовлекает выводимый шаблонный параметр, дедукция не делается, аргумент пропускается, а для пакета берутся все оставшиеся аргументы. Если adjust_deduction_pair не даёт результата и consider_nondeduced истинно, дедукция откладывается, но для пакета это сразу ошибка.
std::initializer_list обрабатывается через is_instance_of_std_initializer_list, которая возвращает elem_type и elem_pack (TRUE для формы T...), и через deduce_from_braced_init_list, вызываемую из deduce_one_parameter при is_braced_init_component(arg). Сама дедукция пары выполняется в deduce_from_one_pair через matches_template_type с флагом MTT_ALLOW_INEXACT_DEDUCTION, а при неудаче для указателей — через matches_template_type_with_qualification_conversion.
Частичный порядок шаблонов
Частичный порядок реализован через сравнение специализации. compare_function_templates возвращает 1, если первый шаблон более специализирован, -1 если второй, 0 если неупорядочены.
Для частичных специализаций compare_partial_specializations сначала пытается вывести параметры одного шаблона из прототипа другого через matches_partial_specialization. Если вывод удался только в одну сторону, эта сторона более специализирована. Если вывод не дал порядка, сравниваются списки аргументов на наличие variadic-параметров: список с большим числом variadic-аргументов считается более специализированным. При включённых концептах дополнительно вызывается compare_constraints, и при равенстве result == 0 и обоих match1/match2 результат берётся из порядка ограничений.
Кандидаты накапливаются в списке через add_to_partial_order_candidates_list, который удаляет менее специализированные записи и не добавляет новую, если она не более специализирована. select_best_partial_order_candidate возвращает первую запись, а при нескольких записях устанавливает ambiguous и выдаёт ошибку ec_ambiguous_partial_spec для частичных специализаций класса.
Deduction guides
create_implicit_deduction_guides обходит конструкторы шаблона класса и для каждого конструктора, не являющегося inheriting constructorконструктором, который класс наследует от базового через using-объявление, создаёт неявный deduction guideправило, по которому компилятор выводит аргументы шаблона класса из аргументов конструктора через create_implicit_deduction_guide. Если конструкторов нет, добавляется guide для гипотетического конструктора по умолчанию через add_guide_for_hypothetical_constructor. Также добавляется copy deduction candidate — тем же вызовом, но с типом параметра, равным proto_type.
Для alias-шаблоновшаблонов-псевдонимов, вводящих второе имя для шаблона create_transformed_deduction_guide_for_alias_template преобразует guide по правилам N4868 [over.match.class.deduct]/2 и записывает результат в deduction_guides alias-шаблона. Для inheriting constructors create_inherited_deduction_guides перебирает using-объявления с is_inheriting_ctor и iek_base_class и вызывает transform_guides_for_inherited_base, который трансформирует guides базового шаблона через изобретённый alias.
При разрешении перегрузки compare_deduction_guides_if_applicable задаёт предпочтения: guide, не порождённый inheriting constructor, предпочитается порождённому; user-declared guide предпочитается сгенерированному; copy deduction candidate предпочитается; guide от обычного конструктора предпочитается guide от шаблона конструктора.
Разрешение перегрузки
Конвейер начинается в try_overloaded_function_match: он обходит каждый символ перегруженного множества и для каждого вызывает проверку жизнеспособности. Ранние отсевы: пропуск шаблонов при ignore_templates, пропуск в первом проходе по braced-init-list всех конструкторов, кроме initializer-list, и отсев по видимости или устаревшим объявлениям.
determine_function_viability проверяет число и типы параметров, добавляет селектор как explicit this, отбрасывает невидимые функции, не прошедшие ограничения, уже присутствующие в списке кандидатов и defaulted-операторы сравнения.
select_best_candidate_functions сначала удаляет встроенные операторы с той же сигнатурой, кандидатов с anachronism-совпадениями и не-initializer-list конструкторы при наличии initializer-list, затем для каждого аргумента строит множество лучших совпадений через compare_arg_match_levels. Финальный тест — match_is_better_on_at_least_one_arg: выбранная функция должна быть строго лучше хотя бы по одному аргументу для каждой другой функции, иначе вызов неоднозначен.
Если совпадения по аргументам равны, compare_candidate_functions применяет tiebreaker'ы по порядку: compare_late_tiebreakers, предпочтение user-defined conversion с совпадающим видом ссылки, template vs non-template, стандартные преобразования после UDC, добавленные квалификаторы, copy/move конструкторы, специализация шаблонов, наследование, inheriting ctor, deduction guides, g++ const this, enable_if и pass_object_size атрибуты, using-declaration.
Rewritten-кандидаты для operator== и operator<=>
discard_eq_candidates_that_are_not_rewrite_targets проходит по списку кандидатов и удаляет те operator==, которые не являются rewrite targetфункцией, из которой стандарт разрешает переписать выражение с другим оператором для первого операнда заданного типа. Кандидат отбрасывается, если это оператор onk_eq и operator_eq_is_rewrite_target для него ложно.
operator_eq_is_rewrite_target считает operator== rewrite target, если у него нет соответствующего operator!=, либо если этот operator!= является defaulted: defaulted operator!= не мешает operator== быть rewrite target.
При обработке перевёрнутых кандидатов для == первый операнд переписанного выражения — это исходный второй операнд, поэтому отбрасывание идёт по operand_2->type. При обработке onk_ne отбрасываются переписанные operator==, не являющиеся rewrite target с исходным первым операндом. Кроме того, если переписанный supplemental-кандидат имеет тот же тип, что и не-переписанный, переписанный отбрасывается, поскольку правило разрешения предпочитает не-переписанного кандидата.
complete_comparison_rewrite применяет второй оператор к результату переписывания: для != результат инвертируется, для relational/spaceship строится нулевой литерал и вызывается process_rel_operator или process_spaceship_operator, причём spaceship переписывается только в reversed-случае. rel_op_synthesizes_from_spaceship определяет, что заданный rel_op для данных типов операндов вызывает spaceship-оператор: выбранный rewritten_candidate должен быть sfk_operator с opname_kind == onk_spaceship.
Сравнение ссылочных привязок
compare_reference_matches сравнивает две записи соответствия аргументов и возвращает +1, 0 или -1. Она применяется только если оба типа параметров ссылочные и ни одно из соответствий не является соответствием для параметра this с ref-квалификатором по умолчанию. Если один тип — rvalue-ссылка, а другой — lvalue-ссылка, привязка rvalue-ссылки лучше, кроме случая ссылки на функцию.
direct_reference_binding_possible проверяет возможность прямой привязки ссылки и учитывает версии стандарта: при !cpp11_mode || ms_version_is(<1928) используется qualification_conversion_possible, иначе при не-gpp и не-clang режимах — are_reference_compatible, иначе f_types_are_compatible с флагами.
Привязка lvalue-ссылки на const volatile к rvalue запрещается при разрешении перегрузки в gpp_mode, но допускается в microsoft_mode и в strict_ansi_mode без cpp11_mode. Также current_mode_requires_early_rvalue_ref_lvalue_test определяет, что текущий диалект следует старому правилу черновика C++11, требующему проверки запрета привязки rvalue-ссылки к lvalue до преобразований:
return rvalue_references_enabled &&
microsoft_mode && microsoft_version < 1700;Классы, специальные члены и переопределения
Специальные функции-члены
generate_trivial_ctors создаёт представление для класса, который полон и не имеет нетривиальных конструкторов — это нужно, например, когда дружественный класс ссылается на них. Она всегда создаёт тривиальный конструктор по умолчанию и тривиальный конструктор копирования, а тривиальный конструктор перемещения — только если generate_move_operations истинно. Все они помечаются compiler_generated = TRUE, never_throws = TRUE и inline-флагом.
generate_trivial_dtor создаёт тривиальный деструктор, но если переданный src_loc не совпадает с именем деструктора, она не генерирует его и возвращает cssp->destructor.
check_suppressed_special_functions проверяет, есть ли ошибки, препятствующие генерации неявного определения копирующего или перемещающего оператора присваивания, конструктора копирования или перемещения, деструктора. Например, нестатический член с const-квалифицированным типом подавляет копирующее и перемещающее присваивание: присваиваниеоперация, которая копирует или перемещает значение в уже существующий объект такому члену нельзя присвоить новое значение, поэтому сгенерировать её тело невозможно. Член ссылочного типа подавляет копирующее и перемещающее присваивание по той же причине: ссылкуимя, привязанное к другому объекту и не допускающее переназначения после инициализации нельзя перепривязать. Член типа rvalue-ссылки подавляет конструктор копирования, поскольку rvalue-ссылкуссылка, привязываемая к временным объектам и перемещаемым значениям не может быть скопирована из обычного lvalue-источника.
mark_suppressed_defaulted_members_as_deleted помечает defaulted-специальные членыспециальные функции-члены, объявленные с = default как deleted, если соответствующий флаг подавления установлен: например, для конструктора по умолчанию при suppress_default_ctor выставляются is_deleted = TRUE и defined = TRUE.
generate_destructor добавляет объявление деструктора; если suppress_dtor истинно, вызывается mark_special_member_suppressed, иначе при включённых constexpr-деструкторах и constexpr_dynamic_alloc_enabled деструктор делается constexpr.
Отложенные fixup'ы
При разборе класса создаются записи о рутинных fixup'ах через alloc_routine_fixup, которые добавляются в список routine_fixup_list класса. Для специализаций в Microsoft-режиме используется add_routine_fixup_for_specialization, которая создаёт запись с флагом is_specialization.
Часть обработки откладывается, потому что тела inline-функций и аргументы по умолчанию нельзя обрабатывать до завершения всех определений классов: process_deferred_class_fixups_and_instantiations вызывается только когда счётчики pending_class_definitions и defer_inline_function_fixups равны нулю.
Для friend-функций, определённых в шаблонном классе, defer_routine_fixup_until_use сохраняет указатель на fixup и ставит deferred = TRUE, чтобы тело обрабатывалось только при первом использовании. Когда fixup откладывается до конца единицы трансляции, add_to_deferred_friend_function_fixup_list добавляет запись в deferred_friend_fixup_list, а process_deferred_friend_fixup_list выполняет их позже. Само выполнение делает deferred_friend_function_fixup: реактивирует область класса, сканирует тело функции через scan_function_body и помечает функцию определённой.
Финальный overrider виртуальной функции
Финальный overriderфункция, которая в итоге переопределяет виртуальную функцию базового класса для данного объекта ищется функцией find_final_overrider. Она берёт список overriding_virtual_functions у базового класса и, если в нём есть запись, у которой primary_function совпадает с искомой функцией, заменяет указатель на функцию и указатель на базовый класс. Если overrider находится в самом производном классе, указатель на базовый класс обнуляется. Список упорядочен по номерам виртуальных функций primary_function, поэтому при первом элементе с большим номером поиск прекращается.
Записи в этот список добавляет record_virtual_function_override: если запись для primary_func уже есть, она переиспользуется, иначе создаётся новая и вставляется в порядке номеров виртуальных функций.
При множественном и виртуальном наследовании, когда один и тот же primary_func переопределён в нескольких базах, а в самом производном классе переопределения нет, в списке остаются две записи с одинаковым primary_function. report_virtual_function_ambiguities находит такие соседние дубликаты, удаляет лишние и выдаёт ec_ambiguous_virtual_function_override, если среди них больше одного overrider в неоднозначных базах. Если же в самом производном классе есть переопределение, record_virtual_function_override при обработке существующей записи удаляет все последующие записи с тем же primary_function, устраняя неоднозначность.
update_override_registry служит для учёта переопределений функций из набора перегрузок: он ведёт список записей по overridden_sym и base_class, считает число виртуальных функций в наборе и увеличивает override_count при успешном переопределении, а при неудаче добавляет запись в override_failures.
Лямбды
Лямбда-замыкание создаётся как неполный класс: make_closure_class создаёт безымянный тег-символ и тип класса, помечает его is_lambda_closure_class = TRUE и при необходимости фиксирует родителя — поле или переменную инициализатора.
Захваты формируются через add_lambda_capture, который для неявных захватов находит глубину области и вызывает r_add_lambda_capture. Тот рекурсивно создаёт захват во вложенной лямбде, при необходимости поднимаясь к объемлющей лямбде, и для неявного захвата вызывает compute_const_capture_flag и создаёт поле через field_for_lambda_capture.
compute_const_capture_flag выставляет const_capture = TRUE для захвата по ссылке, если исходный захват const, либо для захвата по значению, если лямбда не mutable. field_for_lambda_capture при необходимости объявляет поле захвата и соответствующее поле во внешних лямбдах, возвращая lcp->closure_field.
Конверсия в указатель на функцию генерируется только для лямбды без списка захватов, без захвата по умолчанию (с оговоркой для старых GCC) и с телом: тогда создаётся конверсионная функция и статическая точка входа, а позже — их тела.
Модули и IFC
Формат IFC
Заголовок IFC читается функцией read_file_header, которая инициализирует буфер начиная с 4-го байта (после магического числа) и конструирует из модуля узел an_ifc_file_header. Из заголовка извлекаются major- и minor-версии и архитектура.
Совместимость проверяет check_ifc_compatibility: если версия не поддерживается, вызывается handler->bad_version, а если архитектура не совместима с целевой — handler->bad_architecture. is_target_compatible_arch сравнивает архитектуру с текущей целевой: для ifc_as_arm32 требуется ARM и не 64 бита, для ifc_as_arm64 — ARM и 64 бита, для ifc_as_x86 — x86 и не 64 бита, для ifc_as_x64 — x86 и 64 бита, для ifc_as_unknown — FALSE. В an_ifc_error_severity_checker::bad_version при invalid_version_is_warning выставляется es_warning, иначе es_catastrophe; bad_architecture всегда даёт es_catastrophe.
load_string_table получает смещение и размер таблицы строк из заголовка и либо возвращает указатель в отображённой области, либо читает данные из файла. verify_checksum читает 32 байта ожидаемой контрольной суммы после 4 магических байтов и хеширует оставшуюся часть файла (f_size - 36 байт) алгоритмом SHA-256.
Ленивая загрузка
Ленивая загрузка отслеживается учётной структурой, которая ведёт три набора сущностей: сопоставление сущностей переднего плана их IFC-объявлениям, набор сущностей, обрабатываемых в данный момент, и набор сущностей, обработка которых не удалась и не должна повторяться. При обращении к отложенной сущности она попадает в набор обрабатываемых; когда обработка завершается успешно, запись убирается из этого набора и её сопоставление с IFC-объявлением снимается; если обработка завершилась ошибкой, запись убирается из набора обрабатываемых и переносится в набор неудачных, чтобы повторные попытки не предпринимались.
Отложены могут быть IL-инициализаторы переменных и тела IL-функций. Поля и битовые поля откладывать нельзя. Отложенными не могут быть hidden friendsдружественные функции, определённые прямо внутри класса и видимые только через поиск по аргументам: такие функции не загружаются немедленно, и отложить их тоже нельзя, поскольку они загружаются вместе с классом. Также не откладываются классические перечисления и безымянные перечисления — они обрабатываются сразу.
Триггером загрузки служит обращение к отложенной сущности, после чего она помечается как обрабатываемая, а по завершении — как завершённая.
Восстановление объявлений из IFC
process_decl_at_index получает указатель на сущность модуля по индексу объявления и вызывает process_ifc_declaration, который создаёт IL-сущность. request_entity_at_index делает то же, но через request_entity, и обработка считается завершённой только тогда, когда она действительно завершена. process_ifc_declaration выполняет три шага: обработка предварительных условий, создание IL-сущности и привязка информации для ленивой загрузки определения.
cache_decl, cache_type и cache_expr наполняют кэш токенов: cache_type вызывает cache_type_first_part и cache_type_second_part, cache_expr разбирает вид выражения и либо выдаёт ошибку для неподдерживаемых, либо кэширует токены.
Для EDG-расширения (ifc_es_expr_vendor_extension) проверяется is_edg_authored, вычисляется ожидаемое смещение как expr.value - 1, проверяется наличие элемента в ifc_pk_edg_token_cache, затем вызывается cache_edg_token_cache с нормализованным смещением. Для MSVC-расширений в cache_operator (монадические операторы) под #if MICROSOFT_EXTENSIONS_ALLOWED кэшируются токены вроде tok_assume, tok_alignof, tok_uuidof и других; в cache_expr под тем же условием обрабатываются is_broken_reference_to_template_parameter и is_broken_indirect_reference_to_template_parameter.
Redeclaration между модулями
check_and_set_redeclaration ищет повторное объявление базовой сущности через find_redeclared_basic_entity, который проверяет активные и неактивные символы; при обнаружении вызывается maybe_diagnose_redeclaration. Однако диагностика в maybe_diagnose_redeclaration отключена директивой #if 0, поэтому ошибка не выдаётся.
Для шаблонов check_and_set_template_redeclaration использует find_redeclared_template_entity, а is_function_template_redecl сравнивает параметры функции и шаблона: при несовпадении параметров совпадение не подтверждается. Расхождения в параметрах функции и шаблона считаются несовпадением. Таким образом, ошибкой считается только случай, когда диагностика включена, но в текущем коде она отключена.
Что это значит для экосистемы C++
Проект позиционируется как «open source EDG C/C++ compiler project», основной фокус которого — фронтенд EDG C/C++. Его ключевые свойства: совместимость парсинга и эмуляция ошибок, чтобы исходный код, компилируемый Clang, GCC и MSVC, компилировался с EDG; обширная документация; крайняя конфигурируемость, позволяющая использовать его как основу для множества C/C++-ориентированных продуктов. Относительно GCC, Clang и MSVC проект позиционируется как обеспечивающий совместимость исходного кода, который компилируется этими компиляторами, с EDG.
Разработка следует трём направлениям: вклад сообщества, постоянная поддержка и коллективно финансируемые функции. Крупные возможности могут разрабатываться и финансироваться сообществом: организации поддерживают их финансово, а не выделяют своих разработчиков. После того как возможность профинансирована, она выпускается для всех одновременно, и никто не получает ранний доступ. Все изменения попадают в один публичный репозиторий одновременно.
Процессом по всем трём трекам руководит Fiscal Sponsorship Committee под председательством John Spicer, включающий волонтёров из сообщества пользователей EDG. Комитет определяет приоритеты для поддержки и новых возможностей. Финансирование идёт за счёт не облагаемых налогом взносов сообщества, а разработкой занимаются EDG-разработчики, нанятые The C++ Alliance, плюс вклад сообщества пользователей EDG. Обратная связь по составу комитета и по приоритетам разработки приветствуется.
Для связи предлагаются CppLang Slack с каналом `#
Где смотреть в коде
- overload.c: compare_candidate_functions
- ifc_modules_read.c: check_ifc_compatibility
- ifc_modules_read.c: is_pending
- expr.c: scan_expr_or_braced_init_list
- overload.c: ref_to_const_volatile_binding_to_rvalue_disallowed_in_ovl_res
- templates.c: compare_partial_specializations
- ifc_modules_read.c: has_associated_ifc_decl
- overload.c: deduce_from_one_pair