4.8 KiB
Вот готовая программа для PlayStation 1, которая использует MIPS-ассемблерные вставки для сложения двух чисел через указатели. Результат возвращается в переменную основной программы.
1. Полный код (MAIN.C)
#include <stdio.h>
#include <libetc.h>
// Объявляем ассемблерную функцию
int add_numbers_asm(int *a, int *b, int *result);
int main() {
int num1 = 5;
int num2 = 7;
int result = 0;
// Вызываем ассемблерную функцию с указателями
add_numbers_asm(&num1, &num2, &result);
// Выводим результат
FntPrint("Result: %d + %d = %d\n", num1, num2, result);
FntFlush(-1);
while (1) {
VSync(0);
}
return 0;
}
// Реализация на MIPS-ассемблере
int add_numbers_asm(int *a, int *b, int *result) {
int ret;
asm volatile (
"lw $t0, 0(%1) \n" // Загружаем *a в $t0
"lw $t1, 0(%2) \n" // Загружаем *b в $t1
"add $t2, $t0, $t1 \n" // Складываем: $t2 = $t0 + $t1
"sw $t2, 0(%3) \n" // Сохраняем результат по адресу *result
"move %0, $t2 \n" // Возвращаем результат через регистр
: "=r" (ret) // Выходные операнды
: "r" (a), "r" (b), "r" (result) // Входные операнды
: "$t0", "$t1", "$t2" // Разрушаемые регистры
);
return ret;
}
2. Как это работает?
-
Передача указателей в ассемблер
Функцияadd_numbers_asmпринимает три указателя:a— адрес первого числа,b— адрес второго числа,result— адрес для сохранения результата.
-
Ассемблерная вставка (
asm volatile):lw— загружает значения из памяти по указателям.add— складывает числа.sw— сохраняет результат по адресуresult.move— возвращает значение через регистр.
-
Доступ к результату
Результат будет:- В переменной
result(через указатель). - В возвращаемом значении функции (если нужно).
- В переменной
3. Компиляция и запуск
-
Скомпилируйте через PsyQ SDK:
ccpsx MAIN.C -o ADD_DEMO.EXE -
Запишите на диск и запустите на эмуляторе или консоли.
На экране появится:Result: 5 + 7 = 12
4. Важные нюансы
-
Регистры MIPS:
$t0–$t9— временные регистры (можно использовать без сохранения).$s0–$s7— должны сохраняться (если используются).
-
Разрушаемые регистры:
Указаны в концеasm volatile(чтобы компилятор не использовал их для оптимизации). -
Опасность:
Если передать неверные указатели, PS1 уйдёт в краш (нет защиты памяти).
5. Пример с глобальными переменными
Если нужно работать с глобальными переменными напрямую:
int global_num1 = 10;
int global_num2 = 20;
void add_globals_asm() {
asm volatile (
"la $t0, global_num1 \n" // Загружаем адрес global_num1
"lw $t1, 0($t0) \n" // Читаем значение
"la $t0, global_num2 \n"
"lw $t2, 0($t0) \n"
"add $t1, $t1, $t2 \n"
"sw $t1, 0($t0) \n" // Сохраняем сумму в global_num2
);
}
6. Итог
- Ассемблерные вставки дают полный контроль над железом PS1.
- Указатели передаются через регистры (
$a0–$a3в MIPS). - Результат можно получить через память или возвращаемое значение.
Этот подход используется в оптимизированных частях игр (например, в рендеринге или физике).