Решение проблемы RISC-V Kata локально, не очень простой способ

Отказ от ответственности: donaldsebleung является модератором сообщества Codewars, но не официальным сотрудником. Любые мнения, выраженные в этой статье, принадлежат исключительно donaldsebleung и ни в коем случае не должны рассматриваться как официальная позиция Codewars.

Вы слышали? Сборка RISC-V теперь поддерживается на Codewars! Если вы еще не слышали о Codewars, это что-то вроде HackerRank или LeetCode, но с..:

  • Надлежащая поддержка разработки на основе тестирования (TDD) с использованием фреймворков модульного тестирования производственного уровня, таких как JUnit для Java или Chai для Node.js.
  • Разнообразный спектр задач по коду (Kata), охватывающий основы языка, структуры данных и алгоритмы (DSA), математику, расширенные возможности языка, доказательство теорем… в общем, все, что угодно.
  • Столь же разнообразное сообщество
  • Поддержка 50+ различных языков программирования на момент написания статьи.

Если вы разработчик, желающий улучшить свои навыки программирования с помощью практики, вам определенно стоит попробовать Codewars 😉


Покончив с этим, вы, возможно, зададитесь вопросом: «Как настроить среду разработки для решения RISC-V Kata локально?». Для большинства языков программирования это просто и понятно — достаточно установить соответствующие средства разработки на локальную машину, и все готово. Например, чтобы решить Node.js Kata локально, вы можете установить на свой компьютер:

  • Node.js и npm.
  • Chai
  • (Необязательно) VSCode или другую IDE на выбор, плюс специфические для Node.js плагины/расширения для улучшения опыта разработчика.

Затем скопируйте файл решения и модульные тесты из соответствующей Kata и сразу же приступайте к написанию кода.

Но с ассемблером RISC-V все не так просто. Программы на ассемблере тесно связаны с архитектурой процессора и операционной системой, на которую они нацелены, а в 2022 году, скорее всего, архитектура процессора компьютера (или смартфона), на котором вы читаете эту статью, не RISC-V — это, скорее всего, x86_64/amd64 (для большинства потребительских ПК) или aarch64 (для смартфонов и Apple Silicon Macs). Итак, если это вообще возможно, как запускать программы на ассемблере RISC-V на локальном устройстве?

Оказывается, есть несколько возможных способов сделать это. Вы можете (без особого порядка предпочтений):

  1. Запустить контейнер, созданный для RISC-V, с эмуляцией пользовательского режима QEMU. Это самый простой способ, а также то, как это делает Codewars. В Windows и macOS это можно сделать с помощью Docker Desktop, а в Linux — с помощью Docker Engine / Podman после установки qemu-user-static на Ubuntu или эквивалентного пакета (пакетов) для других дистрибутивов.
  2. Приобретите систему-на-чипе (SoC) RISC-V, например, плату SiFive HiFive Unmatched, загрузите на нее Linux, подключитесь к последовательной консоли, затем (соберите и) установите GCC и Cgreen на плату плюс (опционально) подходящий текстовый редактор, такой как Emacs Nano Vim для облегчения процесса разработки
  3. Как нечто среднее между (1) и (2), сделайте то же самое, что и (2), но на полностью эмулированной плате virt с полной эмуляцией системы QEMU.

Как уже упоминалось, (1) — самый простой, а также в точности напоминает среду выполнения Codewars, но он уже хорошо задокументирован в https://github.com/codewars/riscv, поэтому мы не будем его здесь рассматривать. (2) — самый сложный и захватывающий способ, но он требует покупки отдельной платы, которую большинство решателей, вероятно, сочтут излишеством для решения Codewars Kata. Поэтому в этой статье мы рассмотрим способ (3), который почти такой же захватывающий, как и (2), но немного проще и не требует дополнительных вложений в оборудование.

Подготовка

Подходящая среда Linux. Если вы работаете на Windows или macOS, запустите полноценную виртуальную машину (ВМ) Linux с гипервизором, таким как VirtualBox или VMware, и следуйте остальным пунктам этой статьи в ВМ. На Windows также есть возможность использовать WSL2 (WSL1 не будет работать вообще, я уверен), но YMMV.

Эталонным дистрибутивом является Ubuntu 22.04. Если вы используете альтернативный дистрибутив Linux, могут потребоваться изменения в инструкциях, представленных в этой статье (или просто запустите Ubuntu VM в любом случае). Если вы хотите попробовать запустить QEMU непосредственно на Windows / macOS, вы сами справитесь 😉

В любом случае, вы должны быть знакомы с Linux и уметь выполнять простой поиск и устранение неисправностей. Если вы столкнулись с ошибкой типа gpg2: command not found на полпути, мы полностью ожидаем, что вы догадаетесь sudo apt install gnupg2 вместо того, чтобы жаловаться 😉

Установка зависимостей, настройка платы virt для загрузки Ubuntu для RISC-V

Основная статья: RISC-V — Ubuntu Wiki

Обновить метаданные репозитория:

$ sudo apt update
Войти в полноэкранный режим Выйти из полноэкранного режима

Установите необходимые зависимости:

$ sudo apt install qemu-system-misc opensbi u-boot-qemu qemu-utils
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь возьмите сжатый образ предустановленного сервера Ubuntu для SiFive HiFive Unmatched, который также подходит для нашей платы virt:

$ wget https://cdimage.ubuntu.com/releases/22.04/release/ubuntu-22.04.1-preinstalled-server-riscv64+unmatched.img.xz
Войдите в полноэкранный режим Выйти из полноэкранного режима

Распаковать:

$ unxz ubuntu-22.04.1-preinstalled-server-riscv64+unmatched.img.xz
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем загрузить нашу плату virt:

$ qemu-system-riscv64 
    -machine virt 
    -nographic 
    -m 1024 
    -smp 4 
    -bios /usr/lib/riscv64-linux-gnu/opensbi/generic/fw_jump.elf 
    -kernel /usr/lib/u-boot/qemu-riscv64_smode/uboot.elf 
    -device virtio-net-device,netdev=eth0 
    -netdev user,id=eth0 
    -drive file=ubuntu-22.04.1-preinstalled-server-riscv64+unmatched.img,format=raw,if=virtio
Вход в полноэкранный режим Выход из полноэкранного режима

Поскольку это полностью эмулированная плата RISC-V без аппаратного ускорения, нашему гостю Ubuntu RISC-V требуется некоторое время для загрузки и полной инициализации. Пока мы ждем, давайте рассмотрим некоторые опции в приведенной выше команде:

Как только наша плата полностью инициализируется, войдите в систему с именем пользователя ubuntu и паролем ubuntu. При первом входе вам будет предложено изменить пароль.

Теперь, когда мы вошли в систему, все дальнейшие команды в этой статье должны выполняться на нашей плате virt, если не указано иное.

Установка GCC, сборка и установка Cgreen

Codewars использует GCC, предоставляемый дистрибутивом, для сборки решения RISC-V и компиляции тестов, и Cgreen 1.6.0 для модульного тестирования. Чтобы повторить эту установку, мы установим GCC с помощью apt вместе с зависимостями сборки Cgreen и соберем Cgreen 1.6.0 из исходных текстов.

Обновите метаданные репозитория:

$ sudo apt update
Войти в полноэкранный режим Выйти из полноэкранного режима

Установите GCC и сборочные зависимости для Cgreen (g++, make, cmake):

$ sudo apt install -y gcc g++ make cmake
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь давайте git clone Cgreen 1.6.0:

$ git clone --recurse-submodules --branch 1.6.0 https://github.com/cgreen-devs/cgreen
Вход в полноэкранный режим Выход из полноэкранного режима

Технически нам не нужен --recurse-submodules для Cgreen, который рекурсивно извлекает зависимости из Git-репозиториев, но он необходим для других фреймворков для тестирования C, таких как Criterion, и включить эту опцию не помешает.

Введите cgreen/:

$ pushd cgreen/
Вход в полноэкранный режим Выход из полноэкранного режима

Соберите кодовую базу (это может занять некоторое время):

$ make
Войти в полноэкранный режим Выход из полноэкранного режима

Очень важно, чтобы вы не пытались параллельно собирать, например, с помощью -j$(nproc). Я попробовал — вроде бы сборка завершается нормально, но после установки компиляция всего, что связано с юнит-тестами Cgreen, приводит к странным ошибкам компоновщика, которые исчезают после пересборки Cgreen с одним потоком и повторной установки.

Теперь установите:

$ sudo make install
Войдите в полноэкранный режим Выйти из полноэкранного режима

И обновите динамические привязки компоновщика:

$ sudo ldconfig
Войти в полноэкранный режим Выйти из полноэкранного режима

Выходим из дерева исходников Cgreen:

$ popd
Войти в полноэкранный режим Выход из полноэкранного режима

Наконец, быстрый тест, подтверждающий, что Cgreen 1.6.0 установлен правильно:

$ cat > main.c << EOF
#include <cgreen/cgreen.h>

int main(int argc, char **argv) {
  return run_test_suite(create_test_suite(), create_text_reporter());
}
EOF
$ gcc main.c -lcgreen -o main
$ ./main
Войти в полноэкранный режим Выйти из полноэкранного режима

Ожидаемый результат:

Running "main" (0 tests)...
Completed "main": No assertions.
Войти в полноэкранный режим Выйти из полноэкранного режима

Add (вдохновленный Multiply): пример настройки Kata

Установив GCC и Cgreen, мы можем начать решать RISC-V Kata локально. Чтобы не испортить существующие ката, мы создадим гипотетическое ката под названием «Add», в котором нужно исправить синтаксическую ошибку, чтобы функция add(a, b) заработала. Функция add() складывает два числа вместе и возвращает результат.

Установка Codewars (приблизительно) состоит из следующих файлов:

RISC-V на Codewars не поддерживает предварительно загруженный раздел на момент написания статьи (2022-08-13). Существует также Codewars reporter для печати формата вывода Codewars в модульных тестах вместо удобного для человека текстового вывода, но это не имеет значения для локальной разработки нашего решения.

Команда(ы), используемая(ые) для компиляции и выполнения кода, выглядит примерно так:

$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
$ ./tests
Войти в полноэкранный режим Выйти из полноэкранного режима

С помощью tests.c:

#include <cgreen/cgreen.h>

TestSuite *solution_tests();

int main(int argc, char **argv) {
  return run_test_suite(solution_tests(), create_text_reporter());
}
Войти в полноэкранный режим Выход из полноэкранного режима

Давайте начнем со сломанного решения. Можете ли вы заметить ошибку? Сохраните это как solution.s:

.globl add
add:
  addw a0, a1
  ret
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот несколько тестов в solution_tests.c:

#include <cgreen/cgreen.h>

int add(int, int);

Describe(Add);
BeforeEach(Add) {}
AfterEach(Add) {}

Ensure(Add, should_work_for_fixed_tests) {
  assert_that(add(3, 5), is_equal_to(8));
  assert_that(add(-7, 2), is_equal_to(-5));
  assert_that(add(11, -4), is_equal_to(7));
  assert_that(add(-2, -2), is_equal_to(-4));
}

TestSuite *solution_tests() {
  TestSuite *suite = create_test_suite();
  add_test_with_context(suite, Add, should_work_for_fixed_tests);
  return suite;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Попробуйте скомпилировать Kata:

$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
Войти в полноэкранный режим Выйти из полноэкранного режима

Упс — наше решение не удалось скомпилировать. Вот что вы должны увидеть:

solution.s: Assembler messages:
solution.s:3: Error: illegal operands `addw a0,a1'
Войти в полноэкранный режим Выход из полноэкранного режима

Я дам вам немного времени, чтобы разобраться в ошибке.

Теперь замените наше сломанное решение на рабочее в solution.s:

.globl add
add:
  addw a0, a0, a1
  ret
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь скомпилируйте и запустите Kata — тесты должны пройти:

$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
$ ./tests
Войти в полноэкранный режим Выйти из полноэкранного режима

Вывод:

Running "solution_tests" (1 test)...
  "solution_tests": 4 passes in 22ms.
Completed "solution_tests": 4 passes in 32ms.
Войти в полноэкранный режим Выход из полноэкранного режима

Наконец, мы можем делать и случайные тесты, как и для всех остальных поддерживаемых языков в Codewars. Вот обновленный solution_tests.c:

#include <cgreen/cgreen.h>
#include <time.h>

int add(int, int);

Describe(Add);
BeforeEach(Add) {}
AfterEach(Add) {}

Ensure(Add, should_work_for_fixed_tests) {
  assert_that(add(3, 5), is_equal_to(8));
  assert_that(add(-7, 2), is_equal_to(-5));
  assert_that(add(11, -4), is_equal_to(7));
  assert_that(add(-2, -2), is_equal_to(-4));
}

Ensure(Add, should_work_for_random_tests) {
  srand(time(NULL));
  for (int i = 0; i < 100; ++i) {
    int a = rand() % 100, b = rand() % 100;
    int expected = a + b;
    int actual = add(a, b);
    assert_equal_with_message(actual, expected, "Expected add(%d, %d) to equal: %d, instead got: %d", a, b, expected, actual);
  }
}

TestSuite *solution_tests() {
  TestSuite *suite = create_test_suite();
  add_test_with_context(suite, Add, should_work_for_fixed_tests);
  add_test_with_context(suite, Add, should_work_for_random_tests);
  return suite;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Скомпилируйте и запустите Kata снова:

$ gcc solution.s solution_tests.c tests.c -lcgreen -o tests
$ ./tests
Войти в полноэкранный режим Выйти из полноэкранного режима

Выход:

Running "solution_tests" (2 tests)...
  "solution_tests": 104 passes in 49ms.
Completed "solution_tests": 104 passes in 55ms.
Войти в полноэкранный режим Выход из полноэкранного режима

Завершение работы, следующие шаги

Выключите питание платы:

$ sudo systemctl poweroff
Войти в полноэкранный режим Выход из полноэкранного режима

На этом все — надеюсь, вам понравилась статья, и удачных спаррингов!

Вот некоторые возможные дальнейшие шаги, не обязательно связанные с Codewars:

  • Приобрести настоящую, физическую плату RISC-V и следовать этой статье с ней, адаптируя инструкции по мере необходимости.
  • В этой статье мы загрузили готовый дистрибутив Linux, что само по себе интересно, но было бы интереснее скомпилировать собственное ядро и (минимальное) пользовательское пространство и загрузить их на виртуальную (или физическую) плату. Например, загрузить последнее стабильное ядро с https://kernel.org и кросс-компилировать его для RISC-V, сделать то же самое для BusyBox, а затем загрузить нашу плату с ними. Документация по RISC-V дает краткое описание процесса, но вам придется заполнить пробелы, которые, судя по моему ограниченному опыту, не являются тривиальными.

Оцените статью
devanswers.ru
Добавить комментарий