273 lines
11 KiB
Plaintext
273 lines
11 KiB
Plaintext
===========================
|
||
нужна сложная адресация
|
||
===========================
|
||
|
||
Хорошо, вот **полностью доработанный пошаговый план** внедрения сложной адресации во все модули компилятора КВС, с учётом:
|
||
|
||
- Поддержки `+`, `-`, `*`, `[`, `]`
|
||
- Новых мнемоник для работы с памятью
|
||
- Адресации всех типов: `[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, строки)
|
||
|
||
---
|
||
|
||
|
||
---
|
||
|
||
План готов. Когда скажете — начинаем писать код по шагам. |