=========================== нужна сложная адресация =========================== Хорошо, вот **полностью доработанный пошаговый план** внедрения сложной адресации во все модули компилятора КВС, с учётом: - Поддержки `+`, `-`, `*`, `[`, `]` - Новых мнемоник для работы с памятью - Адресации всех типов: `[reg]`, `[disp]`, `[reg+disp]`, `[reg+reg*scale]`, `[reg+reg*scale+disp]` - Корректной работы лексера, парсера, pass1, pass2, сборки ELF --- # ПЛАН РЕАЛИЗАЦИИ СЛОЖНОЙ АДРЕСАЦИИ (ПОЛНАЯ ВЕРСИЯ) ## 1. Общая схема изменений по модулям | Модуль | Что меняется | Сложность | |-------------------|------------------------------------|-----------| | `kvs_lexer.py` | новые токены: `[`, `]`, `+`, `-`, `*`, `NUMBER` | низкая | | `kvs_parser.py` | новая логика операндов: `parse_operand()`, `parse_memory_operand()` | высокая | | `kvs_data.py` | активация и уточнение форматов инструкций | средняя | | `kvs_pass1.py` | динамический рассчёт размера инструкций с памятью | высокая | | `kvs_pass2.py` | разбор адресации → ModR/M + SIB + disp, генерация кода | очень высокая | --- ## 2. Детальный план по шагам (в порядке реализации) ### Шаг 1 — Лексер (`kvs_lexer.py`) **Что сделать:** - Добавить токены: - `LBRACKET` → `[` - `RBRACKET` → `]` - `PLUS` → `+` - `MINUS` → `-` - `STAR` → `*` - Добавить `NUMBER` (отдельно от `WORD`) для: - десятичных чисел (`123`) - отрицательных (`-456`) - шестнадцатеричных (`0x7F`) ** парсер:** - Парсер сможет отличать `[` от начала слова - Числа не будут смешиваться с метками/регистрами **Результат:** новые типы токенов на выходе лексера. --- ### Шаг 2 — Парсер (`kvs_parser.py`) — основа **Что сделать:** 2.1. Заменить прямую загрузку токена в `parse_instruction()` на вызов `parse_operand()` 2.2. Реализовать `parse_operand()`: - Если токен `LBRACKET` → вызвать `parse_memory_operand()` - Иначе → вернуть значение токена (регистр/число/метка/строка) 2.3. Реализовать `parse_memory_operand()`: - Разбирает содержимое между `[` и `]` - Поддерживает выражения вида: - `регистр` - `число` - `регистр + смещение` - `регистр + регистр * масштаб` - `регистр + регистр * масштаб + смещение` - любые комбинации с `+` и `-` - Возвращает строку типа `[раикс + рсикс*8 + 16]` **Результат:** AST будет содержать операнды памяти в каноническом строковом виде. --- ### Шаг 3 — Данные инструкций (`kvs_data.py`) **Что сделать:** 3.1. Активировать (или добавить) записи для: | Мнемоника | Opcode | Тип (формат) | |-----------|--------|--------------| | `загрузить` | `48 8B` | `mov_reg_mem` | | `сохранить` | `48 89` | `mov_mem_reg` | | `загрузить_байт_из_памяти` | `8A` | `mov_reg8_mem` | | `сохранить_байт` | `88` | `mov_mem8_reg8` | | `загрузить_адрес` | `48 8D` | `lea` | | `переместить_с_нулями` | `48 0F B6` | `movzx` | | `переместить_со_знаком` | `48 0F BE` | `movsx` | | `втолкнуть_из_памяти` | `FF /6` | `push_mem` | | `вытолкнуть_в_память` | `8F /0` | `pop_mem` | | `обменять_с_памятью` | `48 87` | `xchg_mem` | 3.2. Удалить поле `type`, оставить `format` или прямо в коде проверять по мнемонике (как вы любите). **Результат:** словарь инструкций готов для кодогенерации. --- ### Шаг 4 — Первый проход (`kvs_pass1.py`) — оценка размера **Что сделать:** 4.1. Написать функцию `estimate_memory_operand_size(operand_str)`: - Парсит строку `[раикс + рсикс*8 + 16]` - Определяет: - наличие SIB (нужен, если есть индексный регистр) - размер смещения (disp8 = 1 байт, disp32 = 4 байта) - Возвращает дополнительные байты: 0, 1, 4 или 5 (SIB + disp) 4.2. Модифицировать `estimate_size()`: - Для инструкций с памятью (список из шага 3) считать: - базовый размер opcode (REX + опкод) - +1 байт (ModR/M) - +1 байт (SIB, если нужен) - + размер смещения (1 или 4) **Результат:** правильное вычисление `.text` и адресов меток. --- ### Шаг 5 — Второй проход (`kvs_pass2.py`) — ядро адресации Это самый объёмный шаг. Реализовать **поэтапно**: #### 5.1. Структура данных для режима адресации Создать класс/словарь `AddressingMode`: - `has_base` (bool) - `base_reg` (int 0–15 или None) - `has_index` (bool) - `index_reg` (int 0–15) - `scale` (1,2,4,8) - `has_disp` (bool) - `disp_value` (int) - `disp_size` (1 или 4) #### 5.2. Парсер адресации Написать `parse_addressing_mode(operand_str)`: - Вход: `[раикс + рсикс*8 + 16]` - Выход: `AddressingMode` - Поддержка всех форм из шага 2.3 #### 5.3. Кодирование ModR/M Написать `encode_modrm_reg_mem(reg, addr_mode)`: - Формирует байт ModR/M: - `mod` (00, 01, 10, 11) - `reg` (3 бита — номер регистра) - `r/m` (3 бита — зависит от наличия SIB) #### 5.4. Кодирование SIB Написать `encode_sib(addr_mode)`: - Формирует байт SIB: - `scale` (2 бита) - `index` (3 бита) - `base` (3 бита) #### 5.5. Кодирование смещения Написать `encode_displacement(addr_mode, current_pos, vaddr_text)`: - Для `disp8` → 1 байт со знаком - Для `disp32` → 4 байта (little-endian) #### 5.6. Генераторы инструкций Написать для каждой мнемоники: | Функция | Для какой инструкции | |---------|----------------------| | `encode_mov_reg_mem` | `загрузить` | | `encode_mov_mem_reg` | `сохранить` | | `encode_mov_reg8_mem` | `загрузить_байт_из_памяти` | | `encode_mov_mem8_reg8` | `сохранить_байт` | | `encode_lea` | `загрузить_адрес` | | `encode_movzx` | `переместить_с_нулями` | | `encode_movsx` | `переместить_со_знаком` | | `encode_push_mem` | `втолкнуть_из_памяти` | | `encode_pop_mem` | `вытолкнуть_в_память` | | `encode_xchg_mem` | `обменять_с_памятью` | Каждая функция: - Берёт операнды (регистр + операнд памяти) - Вызывает `parse_addressing_mode` для операнда памяти - Формирует REX-префикс (W, R, B, X биты) - Добавляет opcode - Добавляет ModR/M - Добавляет SIB (если нужен) - Добавляет disp #### 5.7. Диспетчер Модифицировать `encode_instruction()`: - Добавить ветки для всех новых мнемоник - Вызывать соответствующие функции из 5.6 --- ### Шаг 6 — Тестирование (итеративное) Тестировать после каждого подшага 5.x: | Тест | Что проверяет | |------|---------------| | `загрузить раикс, [рбикс]` | ModR/M, нет SIB, нет disp | | `загрузить раикс, [1234]` | disp32, r/m=101 | | `загрузить раикс, [рбикс + 8]` | disp8 | | `загрузить раикс, [рбикс + 1000]` | disp32 | | `загрузить раикс, [рбикс + рсикс]` | SIB, нет disp | | `загрузить раикс, [рбикс + рсикс*8 + 16]` | SIB + disp8 | | `сохранить [рдиай], рсикс` | MOV mem, reg | | `загрузить_адрес р11, [рбикс + рсикс*4]` | LEA | --- ### Шаг 7 — Стандартная библиотека (параллельно или после) После того как адресация заработает, можно добавить: | Файл | Содержание | |------|------------| | `libkvs.квс` | `malloc`, `free`, `strlen`, `strcmp`, `memcpy`, `putchar`, `getchar`, `printf` (упрощённый) | | `libkvs_data.квс` | статические данные для библиотеки | Библиотека будет использовать сложную адресацию для работы со структурами и кучей. --- ## 3. Зависимости между подзадачами ``` Лексер (шаг 1) ↓ Парсер (шаг 2) ↓ Данные инструкций (шаг 3) ↓ Pass1 (шаг 4) ← может тестироваться отдельно ↓ Pass2: 5.1 → 5.2 → 5.3 → 5.4 → 5.5 ↓ 5.6 (для каждой мнемоники) ↓ 5.7 (диспетчер) ↓ Тестирование (шаг 6) ↓ Библиотека (шаг 7) ``` --- ## 4. Итог: что получим на выходе - **Лексер** выдаёт `LBRACKET`, `RBRACKET`, `PLUS`, `MINUS`, `STAR`, `NUMBER` - **Парсер** понимает `[раикс + рсикс*8 - 16]` - **Pass1** правильно считает размер инструкций с памятью - **Pass2** генерирует правильный машинный код для всех форм адресации - **ELF** содержит корректные инструкции x86-64 с ModR/M, SIB, disp - **Библиотека** позволяет писать более сложные программы (malloc, строки) --- --- План готов. Когда скажете — начинаем писать код по шагам.