diff --git a/.dockerignore b/.dockerignore index 701b218f5..b35373757 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,7 +14,6 @@ build third-party/cryptopp-CRYPTOPP_7_0_0 third-party/downloads third-party/grpc -third-party/lua-5.3.5 src/cartesi-machine-client src/cartesi-machine-server src/cartesi-machine-hash diff --git a/.github/workflows/Dockerfile b/.github/workflows/Dockerfile index 4e86a53e5..44d8fbae5 100644 --- a/.github/workflows/Dockerfile +++ b/.github/workflows/Dockerfile @@ -6,16 +6,16 @@ RUN apt-get update && \ build-essential wget git \ libreadline-dev libboost-coroutine-dev libboost-context-dev \ libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev \ - ca-certificates automake libtool patchelf cmake pkg-config lua5.3 liblua5.3-dev \ + ca-certificates automake libtool patchelf cmake pkg-config lua5.4 liblua5.4-dev \ luarocks libb64-dev libcrypto++-dev nlohmann-json3-dev && \ rm -rf /var/lib/apt/lists/* -RUN luarocks install luasocket && \ - luarocks install luasec && \ - luarocks install luaposix && \ - luarocks install lpeg && \ - luarocks install md5 && \ - luarocks install dkjson +RUN luarocks install --lua-version=5.4 luasocket && \ + luarocks install --lua-version=5.4 luasec && \ + luarocks install --lua-version=5.4 luaposix && \ + luarocks install --lua-version=5.4 lpeg && \ + luarocks install --lua-version=5.4 md5 && \ + luarocks install --lua-version=5.4 dkjson WORKDIR /usr/src/emulator COPY . . @@ -40,7 +40,7 @@ RUN apt-get update && DEBIAN_FRONTEND="noninteractive" apt-get install -y \ zlib1g \ ca-certificates \ libgomp1 \ - lua5.3 \ + lua5.4 \ genext2fs \ libb64-0d \ libcrypto++8 \ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 674e5b95c..2c3f3e9af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ jobs: - name: Install Ubuntu dependencies run: | sudo apt-get update -y - sudo apt-get install -y libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.3 liblua5.3-dev lua-socket libb64-dev libcrypto++-dev nlohmann-json3-dev + sudo apt-get install -y libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.4 liblua5.4-dev lua-socket libb64-dev libcrypto++-dev nlohmann-json3-dev - name: Download cache of third-party build run: aws s3 sync s3://cartesi-ci/${GITHUB_REPOSITORY}/cache/build-${BUILD_CACHE_VERSION} ./build && test -f ./build/`uname`_`uname -m`/lib/libcryptopp.so.7 @@ -78,12 +78,12 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Install Ubuntu dependencies - run: sudo apt-get update -y && sudo apt-get install -y libboost-context1.74.0 libboost-coroutine1.74.0 libboost-filesystem1.74.0 libboost-log1.74.0 libssl1.1 libc-ares2 zlib1g netcat patchelf lua5.3 lua-socket liblua5.3-dev luarocks libb64-dev libcrypto++-dev nlohmann-json3-dev + run: sudo apt-get update -y && sudo apt-get install -y libboost-context1.74.0 libboost-coroutine1.74.0 libboost-filesystem1.74.0 libboost-log1.74.0 libssl1.1 libc-ares2 zlib1g netcat patchelf lua5.4 lua-socket liblua5.4-dev luarocks libb64-dev libcrypto++-dev nlohmann-json3-dev - name: Install lua test dependencies run: | - sudo luarocks install md5 - sudo luarocks install luaposix + sudo luarocks install --lua-version=5.4 md5 + sudo luarocks install --lua-version=5.4 luaposix - name: Download emulator uses: actions/download-artifact@master @@ -169,17 +169,17 @@ jobs: export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export CARTESI_TESTS_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;" - for x in `find tests -maxdepth 1 -type f -name "*.lua"` ; do echo -n 'CTSICTSI' | lua5.3 $x local; done + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" + for x in `find tests -maxdepth 1 -type f -name "*.lua"` ; do echo -n 'CTSICTSI' | lua5.4 $x local; done - name: Run grpc lua test scripts run: | export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export CARTESI_TESTS_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" ./tests/test-grpc-server.sh remote-cartesi-machine cartesi-machine cartesi-machine-tests $CARTESI_TESTS_PATH - name: Run jsonrpc lua test scripts @@ -187,8 +187,8 @@ jobs: export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export CARTESI_TESTS_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" ./tests/test-jsonrpc-server.sh jsonrpc-remote-cartesi-machine cartesi-machine cartesi-machine-tests $CARTESI_TESTS_PATH - name: Run Merkle tree tests @@ -213,11 +213,11 @@ jobs: - name: Install Ubuntu dependencies run: | sudo apt-get update -y - sudo apt-get install -y libboost-context1.74.0 libboost-coroutine1.74.0 libboost-filesystem1.74.0 libboost-log1.74.0 libssl1.1 libc-ares2 zlib1g lua5.3 lua-socket liblua5.3-dev luarocks libb64-dev libcrypto++-dev nlohmann-json3-dev + sudo apt-get install -y libboost-context1.74.0 libboost-coroutine1.74.0 libboost-filesystem1.74.0 libboost-log1.74.0 libssl1.1 libc-ares2 zlib1g lua5.4 lua-socket liblua5.4-dev luarocks libb64-dev libcrypto++-dev nlohmann-json3-dev - name: Install lua test dependencies run: | - sudo luarocks install luaposix + sudo luarocks install --lua-version=5.4 luaposix - name: Download emulator uses: actions/download-artifact@master @@ -234,8 +234,8 @@ jobs: - name: Test microarchitecture interpreter run: | - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" make riscv-arch-tests JOBS=$(nproc) - name: Download test suite @@ -286,7 +286,11 @@ jobs: - name: Install Ubuntu dependencies run: | sudo apt-get update -y - sudo apt-get install -y libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.3 liblua5.3-dev lua-socket libb64-dev libcrypto++-dev nlohmann-json3-dev + sudo apt-get install -y libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.4 liblua5.4-dev luarocks lua-socket libb64-dev libcrypto++-dev nlohmann-json3-dev + + - name: Install lua test dependencies + run: | + sudo luarocks install --lua-version=5.4 luacheck - name: Download cache of third-party build run: aws s3 sync s3://cartesi-ci/${GITHUB_REPOSITORY}/cache/build-${BUILD_CACHE_VERSION} ./build && test -f ./build/`uname`_`uname -m`/lib/libcryptopp.so.7 @@ -303,9 +307,16 @@ jobs: - name: Setup update-alternatives for clang-format run: sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-14 120 - - name: Check format + - name: Check format (C++) run: make check-format + - name: Check format (Lua) + uses: JohnnyMorganz/stylua-action@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: 0.17.1 + args: --indent-type Spaces --collapse-simple-statement Always --check src + - name: Setup update-alternatives for clang-tidy run: sudo update-alternatives --install /usr/bin/clang-tidy clang-tidy /usr/bin/clang-tidy-14 120 @@ -324,7 +335,7 @@ jobs: - name: Install Ubuntu dependencies run: | sudo apt-get update -y - sudo apt-get install -y build-essential libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.3 liblua5.3-dev luarocks lua-socket clang llvm lcov libb64-dev libcrypto++-dev nlohmann-json3-dev + sudo apt-get install -y build-essential libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.4 liblua5.4-dev luarocks lua-socket clang llvm lcov libb64-dev libcrypto++-dev nlohmann-json3-dev sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12 sudo update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-12 12 @@ -334,8 +345,8 @@ jobs: - name: Install lua test dependencies run: | - sudo luarocks install md5 - sudo luarocks install luaposix + sudo luarocks install --lua-version=5.4 md5 + sudo luarocks install --lua-version=5.4 luaposix - name: Download cache of third-party build run: aws s3 sync s3://cartesi-ci/${GITHUB_REPOSITORY}/cache/build-${BUILD_CACHE_VERSION} ./build && test -f ./build/`uname`_`uname -m`/lib/libcryptopp.so.7 @@ -424,8 +435,8 @@ jobs: export TEST_PATH=/opt/cartesi/tests export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export CARTESI_TESTS_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" make -j1 coverage-toolchain=gcc coverage - name: Show coverage summary @@ -458,7 +469,7 @@ jobs: - name: Install Ubuntu dependencies run: | sudo apt-get update -y - sudo apt-get install -y build-essential libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.3 liblua5.3-dev luarocks lua-socket clang llvm lcov libb64-dev libcrypto++-dev nlohmann-json3-dev + sudo apt-get install -y build-essential libreadline-dev libboost-context-dev libboost-coroutine-dev libboost-filesystem-dev libboost-log-dev libssl-dev libc-ares-dev zlib1g-dev ca-certificates patchelf automake cmake clang-tidy-14 clang-format-14 lua5.4 liblua5.4-dev luarocks lua-socket clang llvm lcov libb64-dev libcrypto++-dev nlohmann-json3-dev sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-12 12 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-12 12 sudo update-alternatives --install /usr/bin/cpp cpp /usr/bin/cpp-12 12 @@ -472,8 +483,8 @@ jobs: - name: Install lua test dependencies run: | - sudo luarocks install md5 - sudo luarocks install luaposix + sudo luarocks install --lua-version=5.4 md5 + sudo luarocks install --lua-version=5.4 luaposix - name: Download cache of third-party build run: aws s3 sync s3://cartesi-ci/${GITHUB_REPOSITORY}/cache/build-${BUILD_CACHE_VERSION} ./build && test -f ./build/`uname`_`uname -m`/lib/libcryptopp.so.7 @@ -562,8 +573,8 @@ jobs: export TEST_PATH=/opt/cartesi/tests export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export CARTESI_TESTS_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;/usr/local/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;/usr/local/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" make sanitize=yes test-all verify: @@ -590,7 +601,7 @@ jobs: find /opt/cartesi/lib -type f -exec chmod 755 {} + - name: Install Ubuntu dependencies - run: sudo apt-get update -y && sudo apt-get install -y libboost-coroutine1.74.0 libboost-context1.74.0 libboost-filesystem1.74.0 libboost-log1.74.0 libreadline8 openssl libc-ares2 zlib1g ca-certificates libgomp1 lua5.3 liblua5.3-dev lua-socket libb64-dev libcrypto++-dev nlohmann-json3-dev + run: sudo apt-get update -y && sudo apt-get install -y libboost-coroutine1.74.0 libboost-context1.74.0 libboost-filesystem1.74.0 libboost-log1.74.0 libreadline8 openssl libc-ares2 zlib1g ca-certificates libgomp1 lua5.4 liblua5.4-dev lua-socket libb64-dev libcrypto++-dev nlohmann-json3-dev - name: Download test suite uses: Legion2/download-release-action@v2.1.0 @@ -646,8 +657,8 @@ jobs: export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export TEST_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" cd ./src make -j$(nproc) CYCLE_PERIOD=1 truth-logs @@ -664,8 +675,8 @@ jobs: export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export TEST_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" cd ./src make -j$(nproc) CYCLE_PERIOD=1 test-logs @@ -687,8 +698,8 @@ jobs: export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export TEST_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" cd ./src make -j$(nproc) CONCURRENCY_MERKLE_TREE=1 CYCLE_PERIOD=1 truth-hashes make -j$(nproc) CONCURRENCY_MERKLE_TREE=1 CYCLE_PERIOD=2 truth-hashes @@ -708,8 +719,8 @@ jobs: export PATH=/opt/cartesi/bin:$PATH export CARTESI_IMAGES_PATH=/opt/cartesi/share/images export TEST_PATH=/opt/cartesi/tests - export LUA_PATH="./?.lua;./tests/?.lua;/opt/cartesi/share/lua/5.3/?.lua;" - export LUA_CPATH="/opt/cartesi/lib/lua/5.3/?.so;" + export LUA_PATH_5_4="/opt/cartesi/share/lua/5.4/?.lua;;" + export LUA_CPATH_5_4="/opt/cartesi/lib/lua/5.4/?.so;;" cd ./src make -j$(nproc) CONCURRENCY_MERKLE_TREE=1 CYCLE_PERIOD=1 test-hashes make -j$(nproc) CONCURRENCY_MERKLE_TREE=1 CYCLE_PERIOD=2 compare-hashes diff --git a/.gitignore b/.gitignore index 104f9a4aa..1efa06d1a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ build third-party/cryptopp-CRYPTOPP_7_0_0 third-party/downloads third-party/grpc -third-party/lua-5.3.5 -third-party/luasocket third-party/mongoose-7.9 src/remote-cartesi-machine-proxy src/remote-cartesi-machine diff --git a/CHANGELOG.md b/CHANGELOG.md index bd7427609..0ace7419d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added + +- Added static analysis for Lua code +- Added code formatter for Lua code +- Added support for to-be-closed variables for machine Lua API + ### Fixed - Fixed compile errors with GCC 13.1 - Fixed Lua path being mixed with different Lua version path +### Changed + +- Updated Lua version to 5.4 +- Use Lua path environment variables specific for version 5.4 +- Refactored and added new Lua tests using a Lua testing framework + ## [0.14.0] - 2023-05-03 ### Added diff --git a/Makefile b/Makefile index b9a74d738..a18ff47f0 100644 --- a/Makefile +++ b/Makefile @@ -6,10 +6,8 @@ BIN_INSTALL_PATH= $(PREFIX)/bin LIB_INSTALL_PATH= $(PREFIX)/lib SHARE_INSTALL_PATH= $(PREFIX)/share IMAGES_INSTALL_PATH= $(SHARE_INSTALL_PATH)/images -CDIR=lib/lua/5.3 -LDIR=share/lua/5.3 -LUA_INSTALL_CPATH= $(PREFIX)/$(CDIR) -LUA_INSTALL_PATH= $(PREFIX)/$(LDIR) +LUA_INSTALL_CPATH= $(PREFIX)/lib/lua/5.4 +LUA_INSTALL_PATH= $(PREFIX)/share/lua/5.4 INC_INSTALL_PATH= $(PREFIX)/include/machine-emulator INSTALL_PLAT = install-$(UNAME) LIBCARTESI_Darwin=libcartesi.dylib @@ -47,8 +45,8 @@ DEPCLEAN := $(addsuffix .clean,$(DEPDIRS)) COREPROTO := lib/grpc-interfaces/core.proto GRPC_VERSION ?= v1.50.0 LUASOCKET_VERSION ?= 5b18e475f38fcf28429b1cc4b17baee3b9793a62 -LUA_DEFAULT_PATHS = ${LUA_INSTALL_PATH}/?.lua -LUA_DEFAULT_C_PATHS = ./?.so;/usr/local/lib/lua/5.3/?.so;/usr/local/share/lua/5.3/?.so;/opt/cartesi/lib/lua/5.3/?.so +LUA_DEFAULT_PATHS = $(LUA_INSTALL_PATH)/?.lua +LUA_DEFAULT_C_PATHS = $(LUA_INSTALL_CPATH)/?.so # Docker image tag TAG ?= devel @@ -103,8 +101,8 @@ $(BUILDDIR) $(BIN_INSTALL_PATH) $(LIB_INSTALL_PATH) $(LUA_INSTALL_PATH) $(LUA_IN env: @echo $(LIBRARY_PATH) @echo "export PATH='$(SRCDIR):$(BUILDDIR)/bin:${PATH}'" - @echo "export LUA_CPATH='./?.so;$(SRCDIR)/?.so;$(BUILDDIR)/$(CDIR)/?.so;$$(lua5.3 -e 'print(package.cpath)')'" - @echo "export LUA_PATH='./?.lua;$(SRCDIR)/?.lua;$(BUILDDIR)/$(LDIR)/?.lua;$$(lua5.3 -e 'print(package.path)')'" + @echo "export LUA_PATH_5_4='$(SRCDIR)/?.lua;$${LUA_PATH_5_4:-;}'" + @echo "export LUA_CPATH_5_4='$(SRCDIR)/?.so;$${LUA_CPATH_5_4:-;}'" doc: cd doc && doxygen Doxyfile @@ -144,7 +142,7 @@ $(COREPROTO): $(info gprc-interfaces submodule not initialized!) @exit 1 grpc: | $(COREPROTO) -hash luacartesi grpc test test-all lint coverage check-format format: +hash luacartesi grpc test test-all lint coverage check-format check-format-lua check-lua format format-lua: @eval $$($(MAKE) -s --no-print-directory env); $(MAKE) -C $(SRCDIR) $@ riscv-arch-tests: @eval $$($(MAKE) -s --no-print-directory env); $(MAKE) -C third-party/riscv-arch-tests diff --git a/README.md b/README.md index a5829f0d3..520af7753 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Docker targets: - GNU Make >= 3.81 - Cryptopp >= 7.0.0 - GRPC 1.38.0 -- Lua 5.3.5 +- Lua >= 5.4.4 - b64 >= 1.2.1 - Boost >= 1.71 - nlohmann JSON >= 3.10 @@ -35,14 +35,14 @@ Obs: Please note that Apple Clang Version number does not follow upstream LLVM/C #### Ubuntu 22.04 ``` -sudo apt-get install build-essential automake libtool patchelf cmake pkg-config wget git libreadline-dev libboost-coroutine-dev libboost-context-dev libboost-filesystem-dev libboost-log-dev libssl-dev openssl libc-ares-dev zlib1g-dev ca-certificates liblua5.3-dev libb64-dev libcrypto++-dev nlohmann-json3-dev luarocks +sudo apt-get install build-essential automake libtool patchelf cmake pkg-config wget git libreadline-dev libboost-coroutine-dev libboost-context-dev libboost-filesystem-dev libboost-log-dev libssl-dev openssl libc-ares-dev zlib1g-dev ca-certificates liblua5.4-dev libb64-dev libcrypto++-dev nlohmann-json3-dev luarocks -sudo luarocks install lpeg -sudo luarocks install dkjson -sudo luarocks install luasocket -sudo luarocks install luasec -sudo luarocks install luaposix -sudo luarocks install md5 +sudo luarocks install --lua-version=5.4 lpeg +sudo luarocks install --lua-version=5.4 dkjson +sudo luarocks install --lua-version=5.4 luasocket +sudo luarocks install --lua-version=5.4 luasec +sudo luarocks install --lua-version=5.4 luaposix +sudo luarocks install --lua-version=5.4 md5 ``` #### MacOS @@ -50,26 +50,26 @@ sudo luarocks install md5 ``` sudo port install clang-14 automake boost libtool wget cmake pkgconfig c-ares zlib openssl lua libb64 libcryptopp nlohmann-json lua-luarocks -sudo luarocks install lpeg -sudo luarocks install dkjson -sudo luarocks install luasocket -sudo luarocks install luasec -sudo luarocks install luaposix -sudo luarocks install md5 +sudo luarocks install --lua-version=5.4 lpeg +sudo luarocks install --lua-version=5.4 dkjson +sudo luarocks install --lua-version=5.4 luasocket +sudo luarocks install --lua-version=5.4 luasec +sudo luarocks install --lua-version=5.4 luaposix +sudo luarocks install --lua-version=5.4 md5 ``` ##### Homebrew ``` -brew install llvm@12 automake boost libomp wget cmake pkg-config c-ares zlib openssl lua@5.3 libb64 nlohmann-json luarocks -luarocks --lua-dir=$(brew --prefix)/opt/lua@5.3 install lpeg -luarocks --lua-dir=$(brew --prefix)/opt/lua@5.3 install dkjson -luarocks --lua-dir=$(brew --prefix)/opt/lua@5.3 install luasocket -luarocks --lua-dir=$(brew --prefix)/opt/lua@5.3 install luasec -luarocks --lua-dir=$(brew --prefix)/opt/lua@5.3 install luaposix -luarocks --lua-dir=$(brew --prefix)/opt/lua@5.3 install md5 +brew install llvm@12 automake boost libomp wget cmake pkg-config c-ares zlib openssl lua@5.4 libb64 nlohmann-json luarocks +luarocks --lua-dir=$(brew --prefix)/opt/lua@5.4 install lpeg +luarocks --lua-dir=$(brew --prefix)/opt/lua@5.4 install dkjson +luarocks --lua-dir=$(brew --prefix)/opt/lua@5.4 install luasocket +luarocks --lua-dir=$(brew --prefix)/opt/lua@5.4 install luasec +luarocks --lua-dir=$(brew --prefix)/opt/lua@5.4 install luaposix +luarocks --lua-dir=$(brew --prefix)/opt/lua@5.4 install md5 ``` -For emulator scripts to work it is expected that `lua5.3` binary is available in the system PATH. If operating system/package manager that you are using provides only `lua` or lua binary named in a different way (e.g. on `Homebrew`), please create symbolic link or alias `lua5.3`. +For emulator scripts to work it is expected that `lua5.4` binary is available in the system PATH. If operating system/package manager that you are using provides only `lua` or lua binary named in a different way (e.g. on `Homebrew`), please create symbolic link or alias `lua5.4`. ###### libcryptopp Homebrew does not have a formula for this library on the official repository, at the time of this writing. diff --git a/src/Makefile b/src/Makefile index 06806b6c2..2c1e4f190 100644 --- a/src/Makefile +++ b/src/Makefile @@ -18,9 +18,9 @@ UNAME:=$(shell uname) BUILDDIR ?= $(abspath ../build/$(UNAME)_$(shell uname -m)) -LUA_INC:=$(shell pkg-config --cflags-only-I lua5.3) -LUA_LIB:=-llua5.3 -LUA_BIN?=lua5.3 +LUA_INC:=$(shell pkg-config --cflags-only-I lua5.4) +LUA_LIB:=-llua5.4 +LUA_BIN?=lua5.4 GRPC_DIR:=../lib/grpc-interfaces PROTOC:=$(BUILDDIR)/bin/protoc @@ -252,6 +252,9 @@ CLANG_FORMAT_UARCH_FILES:=$(filter-out %uarch-printf%,$(strip $(CLANG_FORMAT_UAR CLANG_FORMAT_FILES:=$(wildcard *.cpp) $(wildcard *.c) $(wildcard *.h) $(wildcard *.hpp) $(CLANG_FORMAT_UARCH_FILES) CLANG_FORMAT_FILES:=$(filter-out %.pb.h,$(strip $(CLANG_FORMAT_FILES))) +STYLUA=stylua +STYLUA_FLAGS=--indent-type Spaces --collapse-simple-statement Always + EMPTY:= SPACE:=$(EMPTY) $(EMPTY) CLANG_TIDY_HEADER_FILTER=$(PWD)/($(subst $(SPACE),|,$(LINTER_HEADERS))) @@ -283,7 +286,7 @@ endif all: luacartesi grpc hash c-api jsonrpc-remote-cartesi-machine -.PHONY: all generate use clean test lint format check-format luacartesi grpc hash docker c-api compile_flags.txt +.PHONY: all generate use clean test lint format format-lua check-format check-format-lua luacartesi grpc hash docker c-api compile_flags.txt CARTESI_OBJS:= \ pma-driver.o \ @@ -407,6 +410,7 @@ test-jsonrpc: luacartesi jsonrpc ./tests/test-jsonrpc-server.sh ./jsonrpc-remote-cartesi-machine "$(LUA) ./cartesi-machine.lua" "$(LUA) ./cartesi-machine-tests.lua" "$(TEST_PATH)" "$(LUA)" test-scripts: luacartesi + $(LUA) spec/all-tests.lua for x in `find tests -maxdepth 1 -type f -name "*.lua"` ; do \ echo -n 'CTSICTSI' | $(LUA) $$x local || exit 1; \ done @@ -431,6 +435,15 @@ format: check-format: @$(CLANG_FORMAT) -Werror --dry-run $(CLANG_FORMAT_FILES) +format-lua: + @$(STYLUA) $(STYLUA_FLAGS) . + +check-format-lua: + @$(STYLUA) $(STYLUA_FLAGS) --check . + +check-lua: + luacheck . + fs.ext2: fs/* genext2fs -f -i 512 -b 8192 -d fs fs.ext2 truncate -s %4096 fs.ext2 diff --git a/src/cartesi-machine-stored-hash.lua b/src/cartesi-machine-stored-hash.lua index 521fd3486..d2d3f6767 100755 --- a/src/cartesi-machine-stored-hash.lua +++ b/src/cartesi-machine-stored-hash.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -16,11 +16,12 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local util = require"cartesi.util" +local util = require("cartesi.util") local f = assert( io.open(assert(arg[1], "missing machine name") .. "/hash", "rb"), - string.format("unable to open machine '%s'", tostring(arg[1]))) + string.format("unable to open machine '%s'", tostring(arg[1])) +) local h = assert(f:read("a"), "unable to read hash") f:close() print(util.hexhash(h)) diff --git a/src/cartesi-machine-tests.lua b/src/cartesi-machine-tests.lua index ff1d8f3e4..5bcc9acb1 100755 --- a/src/cartesi-machine-tests.lua +++ b/src/cartesi-machine-tests.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -16,291 +16,291 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require"cartesi" -local util = require"cartesi.util" +local cartesi = require("cartesi") +local util = require("cartesi.util") -- Tests Cases -- format {"ram_image_file", number_of_cycles, halt_payload, yield_payloads} -local tests = { - {"rv64mi-p-access.bin", 144}, - {"rv64mi-p-breakpoint.bin", 115}, - {"rv64mi-p-csr.bin", 297}, - {"rv64mi-p-illegal.bin", 361}, - {"rv64mi-p-ld-misaligned.bin", 369}, - {"rv64mi-p-lh-misaligned.bin", 121}, - {"rv64mi-p-lw-misaligned.bin", 181}, - {"rv64mi-p-ma_addr.bin", 742}, - {"rv64mi-p-ma_fetch.bin", 138}, - {"rv64mi-p-mcsr.bin", 103}, - {"rv64mi-p-sbreak.bin", 111}, - {"rv64mi-p-scall.bin", 95}, - {"rv64mi-p-sd-misaligned.bin", 389}, - {"rv64mi-p-sh-misaligned.bin", 129}, - {"rv64mi-p-sw-misaligned.bin", 185}, - {"rv64si-p-csr.bin", 196}, - {"rv64si-p-dirty.bin", 177}, - {"rv64si-p-icache-alias.bin", 227}, - {"rv64si-p-ma_fetch.bin", 125}, - {"rv64si-p-sbreak.bin", 105}, - {"rv64si-p-scall.bin", 112}, - {"rv64si-p-wfi.bin", 91}, - {"rv64ua-p-amoadd_d.bin", 108}, - {"rv64ua-p-amoadd_w.bin", 105}, - {"rv64ua-p-amoand_d.bin", 105}, - {"rv64ua-p-amoand_w.bin", 104}, - {"rv64ua-p-amomax_d.bin", 104}, - {"rv64ua-p-amomax_w.bin", 104}, - {"rv64ua-p-amomaxu_d.bin", 104}, - {"rv64ua-p-amomaxu_w.bin", 104}, - {"rv64ua-p-amomin_d.bin", 104}, - {"rv64ua-p-amomin_w.bin", 104}, - {"rv64ua-p-amominu_d.bin", 104}, - {"rv64ua-p-amominu_w.bin", 104}, - {"rv64ua-p-amoor_d.bin", 103}, - {"rv64ua-p-amoor_w.bin", 103}, - {"rv64ua-p-amoswap_d.bin", 105}, - {"rv64ua-p-amoswap_w.bin", 104}, - {"rv64ua-p-amoxor_d.bin", 106}, - {"rv64ua-p-amoxor_w.bin", 108}, - {"rv64ua-p-lrsc.bin", 6280}, - {"rv64ua-v-amoadd_d.bin", 10597}, - {"rv64ua-v-amoadd_w.bin", 10594}, - {"rv64ua-v-amoand_d.bin", 10606}, - {"rv64ua-v-amoand_w.bin", 10605}, - {"rv64ua-v-amomax_d.bin", 10587}, - {"rv64ua-v-amomax_w.bin", 10587}, - {"rv64ua-v-amomaxu_d.bin", 10587}, - {"rv64ua-v-amomaxu_w.bin", 10587}, - {"rv64ua-v-amomin_d.bin", 10587}, - {"rv64ua-v-amomin_w.bin", 10587}, - {"rv64ua-v-amominu_d.bin", 10593}, - {"rv64ua-v-amominu_w.bin", 10593}, - {"rv64ua-v-amoor_d.bin", 10586}, - {"rv64ua-v-amoor_w.bin", 10586}, - {"rv64ua-v-amoswap_d.bin", 10606}, - {"rv64ua-v-amoswap_w.bin", 10605}, - {"rv64ua-v-amoxor_d.bin", 10589}, - {"rv64ua-v-amoxor_w.bin", 10591}, - {"rv64ua-v-lrsc.bin", 16763}, - {"rv64ui-p-add.bin", 509}, - {"rv64ui-p-addi.bin", 284}, - {"rv64ui-p-addiw.bin", 281}, - {"rv64ui-p-addw.bin", 504}, - {"rv64ui-p-and.bin", 584}, - {"rv64ui-p-andi.bin", 255}, - {"rv64ui-p-auipc.bin", 98}, - {"rv64ui-p-beq.bin", 330}, - {"rv64ui-p-bge.bin", 348}, - {"rv64ui-p-bgeu.bin", 438}, - {"rv64ui-p-blt.bin", 330}, - {"rv64ui-p-bltu.bin", 416}, - {"rv64ui-p-bne.bin", 330}, - {"rv64ui-p-fence_i.bin", 340}, - {"rv64ui-p-jal.bin", 94}, - {"rv64ui-p-jalr.bin", 154}, - {"rv64ui-p-lb.bin", 292}, - {"rv64ui-p-lbu.bin", 292}, - {"rv64ui-p-ld.bin", 474}, - {"rv64ui-p-lh.bin", 308}, - {"rv64ui-p-lhu.bin", 317}, - {"rv64ui-p-lui.bin", 104}, - {"rv64ui-p-lw.bin", 322}, - {"rv64ui-p-lwu.bin", 356}, - {"rv64ui-p-or.bin", 617}, - {"rv64ui-p-ori.bin", 248}, - {"rv64ui-p-sb.bin", 493}, - {"rv64ui-p-sh.bin", 546}, - {"rv64ui-p-sw.bin", 553}, - {"rv64ui-p-sd.bin", 665}, - {"rv64ui-p-simple.bin", 80}, - {"rv64ui-p-sll.bin", 579}, - {"rv64ui-p-slli.bin", 309}, - {"rv64ui-p-slliw.bin", 316}, - {"rv64ui-p-sllw.bin", 579}, - {"rv64ui-p-slt.bin", 498}, - {"rv64ui-p-slti.bin", 276}, - {"rv64ui-p-sltiu.bin", 276}, - {"rv64ui-p-sltu.bin", 515}, - {"rv64ui-p-sra.bin", 551}, - {"rv64ui-p-srai.bin", 297}, - {"rv64ui-p-sraiw.bin", 343}, - {"rv64ui-p-sraw.bin", 591}, - {"rv64ui-p-srl.bin", 593}, - {"rv64ui-p-srli.bin", 318}, - {"rv64ui-p-srliw.bin", 325}, - {"rv64ui-p-srlw.bin", 585}, - {"rv64ui-p-sub.bin", 500}, - {"rv64ui-p-subw.bin", 496}, - {"rv64ui-p-xor.bin", 612}, - {"rv64ui-p-xori.bin", 246}, - {"rv64ui-v-add.bin", 6777}, - {"rv64ui-v-addi.bin", 6552}, - {"rv64ui-v-addiw.bin", 6549}, - {"rv64ui-v-addw.bin", 6772}, - {"rv64ui-v-and.bin", 6852}, - {"rv64ui-v-andi.bin", 6523}, - {"rv64ui-v-auipc.bin", 6365}, - {"rv64ui-v-beq.bin", 6598}, - {"rv64ui-v-bge.bin", 6616}, - {"rv64ui-v-bgeu.bin", 6706}, - {"rv64ui-v-blt.bin", 6598}, - {"rv64ui-v-bltu.bin", 6684}, - {"rv64ui-v-bne.bin", 6598}, - {"rv64ui-v-fence_i.bin", 10854}, - {"rv64ui-v-jal.bin", 6362}, - {"rv64ui-v-jalr.bin", 6422}, - {"rv64ui-v-lb.bin", 11263}, - {"rv64ui-v-lbu.bin", 11263}, - {"rv64ui-v-ld.bin", 11445}, - {"rv64ui-v-lh.bin", 11279}, - {"rv64ui-v-lhu.bin", 11288}, - {"rv64ui-v-lui.bin", 6372}, - {"rv64ui-v-lw.bin", 11293}, - {"rv64ui-v-lwu.bin", 11327}, - {"rv64ui-v-or.bin", 6885}, - {"rv64ui-v-ori.bin", 6516}, - {"rv64ui-v-sb.bin", 10976}, - {"rv64ui-v-sd.bin", 15851}, - {"rv64ui-v-sh.bin", 11029}, - {"rv64ui-v-simple.bin", 6348}, - {"rv64ui-v-sll.bin", 6847}, - {"rv64ui-v-slli.bin", 6577}, - {"rv64ui-v-slliw.bin", 6584}, - {"rv64ui-v-sllw.bin", 6847}, - {"rv64ui-v-slt.bin", 6766}, - {"rv64ui-v-slti.bin", 6544}, - {"rv64ui-v-sltiu.bin", 6544}, - {"rv64ui-v-sltu.bin", 6783}, - {"rv64ui-v-sra.bin", 6819}, - {"rv64ui-v-srai.bin", 6565}, - {"rv64ui-v-sraiw.bin", 6611}, - {"rv64ui-v-sraw.bin", 11562}, - {"rv64ui-v-srl.bin", 6861}, - {"rv64ui-v-srli.bin", 6586}, - {"rv64ui-v-srliw.bin", 6593}, - {"rv64ui-v-srlw.bin", 6853}, - {"rv64ui-v-sub.bin", 6768}, - {"rv64ui-v-subw.bin", 6764}, - {"rv64ui-v-sw.bin", 11036}, - {"rv64ui-v-xor.bin", 6880}, - {"rv64ui-v-xori.bin", 6514}, - {"rv64um-p-div.bin", 140}, - {"rv64um-p-divu.bin", 146}, - {"rv64um-p-divuw.bin", 138}, - {"rv64um-p-divw.bin", 135}, - {"rv64um-p-mul.bin", 499}, - {"rv64um-p-mulh.bin", 507}, - {"rv64um-p-mulhsu.bin", 507}, - {"rv64um-p-mulhu.bin", 539}, - {"rv64um-p-mulw.bin", 438}, - {"rv64um-p-rem.bin", 139}, - {"rv64um-p-remu.bin", 140}, - {"rv64um-p-remuw.bin", 135}, - {"rv64um-p-remw.bin", 141}, - {"rv64um-v-div.bin", 6408}, - {"rv64um-v-divu.bin", 6414}, - {"rv64um-v-divuw.bin", 6406}, - {"rv64um-v-divw.bin", 6403}, - {"rv64um-v-mul.bin", 6767}, - {"rv64um-v-mulh.bin", 6775}, - {"rv64um-v-mulhsu.bin", 6775}, - {"rv64um-v-mulhu.bin", 6807}, - {"rv64um-v-mulw.bin", 6706}, - {"rv64um-v-rem.bin", 6407}, - {"rv64um-v-remu.bin", 6408}, - {"rv64um-v-remuw.bin", 6403}, - {"rv64um-v-remw.bin", 6409}, --- C extension tests - {"rv64uc-p-rvc.bin", 299}, - {"rv64uc-v-rvc.bin", 15501}, --- float tests - {"rv64uf-p-fadd.bin", 214}, - {"rv64uf-p-fclass.bin", 151}, - {"rv64uf-p-fcmp.bin", 264}, - {"rv64uf-p-fcvt.bin", 156}, - {"rv64uf-p-fcvt_w.bin", 554}, - {"rv64uf-p-fdiv.bin", 175}, - {"rv64uf-p-fmadd.bin", 240}, - {"rv64uf-p-fmin.bin", 318}, - {"rv64uf-p-ldst.bin", 110}, - {"rv64uf-p-move.bin", 259}, - {"rv64uf-p-recoding.bin", 117}, - {"rv64uf-v-fadd.bin", 11183}, - {"rv64uf-v-fclass.bin", 6417}, - {"rv64uf-v-fcmp.bin", 11233}, - {"rv64uf-v-fcvt.bin", 11125}, - {"rv64uf-v-fcvt_w.bin", 16226}, - {"rv64uf-v-fdiv.bin", 11144}, - {"rv64uf-v-fmadd.bin", 11209}, - {"rv64uf-v-fmin.bin", 11287}, - {"rv64uf-v-ldst.bin", 10625}, - {"rv64uf-v-move.bin", 6525}, - {"rv64uf-v-recoding.bin", 11086}, - {"rv64ud-p-fadd.bin", 214}, - {"rv64ud-p-fclass.bin", 157}, - {"rv64ud-p-fcmp.bin", 264}, - {"rv64ud-p-fcvt.bin", 196}, - {"rv64ud-p-fcvt_w.bin", 614}, - {"rv64ud-p-fdiv.bin", 188}, - {"rv64ud-p-fmadd.bin", 240}, - {"rv64ud-p-fmin.bin", 318}, - {"rv64ud-p-ldst.bin", 129}, - {"rv64ud-p-move.bin", 1034}, - {"rv64ud-p-recoding.bin", 142}, - {"rv64ud-p-structural.bin", 207}, - {"rv64ud-v-fadd.bin", 11183}, - {"rv64ud-v-fclass.bin", 6423}, - {"rv64ud-v-fcmp.bin", 11233}, - {"rv64ud-v-fcvt.bin", 11165}, - {"rv64ud-v-fcvt_w.bin", 16286}, - {"rv64ud-v-fdiv.bin", 11157}, - {"rv64ud-v-fmadd.bin", 11209}, - {"rv64ud-v-fmin.bin", 11287}, - {"rv64ud-v-ldst.bin", 10620}, - {"rv64ud-v-move.bin", 12003}, - {"rv64ud-v-recoding.bin", 10663}, - {"rv64ud-v-structural.bin", 6473}, - {"fclass.bin", 457}, - {"fcvt.bin", 17614}, - {"fcmp.bin", 46787}, - {"funary.bin", 2834}, - {"fbinary_s.bin", 204284}, - {"fbinary_d.bin", 204284}, - {"fternary_s.bin", 216784}, - {"fternary_d.bin", 216784}, --- cartesi tests - {"ebreak.bin", 21}, - {"pte_reserved_exception.bin", 34}, - {"sd_pma_overflow.bin", 16}, - {"xpie_exceptions.bin", 51}, - {"dont_write_x0.bin", 68}, - {"mcycle_write.bin", 18}, - {"lrsc_semantics.bin", 35}, - {"csr_counters.bin", 741}, - {"csr_semantics.bin", 382}, - {"amo.bin", 166}, - {"access.bin", 101}, - {"interrupts.bin", 8209}, - {"mtime_interrupt.bin", 16404}, - {"illegal_insn.bin", 976}, - {"version_check.bin", 30}, - {"translate_vaddr.bin", 347}, - {"htif_invalid_ops.bin", 113}, - {"clint_ops.bin", 137}, - {"shadow_ops.bin", 118}, - {"compressed.bin", 414}, +local riscv_tests = { + { "rv64mi-p-access.bin", 144 }, + { "rv64mi-p-breakpoint.bin", 115 }, + { "rv64mi-p-csr.bin", 297 }, + { "rv64mi-p-illegal.bin", 361 }, + { "rv64mi-p-ld-misaligned.bin", 369 }, + { "rv64mi-p-lh-misaligned.bin", 121 }, + { "rv64mi-p-lw-misaligned.bin", 181 }, + { "rv64mi-p-ma_addr.bin", 742 }, + { "rv64mi-p-ma_fetch.bin", 138 }, + { "rv64mi-p-mcsr.bin", 103 }, + { "rv64mi-p-sbreak.bin", 111 }, + { "rv64mi-p-scall.bin", 95 }, + { "rv64mi-p-sd-misaligned.bin", 389 }, + { "rv64mi-p-sh-misaligned.bin", 129 }, + { "rv64mi-p-sw-misaligned.bin", 185 }, + { "rv64si-p-csr.bin", 196 }, + { "rv64si-p-dirty.bin", 177 }, + { "rv64si-p-icache-alias.bin", 227 }, + { "rv64si-p-ma_fetch.bin", 125 }, + { "rv64si-p-sbreak.bin", 105 }, + { "rv64si-p-scall.bin", 112 }, + { "rv64si-p-wfi.bin", 91 }, + { "rv64ua-p-amoadd_d.bin", 108 }, + { "rv64ua-p-amoadd_w.bin", 105 }, + { "rv64ua-p-amoand_d.bin", 105 }, + { "rv64ua-p-amoand_w.bin", 104 }, + { "rv64ua-p-amomax_d.bin", 104 }, + { "rv64ua-p-amomax_w.bin", 104 }, + { "rv64ua-p-amomaxu_d.bin", 104 }, + { "rv64ua-p-amomaxu_w.bin", 104 }, + { "rv64ua-p-amomin_d.bin", 104 }, + { "rv64ua-p-amomin_w.bin", 104 }, + { "rv64ua-p-amominu_d.bin", 104 }, + { "rv64ua-p-amominu_w.bin", 104 }, + { "rv64ua-p-amoor_d.bin", 103 }, + { "rv64ua-p-amoor_w.bin", 103 }, + { "rv64ua-p-amoswap_d.bin", 105 }, + { "rv64ua-p-amoswap_w.bin", 104 }, + { "rv64ua-p-amoxor_d.bin", 106 }, + { "rv64ua-p-amoxor_w.bin", 108 }, + { "rv64ua-p-lrsc.bin", 6280 }, + { "rv64ua-v-amoadd_d.bin", 10597 }, + { "rv64ua-v-amoadd_w.bin", 10594 }, + { "rv64ua-v-amoand_d.bin", 10606 }, + { "rv64ua-v-amoand_w.bin", 10605 }, + { "rv64ua-v-amomax_d.bin", 10587 }, + { "rv64ua-v-amomax_w.bin", 10587 }, + { "rv64ua-v-amomaxu_d.bin", 10587 }, + { "rv64ua-v-amomaxu_w.bin", 10587 }, + { "rv64ua-v-amomin_d.bin", 10587 }, + { "rv64ua-v-amomin_w.bin", 10587 }, + { "rv64ua-v-amominu_d.bin", 10593 }, + { "rv64ua-v-amominu_w.bin", 10593 }, + { "rv64ua-v-amoor_d.bin", 10586 }, + { "rv64ua-v-amoor_w.bin", 10586 }, + { "rv64ua-v-amoswap_d.bin", 10606 }, + { "rv64ua-v-amoswap_w.bin", 10605 }, + { "rv64ua-v-amoxor_d.bin", 10589 }, + { "rv64ua-v-amoxor_w.bin", 10591 }, + { "rv64ua-v-lrsc.bin", 16763 }, + { "rv64ui-p-add.bin", 509 }, + { "rv64ui-p-addi.bin", 284 }, + { "rv64ui-p-addiw.bin", 281 }, + { "rv64ui-p-addw.bin", 504 }, + { "rv64ui-p-and.bin", 584 }, + { "rv64ui-p-andi.bin", 255 }, + { "rv64ui-p-auipc.bin", 98 }, + { "rv64ui-p-beq.bin", 330 }, + { "rv64ui-p-bge.bin", 348 }, + { "rv64ui-p-bgeu.bin", 438 }, + { "rv64ui-p-blt.bin", 330 }, + { "rv64ui-p-bltu.bin", 416 }, + { "rv64ui-p-bne.bin", 330 }, + { "rv64ui-p-fence_i.bin", 340 }, + { "rv64ui-p-jal.bin", 94 }, + { "rv64ui-p-jalr.bin", 154 }, + { "rv64ui-p-lb.bin", 292 }, + { "rv64ui-p-lbu.bin", 292 }, + { "rv64ui-p-ld.bin", 474 }, + { "rv64ui-p-lh.bin", 308 }, + { "rv64ui-p-lhu.bin", 317 }, + { "rv64ui-p-lui.bin", 104 }, + { "rv64ui-p-lw.bin", 322 }, + { "rv64ui-p-lwu.bin", 356 }, + { "rv64ui-p-or.bin", 617 }, + { "rv64ui-p-ori.bin", 248 }, + { "rv64ui-p-sb.bin", 493 }, + { "rv64ui-p-sh.bin", 546 }, + { "rv64ui-p-sw.bin", 553 }, + { "rv64ui-p-sd.bin", 665 }, + { "rv64ui-p-simple.bin", 80 }, + { "rv64ui-p-sll.bin", 579 }, + { "rv64ui-p-slli.bin", 309 }, + { "rv64ui-p-slliw.bin", 316 }, + { "rv64ui-p-sllw.bin", 579 }, + { "rv64ui-p-slt.bin", 498 }, + { "rv64ui-p-slti.bin", 276 }, + { "rv64ui-p-sltiu.bin", 276 }, + { "rv64ui-p-sltu.bin", 515 }, + { "rv64ui-p-sra.bin", 551 }, + { "rv64ui-p-srai.bin", 297 }, + { "rv64ui-p-sraiw.bin", 343 }, + { "rv64ui-p-sraw.bin", 591 }, + { "rv64ui-p-srl.bin", 593 }, + { "rv64ui-p-srli.bin", 318 }, + { "rv64ui-p-srliw.bin", 325 }, + { "rv64ui-p-srlw.bin", 585 }, + { "rv64ui-p-sub.bin", 500 }, + { "rv64ui-p-subw.bin", 496 }, + { "rv64ui-p-xor.bin", 612 }, + { "rv64ui-p-xori.bin", 246 }, + { "rv64ui-v-add.bin", 6777 }, + { "rv64ui-v-addi.bin", 6552 }, + { "rv64ui-v-addiw.bin", 6549 }, + { "rv64ui-v-addw.bin", 6772 }, + { "rv64ui-v-and.bin", 6852 }, + { "rv64ui-v-andi.bin", 6523 }, + { "rv64ui-v-auipc.bin", 6365 }, + { "rv64ui-v-beq.bin", 6598 }, + { "rv64ui-v-bge.bin", 6616 }, + { "rv64ui-v-bgeu.bin", 6706 }, + { "rv64ui-v-blt.bin", 6598 }, + { "rv64ui-v-bltu.bin", 6684 }, + { "rv64ui-v-bne.bin", 6598 }, + { "rv64ui-v-fence_i.bin", 10854 }, + { "rv64ui-v-jal.bin", 6362 }, + { "rv64ui-v-jalr.bin", 6422 }, + { "rv64ui-v-lb.bin", 11263 }, + { "rv64ui-v-lbu.bin", 11263 }, + { "rv64ui-v-ld.bin", 11445 }, + { "rv64ui-v-lh.bin", 11279 }, + { "rv64ui-v-lhu.bin", 11288 }, + { "rv64ui-v-lui.bin", 6372 }, + { "rv64ui-v-lw.bin", 11293 }, + { "rv64ui-v-lwu.bin", 11327 }, + { "rv64ui-v-or.bin", 6885 }, + { "rv64ui-v-ori.bin", 6516 }, + { "rv64ui-v-sb.bin", 10976 }, + { "rv64ui-v-sd.bin", 15851 }, + { "rv64ui-v-sh.bin", 11029 }, + { "rv64ui-v-simple.bin", 6348 }, + { "rv64ui-v-sll.bin", 6847 }, + { "rv64ui-v-slli.bin", 6577 }, + { "rv64ui-v-slliw.bin", 6584 }, + { "rv64ui-v-sllw.bin", 6847 }, + { "rv64ui-v-slt.bin", 6766 }, + { "rv64ui-v-slti.bin", 6544 }, + { "rv64ui-v-sltiu.bin", 6544 }, + { "rv64ui-v-sltu.bin", 6783 }, + { "rv64ui-v-sra.bin", 6819 }, + { "rv64ui-v-srai.bin", 6565 }, + { "rv64ui-v-sraiw.bin", 6611 }, + { "rv64ui-v-sraw.bin", 11562 }, + { "rv64ui-v-srl.bin", 6861 }, + { "rv64ui-v-srli.bin", 6586 }, + { "rv64ui-v-srliw.bin", 6593 }, + { "rv64ui-v-srlw.bin", 6853 }, + { "rv64ui-v-sub.bin", 6768 }, + { "rv64ui-v-subw.bin", 6764 }, + { "rv64ui-v-sw.bin", 11036 }, + { "rv64ui-v-xor.bin", 6880 }, + { "rv64ui-v-xori.bin", 6514 }, + { "rv64um-p-div.bin", 140 }, + { "rv64um-p-divu.bin", 146 }, + { "rv64um-p-divuw.bin", 138 }, + { "rv64um-p-divw.bin", 135 }, + { "rv64um-p-mul.bin", 499 }, + { "rv64um-p-mulh.bin", 507 }, + { "rv64um-p-mulhsu.bin", 507 }, + { "rv64um-p-mulhu.bin", 539 }, + { "rv64um-p-mulw.bin", 438 }, + { "rv64um-p-rem.bin", 139 }, + { "rv64um-p-remu.bin", 140 }, + { "rv64um-p-remuw.bin", 135 }, + { "rv64um-p-remw.bin", 141 }, + { "rv64um-v-div.bin", 6408 }, + { "rv64um-v-divu.bin", 6414 }, + { "rv64um-v-divuw.bin", 6406 }, + { "rv64um-v-divw.bin", 6403 }, + { "rv64um-v-mul.bin", 6767 }, + { "rv64um-v-mulh.bin", 6775 }, + { "rv64um-v-mulhsu.bin", 6775 }, + { "rv64um-v-mulhu.bin", 6807 }, + { "rv64um-v-mulw.bin", 6706 }, + { "rv64um-v-rem.bin", 6407 }, + { "rv64um-v-remu.bin", 6408 }, + { "rv64um-v-remuw.bin", 6403 }, + { "rv64um-v-remw.bin", 6409 }, + -- C extension tests + { "rv64uc-p-rvc.bin", 299 }, + { "rv64uc-v-rvc.bin", 15501 }, + -- float tests + { "rv64uf-p-fadd.bin", 214 }, + { "rv64uf-p-fclass.bin", 151 }, + { "rv64uf-p-fcmp.bin", 264 }, + { "rv64uf-p-fcvt.bin", 156 }, + { "rv64uf-p-fcvt_w.bin", 554 }, + { "rv64uf-p-fdiv.bin", 175 }, + { "rv64uf-p-fmadd.bin", 240 }, + { "rv64uf-p-fmin.bin", 318 }, + { "rv64uf-p-ldst.bin", 110 }, + { "rv64uf-p-move.bin", 259 }, + { "rv64uf-p-recoding.bin", 117 }, + { "rv64uf-v-fadd.bin", 11183 }, + { "rv64uf-v-fclass.bin", 6417 }, + { "rv64uf-v-fcmp.bin", 11233 }, + { "rv64uf-v-fcvt.bin", 11125 }, + { "rv64uf-v-fcvt_w.bin", 16226 }, + { "rv64uf-v-fdiv.bin", 11144 }, + { "rv64uf-v-fmadd.bin", 11209 }, + { "rv64uf-v-fmin.bin", 11287 }, + { "rv64uf-v-ldst.bin", 10625 }, + { "rv64uf-v-move.bin", 6525 }, + { "rv64uf-v-recoding.bin", 11086 }, + { "rv64ud-p-fadd.bin", 214 }, + { "rv64ud-p-fclass.bin", 157 }, + { "rv64ud-p-fcmp.bin", 264 }, + { "rv64ud-p-fcvt.bin", 196 }, + { "rv64ud-p-fcvt_w.bin", 614 }, + { "rv64ud-p-fdiv.bin", 188 }, + { "rv64ud-p-fmadd.bin", 240 }, + { "rv64ud-p-fmin.bin", 318 }, + { "rv64ud-p-ldst.bin", 129 }, + { "rv64ud-p-move.bin", 1034 }, + { "rv64ud-p-recoding.bin", 142 }, + { "rv64ud-p-structural.bin", 207 }, + { "rv64ud-v-fadd.bin", 11183 }, + { "rv64ud-v-fclass.bin", 6423 }, + { "rv64ud-v-fcmp.bin", 11233 }, + { "rv64ud-v-fcvt.bin", 11165 }, + { "rv64ud-v-fcvt_w.bin", 16286 }, + { "rv64ud-v-fdiv.bin", 11157 }, + { "rv64ud-v-fmadd.bin", 11209 }, + { "rv64ud-v-fmin.bin", 11287 }, + { "rv64ud-v-ldst.bin", 10620 }, + { "rv64ud-v-move.bin", 12003 }, + { "rv64ud-v-recoding.bin", 10663 }, + { "rv64ud-v-structural.bin", 6473 }, + { "fclass.bin", 457 }, + { "fcvt.bin", 17614 }, + { "fcmp.bin", 46787 }, + { "funary.bin", 2834 }, + { "fbinary_s.bin", 204284 }, + { "fbinary_d.bin", 204284 }, + { "fternary_s.bin", 216784 }, + { "fternary_d.bin", 216784 }, + -- cartesi tests + { "ebreak.bin", 21 }, + { "pte_reserved_exception.bin", 34 }, + { "sd_pma_overflow.bin", 16 }, + { "xpie_exceptions.bin", 51 }, + { "dont_write_x0.bin", 68 }, + { "mcycle_write.bin", 18 }, + { "lrsc_semantics.bin", 35 }, + { "csr_counters.bin", 741 }, + { "csr_semantics.bin", 382 }, + { "amo.bin", 166 }, + { "access.bin", 101 }, + { "interrupts.bin", 8209 }, + { "mtime_interrupt.bin", 16404 }, + { "illegal_insn.bin", 976 }, + { "version_check.bin", 30 }, + { "translate_vaddr.bin", 347 }, + { "htif_invalid_ops.bin", 113 }, + { "clint_ops.bin", 137 }, + { "shadow_ops.bin", 118 }, + { "compressed.bin", 414 }, } local log_proofs = false local log_annotations = false -- Microarchitecture configuration -local uarch = nil - +local uarch -- Print help and exit local function help() - io.stderr:write(string.format([=[ + io.stderr:write(string.format( + [=[ Usage: %s [options] @@ -394,17 +394,20 @@ or a left shift (e.g., 2 << 20). can be a host name, IPv4 or IPv6 address. -]=], arg[0])) +]=], + arg[0] + )) os.exit() end local test_path = "./" local test_pattern = ".*" +local protocol local remote_protocol = "grpc" -local remote_address = nil -local checkin_address = nil -local remote = nil -local output = nil +local remote_address +local checkin_address +local remote +local output local jobs = 1 local json_list = false local periodic_action = false @@ -421,121 +424,167 @@ local cleanup = {} -- if callback returns true, the option is accepted. -- if callback returns false, the option is rejected. local options = { - { "^%-%-h$", function(all) - if not all then return false end - help() - end }, - { "^%-%-help$", function(all) - if not all then return false end - help() - end }, - { "^%-%-remote%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - remote_address = o - return true - end }, - { "^%-%-remote%-protocol%=(.*)$", function(o) - if not o or #o < 1 then return false end - remote_protocol = o - return true - end }, - { "^%-%-checkin%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - checkin_address = o - return true - end }, - { "^%-%-output%=(.*)$", function(o) - if not o or #o < 1 then return false end - output = o - return true - end }, - { "^%-%-json%-test%-list$", function(all) - if not all then return false end - json_list = true - return true - end }, - { "^%-%-test%-path%=(.*)$", function(o) - if not o or #o < 1 then return false end - test_path = o - return true - end }, - { "^%-%-test%=(.*)$", function(o, a) - if not o or #o < 1 then return false end - test_pattern = o - return true - end }, - { "^%-%-jobs%=([0-9]+)$", function(o, a) - if not o or #o < 1 then return false end - jobs = tonumber(o) - assert(jobs and jobs >= 1, 'invalid number of jobs') - return true - end }, - { "^%-%-log%-proofs$", function(o, a) - if not o then return false end - log_proofs = true - return true - end }, - { "^%-%-log%-annotations$", function(o, a) - if not o then return false end - log_annotations = true - return true - end }, - { "^(%-%-periodic%-action%=(.*))$", function(all, v) - if not v then return false end - string.gsub(v, "^([^%,]+),(.+)$", function(p, s) - periodic_action_period = assert(util.parse_number(p), "invalid period " .. all) - periodic_action_start = assert(util.parse_number(s), "invalid start " .. all) - end) - if periodic_action_period == math.maxinteger then - periodic_action_period = assert(util.parse_number(v), "invalid period " .. all) - periodic_action_start = 0 - end - assert(periodic_action_period > 0, "invalid period " .. - periodic_action_period) - periodic_action = true - return true - end }, - { "^(%-%-concurrency%=(.+))$", function(all, opts) - if not opts then return false end - local c = util.parse_options(opts, { - update_merkle_tree = true - }) - c.update_merkle_tree = assert(util.parse_number(c.update_merkle_tree), - "invalid update_merkle_tree number in " .. all) - concurrency_update_merkle_tree = c.update_merkle_tree - return true - end }, - { "^%-%-uarch%-ram%-image%=(.*)$", function(o) - if not o or #o < 1 then return false end - uarch = uarch or {} - uarch.ram = uarch.ram or {} - uarch.ram.image_filename = o - return true - end }, - { "^%-%-uarch%-ram%-length%=(.+)$", function(n) - if not n then return false end - uarch = uarch or {} - uarch.ram = uarch.ram or {} - uarch.ram.length = assert(util.parse_number(n), "invalid microarchitecture RAM length " .. n) - return true - end }, - { ".*", function(all) - error("unrecognized option " .. all) - end } + { + "^%-%-h$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-help$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-remote%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + remote_address = o + return true + end, + }, + { + "^%-%-remote%-protocol%=(.*)$", + function(o) + if not o or #o < 1 then return false end + remote_protocol = o + return true + end, + }, + { + "^%-%-checkin%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + checkin_address = o + return true + end, + }, + { + "^%-%-output%=(.*)$", + function(o) + if not o or #o < 1 then return false end + output = o + return true + end, + }, + { + "^%-%-json%-test%-list$", + function(all) + if not all then return false end + json_list = true + return true + end, + }, + { + "^%-%-test%-path%=(.*)$", + function(o) + if not o or #o < 1 then return false end + test_path = o + return true + end, + }, + { + "^%-%-test%=(.*)$", + function(o) + if not o or #o < 1 then return false end + test_pattern = o + return true + end, + }, + { + "^%-%-jobs%=([0-9]+)$", + function(o) + if not o or #o < 1 then return false end + jobs = tonumber(o) + assert(jobs and jobs >= 1, "invalid number of jobs") + return true + end, + }, + { + "^%-%-log%-proofs$", + function(o) + if not o then return false end + log_proofs = true + return true + end, + }, + { + "^%-%-log%-annotations$", + function(o) + if not o then return false end + log_annotations = true + return true + end, + }, + { + "^(%-%-periodic%-action%=(.*))$", + function(all, v) + if not v then return false end + string.gsub(v, "^([^%,]+),(.+)$", function(p, s) + periodic_action_period = assert(util.parse_number(p), "invalid period " .. all) + periodic_action_start = assert(util.parse_number(s), "invalid start " .. all) + end) + if periodic_action_period == math.maxinteger then + periodic_action_period = assert(util.parse_number(v), "invalid period " .. all) + periodic_action_start = 0 + end + assert(periodic_action_period > 0, "invalid period " .. periodic_action_period) + periodic_action = true + return true + end, + }, + { + "^(%-%-concurrency%=(.+))$", + function(all, opts) + if not opts then return false end + local c = util.parse_options(opts, { + update_merkle_tree = true, + }) + c.update_merkle_tree = + assert(util.parse_number(c.update_merkle_tree), "invalid update_merkle_tree number in " .. all) + concurrency_update_merkle_tree = c.update_merkle_tree + return true + end, + }, + { + "^%-%-uarch%-ram%-image%=(.*)$", + function(o) + if not o or #o < 1 then return false end + uarch = uarch or {} + uarch.ram = uarch.ram or {} + uarch.ram.image_filename = o + return true + end, + }, + { + "^%-%-uarch%-ram%-length%=(.+)$", + function(n) + if not n then return false end + uarch = uarch or {} + uarch.ram = uarch.ram or {} + uarch.ram.length = assert(util.parse_number(n), "invalid microarchitecture RAM length " .. n) + return true + end, + }, + { + ".*", + function(all) error("unrecognized option " .. all) end, + }, } local values = {} -- Process command line options -for i, argument in ipairs({...}) do - if argument:sub(1,1) == "-" then - for j, option in ipairs(options) do - if option[2](argument:match(option[1])) then - break - end +for _, argument in ipairs({ ... }) do + if argument:sub(1, 1) == "-" then + for _, option in ipairs(options) do + if option[2](argument:match(option[1])) then break end end else - values[#values+1] = argument + values[#values + 1] = argument end end @@ -544,23 +593,14 @@ assert(test_path, "missing test path") if remote_address then protocol = require("cartesi." .. remote_protocol) - if remote_protocol == "grpc" then - assert(checkin_address, "checkin address missing") - end + if remote_protocol == "grpc" then assert(checkin_address, "checkin address missing") end end -local function nothing() -end - -local function advance_machine(machine, max_mcycle) - return machine:run(max_mcycle) -end +local function advance_machine(machine, max_mcycle) return machine:run(max_mcycle) end -local function run_machine(machine, ctx, max_mcycle, callback, advance_machine_fn) - callback = callback or nothing +local function run_machine(machine, ctx, max_mcycle, advance_machine_fn) advance_machine_fn = advance_machine_fn or advance_machine local mcycle = machine:read_mcycle() - local total_cycles = 0 while math.ult(mcycle, max_mcycle) do advance_machine_fn(machine, max_mcycle) mcycle = machine:read_mcycle() @@ -570,24 +610,24 @@ local function run_machine(machine, ctx, max_mcycle, callback, advance_machine_f return machine:read_mcycle() end -local function advance_machine_with_uarch(machine, max_mcycle) - if machine:run_uarch() == cartesi.UARCH_BREAK_REASON_HALTED then - machine:reset_uarch_state() - end +local function advance_machine_with_uarch(machine) + if machine:run_uarch() == cartesi.UARCH_BREAK_REASON_HALTED then machine:reset_uarch_state() end end -local function run_machine_with_uarch(machine, ctx, max_mcycle, callback) - return run_machine(machine, ctx, max_mcycle, callback, advance_machine_with_uarch) +local function run_machine_with_uarch(machine, ctx, max_mcycle) + return run_machine(machine, ctx, max_mcycle, advance_machine_with_uarch) end local function connect() - local remote = protocol.stub(remote_address, checkin_address) - local version = assert(remote.get_version(), - "could not connect to remote cartesi machine at " .. remote_address) - local shutdown = function() remote.shutdown() end - local mt = { __gc = function() pcall(shutdown) end} + local remote_stub = protocol.stub(remote_address, checkin_address) + local version = + assert(remote_stub.get_version(), "could not connect to remote cartesi machine at " .. remote_address) + local shutdown = function() remote_stub.shutdown() end + local mt = { + __gc = function() pcall(shutdown) end, + } setmetatable(cleanup, mt) - return remote, version + return remote_stub, version end local function build_machine(test_name) @@ -596,70 +636,75 @@ local function build_machine(test_name) -- Request automatic default values for versioning CSRs mimpid = -1, marchid = -1, - mvendorid = -1 + mvendorid = -1, }, rom = { - image_filename = test_path .. "/bootstrap.bin" + image_filename = test_path .. "/bootstrap.bin", }, ram = { length = 32 << 20, - image_filename = test_path .. "/" .. test_name + image_filename = test_path .. "/" .. test_name, }, htif = { console_getchar = false, yield_progress = false, - yield_rollup = false + yield_rollup = false, }, - flash_drive = {{ - start = 0x80000000000000, - length = 0x40000, - }} + flash_drive = { { + start = 0x80000000000000, + length = 0x40000, + } }, } - if uarch then - config.uarch = uarch - end + if uarch then config.uarch = uarch end local runtime = { concurrency = { - update_merkle_tree = concurrency_update_merkle_tree - } + update_merkle_tree = concurrency_update_merkle_tree, + }, } if remote_address then - if not remote then remote = connect() end - return assert(remote.machine(config, runtime)) + if not remote then remote = connect() end + return assert(remote.machine(config, runtime)) end return assert(cartesi.machine(config, runtime)) end -local function destroy_machine(machine) - machine:destroy() -end +local function destroy_machine(machine) machine:destroy() end local function print_machine(test_name, expected_cycles) if not uarch then - print( - string.format( - "./cartesi-machine.lua --ram-length=32Mi --rom-image='%s' --ram-image='%s' --no-rom-bootargs --max-mcycle=%d ", - test_path .. "/bootstrap.bin", - test_path .. "/" .. test_name, - 2*expected_cycles - ) - ) + print(string.format( + "./cartesi-machine.lua \ + --ram-length=32Mi\ + --rom-image='%s'\ + --ram-image='%s'\ + --no-rom-bootargs\ + --max-mcycle=%d ", + test_path .. "/bootstrap.bin", + test_path .. "/" .. test_name, + 2 * expected_cycles + )) else print( string.format( - "./cartesi-machine.lua --ram-length=32Mi --rom-image='%s' --ram-image='%s' --no-rom-bootargs --uarch-ram-length=%d --uarch-ram-image=%s --max-mcycle=%d ", + "./cartesi-machine.lua \ + --ram-length=32Mi\ + --rom-image='%s'\ + --ram-image='%s'\ + --no-rom-bootargs\ + --uarch-ram-length=%d\ + --uarch-ram-image=%s\ + --max-mcycle=%d ", test_path .. "/bootstrap.bin", test_path .. "/" .. test_name, uarch.ram.length, uarch.ram.image_filename, - 2*expected_cycles + 2 * expected_cycles ) ) end end -local function add_error(ctx, msg, ...) - local ram_image = ctx.ram_image +local function add_error(ctx, msg, ...) local e = string.format(msg, ...) ctx.failed = true ctx.errors[#ctx.errors + 1] = e @@ -668,17 +713,22 @@ end local function check_test_result(ctx) io.write(ctx.ram_image, ": ") if #ctx.expected_yield_payloads ~= (ctx.yield_payload_index - 1) then - add_error(ctx, "yielded %d times, expected %d", ctx.yield_payload_index-1, #ctx.expected_yield_payloads) + add_error(ctx, "yielded %d times, expected %d", ctx.yield_payload_index - 1, #ctx.expected_yield_payloads) end if ctx.read_htif_tohost_data >> 1 ~= ctx.expected_halt_payload then - add_error(ctx, "returned halt payload %d, expected %d", ctx.read_htif_tohost_data >> 1, ctx.expected_halt_payload) + add_error( + ctx, + "returned halt payload %d, expected %d", + ctx.read_htif_tohost_data >> 1, + ctx.expected_halt_payload + ) end if ctx.cycles ~= ctx.expected_cycles then add_error(ctx, "terminated with mcycle = %d, expected %d", ctx.cycles, ctx.expected_cycles) end if ctx.failed then print("failed") - for _,e in pairs(ctx.errors) do + for _, e in pairs(ctx.errors) do print(string.format("%s: %s", ctx.ram_image, e)) end else @@ -687,13 +737,13 @@ local function check_test_result(ctx) end local function run_parallel(contexts) - local unistd = require 'posix.unistd' - local syswait = require 'posix.sys.wait' + local unistd = require("posix.unistd") + local syswait = require("posix.sys.wait") local pids = {} local running_jobs = 0 -- sort to run slower tests first to maximize utilization of CPU cores - table.sort(tests, function(a ,b) return b[2] < a[2] end) - for _,ctx in ipairs(contexts) do + table.sort(contexts, function(a, b) return b.expected_cycles < a.expected_cycles end) + for _, ctx in ipairs(contexts) do do -- run test in parallel local pid = assert(unistd.fork()) if pid == 0 then -- child @@ -710,10 +760,8 @@ local function run_parallel(contexts) while running_jobs >= jobs do -- wait a child to finish local pid, reason, exitcode = syswait.wait(-1) - if pid and pid > 0 and reason ~= 'running' then - if not (reason == 'exited' and exitcode == 0) then - add_error("unexpected child process exit") - end + if pid and pid > 0 and reason ~= "running" then + if not (reason == "exited" and exitcode == 0) then add_error("unexpected child process exit") end pids[pid] = nil running_jobs = running_jobs - 1 break @@ -723,8 +771,8 @@ local function run_parallel(contexts) -- wait all children for pid in pairs(pids) do local retpid, reason, exitcode = syswait.wait(pid) - if not (retpid == pid and reason == 'exited' and exitcode == 0) then - error_count = error_count + 1 + if not (retpid == pid and reason == "exited" and exitcode == 0) then + add_error("unexpected child process exit") end pids[pid] = nil running_jobs = running_jobs - 1 @@ -732,8 +780,8 @@ local function run_parallel(contexts) assert(running_jobs == 0 and next(pids) == nil) end -local function run_sync(contexts) - for _, ctx in pairs(contexts) do +local function run_sync(contexts) + for _, ctx in pairs(contexts) do local machine = ctx.target.build(ctx.ram_image) ctx.cycles = ctx.target.run(machine, ctx, 2 * ctx.expected_cycles) check_test_result(ctx) @@ -742,8 +790,8 @@ local function run_sync(contexts) end local function run_tests(tests, target) - local contexts = {} -- construct contexts + local contexts = {} for _, test in ipairs(tests) do contexts[#contexts + 1] = { target = target, @@ -754,8 +802,7 @@ local function run_tests(tests, target) yield_payload_index = 1, failed = false, cycles = 0, - errors = {} - + errors = {}, } end -- run @@ -766,10 +813,8 @@ local function run_tests(tests, target) end -- collect results local error_count = 0 - for _, ctx in pairs(contexts) do - if ctx.failed then - error_count = error_count + 1 - end + for _, ctx in pairs(contexts) do + if ctx.failed then error_count = error_count + 1 end end -- print summary if error_count > 0 then @@ -784,7 +829,7 @@ end local function hash(tests) local out = io.stdout if output then out = assert(io.open(output, "w"), "error opening file: " .. output) end - for i, test in ipairs(tests) do + for _, test in ipairs(tests) do local ram_image = test[1] local expected_cycles = test[2] local expected_payload = test[3] or 0 @@ -798,13 +843,16 @@ local function hash(tests) next_action_cycle = periodic_action_start if next_action_cycle <= total_cycles then next_action_cycle = next_action_cycle - + ((((total_cycles-periodic_action_start)//periodic_action_period)+1) * periodic_action_period) + + ( + (((total_cycles - periodic_action_start) // periodic_action_period) + 1) + * periodic_action_period + ) end end local status = machine:run_uarch(initial_cycle + (next_action_cycle - total_cycles)) local final_cycle = machine:read_uarch_cycle() total_cycles = total_cycles + (final_cycle - initial_cycle) - if not periodic_action or total_cycles == next_action_cycle then + if not periodic_action or total_cycles == next_action_cycle then out:write(machine:read_mcycle(), " ", final_cycle, " ", util.hexhash(machine:get_root_hash()), "\n") total_cycles = total_cycles + 1 end @@ -816,7 +864,14 @@ local function hash(tests) if machine:read_htif_tohost_data() >> 1 ~= expected_payload or machine:read_mcycle() ~= expected_cycles then os.exit(1, true) end - out:write(machine:read_mcycle(), " ", machine:read_uarch_cycle(), " ", util.hexhash(machine:get_root_hash()), "\n") + out:write( + machine:read_mcycle(), + " ", + machine:read_uarch_cycle(), + " ", + util.hexhash(machine:get_root_hash()), + "\n" + ) machine:destroy() end end @@ -852,30 +907,33 @@ local function step(tests) local max_mcycle = 2 * expected_cycles while math.ult(machine:read_mcycle(), max_mcycle) do local uarch_cycle_increment = 0 - local next_action_uarch_cycle; + local next_action_uarch_cycle if periodic_action then next_action_uarch_cycle = periodic_action_start if next_action_uarch_cycle <= total_uarch_cycles then next_action_uarch_cycle = next_action_uarch_cycle - + ((((total_uarch_cycles-periodic_action_start)//periodic_action_period)+1) * periodic_action_period) + + ( + (((total_uarch_cycles - periodic_action_start) // periodic_action_period) + 1) + * periodic_action_period + ) end uarch_cycle_increment = next_action_uarch_cycle - total_uarch_cycles end - local init_uarch_cycle = machine:read_uarch_cycle() + local init_uarch_cycle = machine:read_uarch_cycle() machine:run_uarch(machine:read_uarch_cycle() + uarch_cycle_increment) - local final_uarch_cycle = machine:read_uarch_cycle() + local final_uarch_cycle = machine:read_uarch_cycle() total_uarch_cycles = total_uarch_cycles + (final_uarch_cycle - init_uarch_cycle) if machine:read_uarch_halt_flag() then machine:reset_uarch_state() if machine:read_iflags_H() then break end end - if not periodic_action or total_uarch_cycles == next_action_uarch_cycle then + if not periodic_action or total_uarch_cycles == next_action_uarch_cycle then local init_mcycle = machine:read_mcycle() init_uarch_cycle = machine:read_uarch_cycle() local log = machine:step_uarch(log_type) local final_mcycle = machine:read_mcycle() final_uarch_cycle = machine:read_uarch_cycle() - if total_logged_steps > 0 then out:write(',\n') end + if total_logged_steps > 0 then out:write(",\n") end util.dump_json_log(log, init_mcycle, init_uarch_cycle, final_mcycle, final_uarch_cycle, out, 3) total_uarch_cycles = total_uarch_cycles + 1 total_logged_steps = total_logged_steps + 1 @@ -886,8 +944,11 @@ local function step(tests) end end indentout(out, 2, "]\n") - if tests[i+1] then indentout(out, 1, "},\n") - else indentout(out, 1, "}\n") end + if tests[i + 1] then + indentout(out, 1, "},\n") + else + indentout(out, 1, "}\n") + end if machine:read_htif_tohost_data() >> 1 ~= expected_payload or machine:read_mcycle() ~= expected_cycles then os.exit(1, true) end @@ -908,12 +969,12 @@ local function list(tests) if json_list then local out = io.stdout local indentout = util.indentout - out:write("{\n \"tests\": [\n") + out:write('{\n "tests": [\n') for i, test in ipairs(tests) do if i ~= 1 then out:write(",\n") end indentout(out, 2, "{\n") - indentout(out, 3, "\"file\": \"" .. test[1] .. "\",\n") - indentout(out, 3, "\"mcycle\": " .. test[2] .. "\n") + indentout(out, 3, '"file": "' .. test[1] .. '",\n') + indentout(out, 3, '"mcycle": ' .. test[2] .. "\n") indentout(out, 2, "}") end out:write("\n ]\n}\n") @@ -924,70 +985,83 @@ local function list(tests) end end -local function select(test_name, test_pattern) - local i, j = test_name:find(test_pattern) +local function select_test(test_name, patt) + local i, j = test_name:find(patt) if i == 1 and j == #test_name then return true end - i, j = test_name:find(test_pattern, 1, true) + i, j = test_name:find(patt, 1, true) return i == 1 and j == #test_name end local selected_tests = {} -for _, test in ipairs(tests) do - if select(test[1], test_pattern) then - selected_tests[#selected_tests+1] = test - end +for _, test in ipairs(riscv_tests) do + if select_test(test[1], test_pattern) then selected_tests[#selected_tests + 1] = test end end -local function build_both_machines(test_name) +local function build_both_machines(test_name) return { host = build_machine(test_name), - uarch = build_machine(test_name) + uarch = build_machine(test_name), } end -local function destroy_both_machines(target) +local function destroy_both_machines(target) destroy_machine(target.host) destroy_machine(target.uarch) end -local function run_host_and_uarch_machines(target, ctx, max_mcycle, callback) - local host = target.host - local uarch = target.uarch - callback = callback or nothing - local host_cycles = host:read_mcycle() - local uarch_cycles = uarch:read_mcycle() +local function run_host_and_uarch_machines(target, ctx, max_mcycle) + local host_machine = target.host + local uarch_machine = target.uarch + local host_cycles = host_machine:read_mcycle() + local uarch_cycles = uarch_machine:read_mcycle() assert(host_cycles == uarch_cycles) - if host_cycles ~= uarch_cycles then - add_error(ctx,"host_cycles ~= uarch_cycles: %d ~= %d", host_cycles, uarch_cycles) + if host_cycles ~= uarch_cycles then + add_error(ctx, "host_cycles ~= uarch_cycles: %d ~= %d", host_cycles, uarch_cycles) return 0 end while math.ult(host_cycles, max_mcycle) do - local host_hash = host:get_root_hash() - local uarch_hash = uarch:get_root_hash() + local host_hash = host_machine:get_root_hash() + local uarch_hash = uarch_machine:get_root_hash() if host_hash ~= uarch_hash then - add_error(ctx,"Hash mismatch at mcycle %d: %s ~= %s", host_cycles, util.hexhash(host_hash), util.hexhash(uarch_hash)) + add_error( + ctx, + "Hash mismatch at mcycle %d: %s ~= %s", + host_cycles, + util.hexhash(host_hash), + util.hexhash(uarch_hash) + ) break end - host:run(1 + host_cycles) - advance_machine_with_uarch(uarch) - host_cycles = host:read_mcycle() - uarch_cycles = uarch:read_mcycle() + host_machine:run(1 + host_cycles) + advance_machine_with_uarch(uarch_machine) + host_cycles = host_machine:read_mcycle() + uarch_cycles = uarch_machine:read_mcycle() if host_cycles ~= uarch_cycles then - add_error(ctx,"host_cycles ~= uarch_cycles: %d ~= %d", host_cycles, uarch_cycles) + add_error(ctx, "host_cycles ~= uarch_cycles: %d ~= %d", host_cycles, uarch_cycles) break end - local host_iflags_H = host:read_iflags_H() - local uarch_iflags_H = uarch:read_iflags_H() + local host_iflags_H = host_machine:read_iflags_H() + local uarch_iflags_H = uarch_machine:read_iflags_H() if host_iflags_H ~= uarch_iflags_H then - add_error(ctx,"host_iflags_H ~= uarch_iflags_H: %s ~= %s", tostring(host_iflags_H), tostring(uarch_iflags_H)) + add_error( + ctx, + "host_iflags_H ~= uarch_iflags_H: %s ~= %s", + tostring(host_iflags_H), + tostring(uarch_iflags_H) + ) break end if host_iflags_H then break end end - local host_htif_tohost_data = host:read_htif_tohost_data() - local uarch_htif_tohost_data = uarch:read_htif_tohost_data() + local host_htif_tohost_data = host_machine:read_htif_tohost_data() + local uarch_htif_tohost_data = uarch_machine:read_htif_tohost_data() if host_htif_tohost_data ~= uarch_htif_tohost_data then - add_error(ctx, "host_htif_tohost_data ~= uarch_htif_tohost_data: %d ~= %d", host_htif_tohost_data, uarch_htif_tohost_data) + add_error( + ctx, + "host_htif_tohost_data ~= uarch_htif_tohost_data: %d ~= %d", + host_htif_tohost_data, + uarch_htif_tohost_data + ) end ctx.read_htif_tohost_data = host_htif_tohost_data return host_cycles @@ -998,29 +1072,40 @@ local targets = { host = { build = build_machine, run = run_machine, - destroy = destroy_machine + destroy = destroy_machine, }, -- Run test on microarchitecture-based emulator uarch = { build = build_machine, run = run_machine_with_uarch, - destroy = destroy_machine + destroy = destroy_machine, }, -- Run test on both architectures: macro and micro; comparing root hashes after every mcycle host_and_uarch = { build = build_both_machines, run = run_host_and_uarch_machines, - destroy = destroy_both_machines - } + destroy = destroy_both_machines, + }, } -if #selected_tests < 1 then error("no test selected") -elseif command == "run" then run_tests(selected_tests, targets.host) -elseif command == "run_uarch" then run_tests(selected_tests, targets.uarch) -elseif command == "run_host_and_uarch" then run_tests(selected_tests, targets.host_and_uarch) -elseif command == "hash" then hash(selected_tests) -elseif command == "step" then step(selected_tests) -elseif command == "dump" then dump(selected_tests) -elseif command == "list" then list(selected_tests) -elseif command == "machine" then print_machines(selected_tests) -else error("command not found") end +if #selected_tests < 1 then + error("no test selected") +elseif command == "run" then + run_tests(selected_tests, targets.host) +elseif command == "run_uarch" then + run_tests(selected_tests, targets.uarch) +elseif command == "run_host_and_uarch" then + run_tests(selected_tests, targets.host_and_uarch) +elseif command == "hash" then + hash(selected_tests) +elseif command == "step" then + step(selected_tests) +elseif command == "dump" then + dump(selected_tests) +elseif command == "list" then + list(selected_tests) +elseif command == "machine" then + print_machines(selected_tests) +else + error("command not found") +end diff --git a/src/cartesi-machine.lua b/src/cartesi-machine.lua index 1836968aa..48daa9dbf 100755 --- a/src/cartesi-machine.lua +++ b/src/cartesi-machine.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019-2021 Cartesi Pte. Ltd. -- @@ -16,12 +16,10 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require"cartesi" -local util = require"cartesi.util" +local cartesi = require("cartesi") +local util = require("cartesi.util") -local function stderr_unsilenceable(fmt, ...) - io.stderr:write(string.format(fmt, ...)) -end +local function stderr_unsilenceable(fmt, ...) io.stderr:write(string.format(fmt, ...)) end local stderr = stderr_unsilenceable local function adjust_images_path(path) @@ -31,7 +29,8 @@ end -- Print help and exit local function help() - stderr([=[ + stderr( + [=[ Usage: %s [options] [command] [arguments] @@ -354,31 +353,34 @@ or a left shift (e.g., 2 << 20). can be a host name, IPv4 or IPv6 address. -]=], arg[0]) +]=], + arg[0] + ) os.exit() end +local remote local remote_protocol = "grpc" -local remote_address = nil -local checkin_address = nil +local remote_address +local checkin_address local remote_shutdown = false local remote_create = true local remote_destroy = true -local images_path = adjust_images_path(os.getenv('CARTESI_IMAGES_PATH')) +local images_path = adjust_images_path(os.getenv("CARTESI_IMAGES_PATH")) local flash_image_filename = { root = images_path .. "rootfs.ext2" } local flash_label_order = { "root" } -local flash_shared = { } -local flash_start = { } -local flash_length = { } -local memory_range_replace = { } +local flash_shared = {} +local flash_start = {} +local flash_length = {} +local memory_range_replace = {} local ram_image_filename = images_path .. "linux.bin" local ram_length = 64 << 20 local rom_image_filename = images_path .. "rom.bin" local rom_bootargs = "console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw quiet swiotlb=noforce" -local rollup = nil -local uarch = nil -local rollup_advance = nil -local rollup_inspect = nil +local rollup +local uarch +local rollup_advance +local rollup_inspect local concurrency_update_merkle_tree = 0 local append_rom_bootargs = "" local htif_console_getchar = false @@ -395,32 +397,30 @@ local max_mcycle = math.maxinteger local json_steps local max_uarch_cycle = 0 local auto_reset_uarch_state = false -local step = false -local store_dir = nil -local load_dir = nil +local step_uarch = false +local store_dir +local load_dir local cmdline_opts_finished = false local store_config = false local load_config = false -local gdb_address = nil +local gdb_address local exec_arguments = {} local quiet = false +local assert_rolling_template = false local function parse_memory_range(opts, what, all) local f = util.parse_options(opts, { filename = true, shared = true, length = true, - start = true + start = true, }) f.image_filename = f.filename f.filename = nil if f.image_filename == true then f.image_filename = "" end - assert(not f.shared or f.shared == true, - "invalid " .. what .. " shared value in " .. all) - f.start = assert(util.parse_number(f.start), - "invalid " .. what .. " start in " .. all) - f.length = assert(util.parse_number(f.length), - "invalid " .. what .. " length in " .. all) + assert(not f.shared or f.shared == true, "invalid " .. what .. " shared value in " .. all) + f.start = assert(util.parse_number(f.start), "invalid " .. what .. " start in " .. all) + f.length = assert(util.parse_number(f.length), "invalid " .. what .. " length in " .. all) return f end @@ -432,438 +432,575 @@ end -- if callback returns true, the option is accepted. -- if callback returns false, the option is rejected. local options = { - { "^%-h$", function(all) - if not all then return false end - help() - return true - end }, - { "^%-%-help$", function(all) - if not all then return false end - help() - return true - end }, - { "^%-%-rom%-image%=(.*)$", function(o) - if not o or #o < 1 then return false end - rom_image_filename = o - return true - end }, - { "^%-%-no%-rom%-bootargs$", function(all) - if not all then return false end - rom_bootargs = "" - return true - end }, - { "^%-%-append%-rom%-bootargs%=(.*)$", function(o) - if not o or #o < 1 then return false end - append_rom_bootargs = o - return true - end }, - { "^%-%-ram%-length%=(.+)$", function(n) - if not n then return false end - ram_length = assert(util.parse_number(n), "invalid RAM length " .. n) - return true - end }, - { "^%-%-ram%-image%=(.*)$", function(o) - if not o or #o < 1 then return false end - ram_image_filename = o - return true - end }, - { "^%-%-no%-ram%-image$", function(all) - if not all then return false end - ram_image_filename = "" - return true - end }, - { "^%-%-uarch%-ram%-length%=(.+)$", function(n) - if not n then return false end - uarch = uarch or {} - uarch.ram = uarch.ram or {} - uarch.ram.length = assert(util.parse_number(n), "invalid microarchitecture RAM length " .. n) - return true - end }, - { "^%-%-uarch%-ram%-image%=(.*)$", function(o) - if not o or #o < 1 then return false end - uarch = uarch or {} - uarch.ram = uarch.ram or {} - uarch.ram.image_filename = o - return true - end }, - { "^%-%-htif%-console%-getchar$", function(all) - if not all then return false end - htif_console_getchar = true - return true - end }, - { "^%-i$", function(all) - if not all then return false end - htif_console_getchar = true - return true - end }, - { "^%-%-htif%-yield%-manual$", function(all) - if not all then return false end - htif_yield_manual = true - return true - end }, - { "^%-%-htif%-yield%-automatic$", function(all) - if not all then return false end - htif_yield_automatic = true - return true - end }, - { "^%-%-rollup$", function(all) - if not all then return false end - rollup = rollup or {} - rollup.rx_buffer = { start = 0x60000000, length = 2 << 20 } - rollup.tx_buffer = { start = 0x60200000, length = 2 << 20 } - rollup.input_metadata = { start = 0x60400000, length = 4096 } - rollup.voucher_hashes = { start = 0x60600000, length = 2 << 20 } - rollup.notice_hashes = { start = 0x60800000, length = 2 << 20 } - htif_yield_automatic = true - htif_yield_manual = true - return true - end }, - { "^(%-%-flash%-drive%=(.+))$", function(all, opts) - if not opts then return false end - local f = util.parse_options(opts, { - label = true, - filename = true, - shared = true, - length = true, - start = true - }) - assert(f.label, "missing flash drive label in " .. all) - f.image_filename = f.filename - f.filename = nil - if f.image_filename == true then f.image_filename = "" end - assert(not f.shared or f.shared == true, - "invalid flash drive shared value in " .. all) - if f.start then - f.start = assert(util.parse_number(f.start), - "invalid flash drive start in " .. all) - end - if f.length then - f.length = assert(util.parse_number(f.length), - "invalid flash drive length in " .. all) - end - local d = f.label - if not flash_image_filename[d] then - flash_label_order[#flash_label_order+1] = d - flash_image_filename[d] = "" - end - flash_image_filename[d] = f.image_filename or - flash_image_filename[d] - flash_start[d] = f.start or flash_start[d] - flash_length[d] = f.length or flash_length[d] - flash_shared[d] = f.shared or flash_shared[d] - return true - end }, - { "^(%-%-replace%-flash%-drive%=(.+))$", function(all, opts) - if not opts then return false end - memory_range_replace[#memory_range_replace+1] = - parse_memory_range(opts, "flash drive", all) - return true - end }, - { "^(%-%-replace%-memory%-range%=(.+))$", function(all, opts) - if not opts then return false end - memory_range_replace[#memory_range_replace+1] = - parse_memory_range(opts, "flash drive", all) - return true - end }, - { "^(%-%-rollup%-advance%-state%=(.+))$", function(all, opts) - if not opts then return false end - local r = util.parse_options(opts, { - epoch_index = true, - input = true, - input_metadata = true, - input_index_begin = true, - input_index_end = true, - voucher = true, - voucher_hashes = true, - notice = true, - notice_hashes = true, - report = true, - hashes = true, - }) - assert(not r.hashes or r.hashes == true, "invalid hashes value in " .. all) - r.epoch_index = assert(util.parse_number(r.epoch_index), "invalid epoch index in " .. all) - r.input = r.input or "epoch-%e-input-%i.bin" - r.input_metadata = r.input_metadata or "epoch-%e-input-metadata-%i.bin" - r.input_index_begin = r.input_index_begin or 0 - r.input_index_begin = assert(util.parse_number(r.input_index_begin), "invalid input index begin in " .. all) - r.input_index_end = r.input_index_end or 0 - r.input_index_end = assert(util.parse_number(r.input_index_end), "invalid input index end in " .. all) - r.voucher = r.voucher or "epoch-%e-input-%i-voucher-%o.bin" - r.voucher_hashes = r.voucher_hashes or "epoch-%e-input-%i-voucher-hashes.bin" - r.notice = r.notice or "epoch-%e-input-%i-notice-%o.bin" - r.notice_hashes = r.notice_hashes or "epoch-%e-input-%i-notice-hashes.bin" - r.report = r.report or "epoch-%e-input-%i-report-%o.bin" - r.next_input_index = r.input_index_begin - rollup_advance = r - return true - end }, - { "^(%-%-rollup%-inspect%-state%=(.+))$", function(all, opts) - if not opts then return false end - local r = util.parse_options(opts, { - query = true, - report = true, - }) - r.query = r.query or "query.bin" - r.report = r.report or "query-report-%o.bin" - rollup_inspect = r - return true - end }, - { "^%-%-rollup%-inspect%-state$", function(all) - if not all then return false end - rollup_inspect = { - query = "query.bin", - report = "query-report-%o.bin" - } - return true - end }, - { "^(%-%-concurrency%=(.+))$", function(all, opts) - if not opts then return false end - local c = util.parse_options(opts, { - update_merkle_tree = true - }) - c.update_merkle_tree = assert(util.parse_number(c.update_merkle_tree), - "invalid update_merkle_tree number in " .. all) - concurrency_update_merkle_tree = c.update_merkle_tree - return true - end }, - { "^(%-%-initial%-proof%=(.+))$", function(all, opts) - if not opts then return false end - local p = util.parse_options(opts, { - address = true, - log2_size = true, - filename = true - }) - p.cmdline = all - p.address = assert(util.parse_number(p.address), - "invalid address in " .. all) - p.log2_size = assert(util.parse_number(p.log2_size), - "invalid log2_size in " .. all) - assert(p.log2_size >= 3, - "log2_size must be at least 3 in " .. all) - initial_proof[#initial_proof+1] = p - return true - end }, - { "^(%-%-final%-proof%=(.+))$", function(all, opts) - if not opts then return false end - local p = util.parse_options(opts, { - address = true, - log2_size = true, - filename = true - }) - p.cmdline = all - p.address = assert(util.parse_number(p.address), - "invalid address in " .. all) - p.log2_size = assert(util.parse_number(p.log2_size), - "invalid log2_size in " .. all) - assert(p.log2_size >= 3, - "log2_size must be at least 3 in " .. all) - final_proof[#final_proof+1] = p - return true - end }, - { "^%-%-no%-root%-flash%-drive$", function(all) - if not all then return false end - assert(flash_image_filename.root and flash_label_order[1] == "root", - "no root flash drive to remove") - flash_image_filename.root = nil - flash_start.root = nil - flash_length.root = nil - flash_shared.root = nil - table.remove(flash_label_order, 1) - rom_bootargs = "console=hvc0" - return true - end }, - { "^%-%-dump%-pmas$", function(all) - if not all then return false end - dump_pmas = true - return true - end }, - { "%-%-assert%-rolling%-template", function(all) - if not all then return false end - assert_rolling_template = true - return true - end }, - { "%-%-quiet", function(all) - if not all then return false end - stderr = function(...) end - quiet = true - return true - end }, - { "^%-%-step%-uarch$", function(all) - if not all then return false end - step_uarch = true - return true - end }, - { "^(%-%-max%-mcycle%=(.*))$", function(all, n) - if not n then return false end - max_mcycle = assert(util.parse_number(n), "invalid option " .. all) - return true - end }, - { "^(%-%-max%-uarch%-cycle%=(.*))$", function(all, n) - if not n then return false end - max_uarch_cycle = assert(util.parse_number(n), "invalid option " .. all) - return true - end }, - { "^%-%-auto%-reset%-uarch%-state$", function(all) - if not all then return false end - auto_reset_uarch_state = true - return true - end }, - { "^%-%-load%=(.*)$", function(o) - if not o or #o < 1 then return false end - load_dir = o - return true - end }, - { "^%-%-store%=(.*)$", function(o) - if not o or #o < 1 then return false end - store_dir = o - return true - end }, - { "^%-%-remote%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - remote_address = o - return true - end }, - { "^%-%-remote%-protocol%=(.*)$", function(o) - if not o or #o < 1 then return false end - remote_protocol = o - return true - end }, - { "^%-%-checkin%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - checkin_address = o - return true - end }, - { "^%-%-remote%-shutdown$", function(o) - if not o then return false end - remote_shutdown = true - return true - end }, - { "^%-%-no%-remote%-create$", function(o) - if not o then return false end - remote_create = false - return true - end }, - { "^%-%-no%-remote%-destroy$", function(o) - if not o then return false end - remote_destroy = false - return true - end }, - { "^%-%-json%-steps%=(.*)$", function(o) - if not o or #o < 1 then return false end - json_steps = o - return true - end }, - { "^%-%-initial%-hash$", function(all) - if not all then return false end - initial_hash = true - return true - end }, - { "^%-%-final%-hash$", function(all) - if not all then return false end - final_hash = true - return true - end }, - { "^(%-%-periodic%-hashes%=(.*))$", function(all, v) - if not v then return false end - string.gsub(v, "^([^%,]+),(.+)$", function(p, s) - periodic_hashes_period = assert(util.parse_number(p), - "invalid period " .. all) - periodic_hashes_start = assert(util.parse_number(s), - "invalid start " .. all) - end) - if periodic_hashes_period == math.maxinteger then - periodic_hashes_period = assert(util.parse_number(v), - "invalid period " .. all) - periodic_hashes_start = 0 - end - initial_hash = true - final_hash = true - return true - end }, - { "^%-%-store%-config(%=?)(%g*)$", function(o, v) - if not o then return false end - if o == '=' then - if not v or #v < 1 then return false end - store_config = v - else - store_config = stderr - end - return true - end }, - { "^%-%-load%-config%=(%g*)$", function(o) - if not o or #o < 1 then return false end - load_config = o - return true - end }, - { "^(%-%-rollup%-rx%-buffer%=(.+))$", function(all, opts) - if not opts then return false end - rollup = rollup or {} - rollup.rx_buffer = parse_memory_range(opts, "rollup rx buffer", all) - return true - end }, - { "^(%-%-rollup%-tx%-buffer%=(.+))$", function(all, opts) - if not opts then return false end - rollup = rollup or {} - rollup.tx_buffer = parse_memory_range(opts, "tx buffer", all) - return true - end }, - { "^(%-%-rollup%-input%-metadata%=(.+))$", function(all, opts) - if not opts then return false end - rollup = rollup or {} - rollup.input_metadata = parse_memory_range(opts, "rollup input metadata", all) - return true - end }, - { "^(%-%-rollup%-voucher%-hashes%=(.+))$", function(all, opts) - if not opts then return false end - rollup = rollup or {} - rollup.voucher_hashes = parse_memory_range(opts, "rollup voucher hashes", all) - return true - end }, - { "^(%-%-rollup%-notice%-hashes%=(.+))$", function(all, opts) - if not opts then return false end - rollup = rollup or {} - rollup.notice_hashes = parse_memory_range(opts, "rollup notice hashes", all) - return true - end }, - { "^%-%-gdb(%=?)(.*)$", function(o, address) - if o == '=' and #o > 0 then - gdb_address = address - return true - elseif o == '' then - gdb_address = '127.0.0.1:1234' - return true - end - return false - end }, - { ".*", function(all) - if not all then return false end - local not_option = all:sub(1,1) ~= "-" - if not_option or all == "--" then - cmdline_opts_finished = true - if not_option then exec_arguments = { all } end - return true - end - error("unrecognized option " .. all) - end } + { + "^%-h$", + function(all) + if not all then return false end + help() + return true + end, + }, + { + "^%-%-help$", + function(all) + if not all then return false end + help() + return true + end, + }, + { + "^%-%-rom%-image%=(.*)$", + function(o) + if not o or #o < 1 then return false end + rom_image_filename = o + return true + end, + }, + { + "^%-%-no%-rom%-bootargs$", + function(all) + if not all then return false end + rom_bootargs = "" + return true + end, + }, + { + "^%-%-append%-rom%-bootargs%=(.*)$", + function(o) + if not o or #o < 1 then return false end + append_rom_bootargs = o + return true + end, + }, + { + "^%-%-ram%-length%=(.+)$", + function(n) + if not n then return false end + ram_length = assert(util.parse_number(n), "invalid RAM length " .. n) + return true + end, + }, + { + "^%-%-ram%-image%=(.*)$", + function(o) + if not o or #o < 1 then return false end + ram_image_filename = o + return true + end, + }, + { + "^%-%-no%-ram%-image$", + function(all) + if not all then return false end + ram_image_filename = "" + return true + end, + }, + { + "^%-%-uarch%-ram%-length%=(.+)$", + function(n) + if not n then return false end + uarch = uarch or {} + uarch.ram = uarch.ram or {} + uarch.ram.length = assert(util.parse_number(n), "invalid microarchitecture RAM length " .. n) + return true + end, + }, + { + "^%-%-uarch%-ram%-image%=(.*)$", + function(o) + if not o or #o < 1 then return false end + uarch = uarch or {} + uarch.ram = uarch.ram or {} + uarch.ram.image_filename = o + return true + end, + }, + { + "^%-%-htif%-console%-getchar$", + function(all) + if not all then return false end + htif_console_getchar = true + return true + end, + }, + { + "^%-i$", + function(all) + if not all then return false end + htif_console_getchar = true + return true + end, + }, + { + "^%-%-htif%-yield%-manual$", + function(all) + if not all then return false end + htif_yield_manual = true + return true + end, + }, + { + "^%-%-htif%-yield%-automatic$", + function(all) + if not all then return false end + htif_yield_automatic = true + return true + end, + }, + { + "^%-%-rollup$", + function(all) + if not all then return false end + rollup = rollup or {} + rollup.rx_buffer = { start = 0x60000000, length = 2 << 20 } + rollup.tx_buffer = { start = 0x60200000, length = 2 << 20 } + rollup.input_metadata = { start = 0x60400000, length = 4096 } + rollup.voucher_hashes = { start = 0x60600000, length = 2 << 20 } + rollup.notice_hashes = { start = 0x60800000, length = 2 << 20 } + htif_yield_automatic = true + htif_yield_manual = true + return true + end, + }, + { + "^(%-%-flash%-drive%=(.+))$", + function(all, opts) + if not opts then return false end + local f = util.parse_options(opts, { + label = true, + filename = true, + shared = true, + length = true, + start = true, + }) + assert(f.label, "missing flash drive label in " .. all) + f.image_filename = f.filename + f.filename = nil + if f.image_filename == true then f.image_filename = "" end + assert(not f.shared or f.shared == true, "invalid flash drive shared value in " .. all) + if f.start then f.start = assert(util.parse_number(f.start), "invalid flash drive start in " .. all) end + if f.length then f.length = assert(util.parse_number(f.length), "invalid flash drive length in " .. all) end + local d = f.label + if not flash_image_filename[d] then + flash_label_order[#flash_label_order + 1] = d + flash_image_filename[d] = "" + end + flash_image_filename[d] = f.image_filename or flash_image_filename[d] + flash_start[d] = f.start or flash_start[d] + flash_length[d] = f.length or flash_length[d] + flash_shared[d] = f.shared or flash_shared[d] + return true + end, + }, + { + "^(%-%-replace%-flash%-drive%=(.+))$", + function(all, opts) + if not opts then return false end + memory_range_replace[#memory_range_replace + 1] = parse_memory_range(opts, "flash drive", all) + return true + end, + }, + { + "^(%-%-replace%-memory%-range%=(.+))$", + function(all, opts) + if not opts then return false end + memory_range_replace[#memory_range_replace + 1] = parse_memory_range(opts, "flash drive", all) + return true + end, + }, + { + "^(%-%-rollup%-advance%-state%=(.+))$", + function(all, opts) + if not opts then return false end + local r = util.parse_options(opts, { + epoch_index = true, + input = true, + input_metadata = true, + input_index_begin = true, + input_index_end = true, + voucher = true, + voucher_hashes = true, + notice = true, + notice_hashes = true, + report = true, + hashes = true, + }) + assert(not r.hashes or r.hashes == true, "invalid hashes value in " .. all) + r.epoch_index = assert(util.parse_number(r.epoch_index), "invalid epoch index in " .. all) + r.input = r.input or "epoch-%e-input-%i.bin" + r.input_metadata = r.input_metadata or "epoch-%e-input-metadata-%i.bin" + r.input_index_begin = r.input_index_begin or 0 + r.input_index_begin = assert(util.parse_number(r.input_index_begin), "invalid input index begin in " .. all) + r.input_index_end = r.input_index_end or 0 + r.input_index_end = assert(util.parse_number(r.input_index_end), "invalid input index end in " .. all) + r.voucher = r.voucher or "epoch-%e-input-%i-voucher-%o.bin" + r.voucher_hashes = r.voucher_hashes or "epoch-%e-input-%i-voucher-hashes.bin" + r.notice = r.notice or "epoch-%e-input-%i-notice-%o.bin" + r.notice_hashes = r.notice_hashes or "epoch-%e-input-%i-notice-hashes.bin" + r.report = r.report or "epoch-%e-input-%i-report-%o.bin" + r.next_input_index = r.input_index_begin + rollup_advance = r + return true + end, + }, + { + "^(%-%-rollup%-inspect%-state%=(.+))$", + function(_, opts) + if not opts then return false end + local r = util.parse_options(opts, { + query = true, + report = true, + }) + r.query = r.query or "query.bin" + r.report = r.report or "query-report-%o.bin" + rollup_inspect = r + return true + end, + }, + { + "^%-%-rollup%-inspect%-state$", + function(all) + if not all then return false end + rollup_inspect = { + query = "query.bin", + report = "query-report-%o.bin", + } + return true + end, + }, + { + "^(%-%-concurrency%=(.+))$", + function(all, opts) + if not opts then return false end + local c = util.parse_options(opts, { + update_merkle_tree = true, + }) + c.update_merkle_tree = + assert(util.parse_number(c.update_merkle_tree), "invalid update_merkle_tree number in " .. all) + concurrency_update_merkle_tree = c.update_merkle_tree + return true + end, + }, + { + "^(%-%-initial%-proof%=(.+))$", + function(all, opts) + if not opts then return false end + local p = util.parse_options(opts, { + address = true, + log2_size = true, + filename = true, + }) + p.cmdline = all + p.address = assert(util.parse_number(p.address), "invalid address in " .. all) + p.log2_size = assert(util.parse_number(p.log2_size), "invalid log2_size in " .. all) + assert(p.log2_size >= 3, "log2_size must be at least 3 in " .. all) + initial_proof[#initial_proof + 1] = p + return true + end, + }, + { + "^(%-%-final%-proof%=(.+))$", + function(all, opts) + if not opts then return false end + local p = util.parse_options(opts, { + address = true, + log2_size = true, + filename = true, + }) + p.cmdline = all + p.address = assert(util.parse_number(p.address), "invalid address in " .. all) + p.log2_size = assert(util.parse_number(p.log2_size), "invalid log2_size in " .. all) + assert(p.log2_size >= 3, "log2_size must be at least 3 in " .. all) + final_proof[#final_proof + 1] = p + return true + end, + }, + { + "^%-%-no%-root%-flash%-drive$", + function(all) + if not all then return false end + assert(flash_image_filename.root and flash_label_order[1] == "root", "no root flash drive to remove") + flash_image_filename.root = nil + flash_start.root = nil + flash_length.root = nil + flash_shared.root = nil + table.remove(flash_label_order, 1) + rom_bootargs = "console=hvc0" + return true + end, + }, + { + "^%-%-dump%-pmas$", + function(all) + if not all then return false end + dump_pmas = true + return true + end, + }, + { + "%-%-assert%-rolling%-template", + function(all) + if not all then return false end + assert_rolling_template = true + return true + end, + }, + { + "%-%-quiet", + function(all) + if not all then return false end + stderr = function() end + quiet = true + return true + end, + }, + { + "^%-%-step%-uarch$", + function(all) + if not all then return false end + step_uarch = true + return true + end, + }, + { + "^(%-%-max%-mcycle%=(.*))$", + function(all, n) + if not n then return false end + max_mcycle = assert(util.parse_number(n), "invalid option " .. all) + return true + end, + }, + { + "^(%-%-max%-uarch%-cycle%=(.*))$", + function(all, n) + if not n then return false end + max_uarch_cycle = assert(util.parse_number(n), "invalid option " .. all) + return true + end, + }, + { + "^%-%-auto%-reset%-uarch%-state$", + function(all) + if not all then return false end + auto_reset_uarch_state = true + return true + end, + }, + { + "^%-%-load%=(.*)$", + function(o) + if not o or #o < 1 then return false end + load_dir = o + return true + end, + }, + { + "^%-%-store%=(.*)$", + function(o) + if not o or #o < 1 then return false end + store_dir = o + return true + end, + }, + { + "^%-%-remote%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + remote_address = o + return true + end, + }, + { + "^%-%-remote%-protocol%=(.*)$", + function(o) + if not o or #o < 1 then return false end + remote_protocol = o + return true + end, + }, + { + "^%-%-checkin%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + checkin_address = o + return true + end, + }, + { + "^%-%-remote%-shutdown$", + function(o) + if not o then return false end + remote_shutdown = true + return true + end, + }, + { + "^%-%-no%-remote%-create$", + function(o) + if not o then return false end + remote_create = false + return true + end, + }, + { + "^%-%-no%-remote%-destroy$", + function(o) + if not o then return false end + remote_destroy = false + return true + end, + }, + { + "^%-%-json%-steps%=(.*)$", + function(o) + if not o or #o < 1 then return false end + json_steps = o + return true + end, + }, + { + "^%-%-initial%-hash$", + function(all) + if not all then return false end + initial_hash = true + return true + end, + }, + { + "^%-%-final%-hash$", + function(all) + if not all then return false end + final_hash = true + return true + end, + }, + { + "^(%-%-periodic%-hashes%=(.*))$", + function(all, v) + if not v then return false end + string.gsub(v, "^([^%,]+),(.+)$", function(p, s) + periodic_hashes_period = assert(util.parse_number(p), "invalid period " .. all) + periodic_hashes_start = assert(util.parse_number(s), "invalid start " .. all) + end) + if periodic_hashes_period == math.maxinteger then + periodic_hashes_period = assert(util.parse_number(v), "invalid period " .. all) + periodic_hashes_start = 0 + end + initial_hash = true + final_hash = true + return true + end, + }, + { + "^%-%-store%-config(%=?)(%g*)$", + function(o, v) + if not o then return false end + if o == "=" then + if not v or #v < 1 then return false end + store_config = v + else + store_config = stderr + end + return true + end, + }, + { + "^%-%-load%-config%=(%g*)$", + function(o) + if not o or #o < 1 then return false end + load_config = o + return true + end, + }, + { + "^(%-%-rollup%-rx%-buffer%=(.+))$", + function(all, opts) + if not opts then return false end + rollup = rollup or {} + rollup.rx_buffer = parse_memory_range(opts, "rollup rx buffer", all) + return true + end, + }, + { + "^(%-%-rollup%-tx%-buffer%=(.+))$", + function(all, opts) + if not opts then return false end + rollup = rollup or {} + rollup.tx_buffer = parse_memory_range(opts, "tx buffer", all) + return true + end, + }, + { + "^(%-%-rollup%-input%-metadata%=(.+))$", + function(all, opts) + if not opts then return false end + rollup = rollup or {} + rollup.input_metadata = parse_memory_range(opts, "rollup input metadata", all) + return true + end, + }, + { + "^(%-%-rollup%-voucher%-hashes%=(.+))$", + function(all, opts) + if not opts then return false end + rollup = rollup or {} + rollup.voucher_hashes = parse_memory_range(opts, "rollup voucher hashes", all) + return true + end, + }, + { + "^(%-%-rollup%-notice%-hashes%=(.+))$", + function(all, opts) + if not opts then return false end + rollup = rollup or {} + rollup.notice_hashes = parse_memory_range(opts, "rollup notice hashes", all) + return true + end, + }, + { + "^%-%-gdb(%=?)(.*)$", + function(o, address) + if o == "=" and #o > 0 then + gdb_address = address + return true + elseif o == "" then + gdb_address = "127.0.0.1:1234" + return true + end + return false + end, + }, + { + ".*", + function(all) + if not all then return false end + local not_option = all:sub(1, 1) ~= "-" + if not_option or all == "--" then + cmdline_opts_finished = true + if not_option then exec_arguments = { all } end + return true + end + error("unrecognized option " .. all) + end, + }, } -- Process command line options -for i, a in ipairs(arg) do +for _, a in ipairs(arg) do if not cmdline_opts_finished then - for j, option in ipairs(options) do - if option[2](a:match(option[1])) then - break - end - end + for _, option in ipairs(options) do + if option[2](a:match(option[1])) then break end + end else - exec_arguments[#exec_arguments+1] = a + exec_arguments[#exec_arguments + 1] = a end end local function get_file_length(filename) local file = io.open(filename, "rb") if not file then return nil end - local size = file:seek("end") -- get file size + local size = file:seek("end") -- get file size file:close() return size end @@ -873,10 +1010,8 @@ local function print_root_hash(machine, print) end local function store_memory_range(r, indent, output) - local function comment_default(u, v) - output(u == v and " -- default\n" or "\n") - end - output("{\n", i) + local function comment_default(u, v) output(u == v and " -- default\n" or "\n") end + output("{\n") output("%s start = 0x%x,", indent, r.start) comment_default(0, r.start) output("%s length = 0x%x,", indent, r.length) @@ -890,9 +1025,7 @@ local function store_memory_range(r, indent, output) end local function store_machine_config(config, output) - local function comment_default(u, v) - output(u == v and " -- default\n" or "\n") - end + local function comment_default(u, v) output(u == v and " -- default\n" or "\n") end local def if remote then @@ -906,7 +1039,7 @@ local function store_machine_config(config, output) local processor = config.processor or { x = {} } for i = 1, 31 do local xi = processor.x[i] or def.processor.x[i] - output(" 0x%x,", xi) + output(" 0x%x,", xi) comment_default(xi, def.processor.x[i]) end output(" },\n") @@ -914,24 +1047,22 @@ local function store_machine_config(config, output) for i = 0, 31 do local xi = processor.f[i] or def.processor.f[i] if i == 0 then - output(" [0] = 0x%x,", xi) + output(" [0] = 0x%x,", xi) else - output(" 0x%x,", xi) + output(" 0x%x,", xi) end comment_default(xi, def.processor.f[i]) end output(" },\n") local order = {} - for i,v in pairs(def.processor) do - if type(v) == "number" then - order[#order+1] = i - end + for i, v in pairs(def.processor) do + if type(v) == "number" then order[#order + 1] = i end end table.sort(order) - for i,csr in ipairs(order) do + for _, csr in ipairs(order) do local c = processor[csr] or def.processor[csr] output(" %s = 0x%x,", csr, c) - comment_default(c, def.processor[csr]) + comment_default(c, def.processor[csr]) end output(" },\n") local ram = config.ram or {} @@ -972,7 +1103,7 @@ local function store_machine_config(config, output) comment_default(clint.mtimecmp, def.clint.mtimecmp) output(" },\n") output(" flash_drive = {\n") - for i, f in ipairs(config.flash_drive) do + for _, f in ipairs(config.flash_drive) do output(" ") store_memory_range(f, " ", output) end @@ -991,19 +1122,18 @@ local function store_machine_config(config, output) store_memory_range(config.rollup.notice_hashes, " ", output) output(" },\n") end - local uarch = config.uarch or def.uarch output(" uarch = {\n") output(" ram = {\n") - output(" length = 0x%x,", uarch.ram.length or def.uarch.ram.length) - comment_default(uarch.ram.length, def.uarch.ram.length) - output(" image_filename = %q,", uarch.ram.image_filename or def.uarch.ram.image_filename) - comment_default(uarch.ram.image_filename, def.uarch.ram.image_filename) + output(" length = 0x%x,", config.uarch.ram.length or def.uarch.ram.length) + comment_default(config.uarch.ram.length, def.uarch.ram.length) + output(" image_filename = %q,", config.uarch.ram.image_filename or def.uarch.ram.image_filename) + comment_default(config.uarch.ram.image_filename, def.uarch.ram.image_filename) output(" },\n") output(" processor = {\n") output(" x = {\n") for i = 1, 31 do local xi = uarch.processor.x[i] or def.uarch.processor.x[i] - output(" 0x%x,", xi) + output(" 0x%x,", xi) comment_default(xi, def.uarch.processor.x[i]) end output(" },\n") @@ -1016,62 +1146,70 @@ local function store_machine_config(config, output) output("}\n") end -local function resolve_flash_lengths(label_order, image_filename, start, length) - for i, label in ipairs(label_order) do +local function resolve_flash_lengths(label_order, image_filename, length) + for _, label in ipairs(label_order) do local filename = image_filename[label] local len = length[label] local filelen if filename and filename ~= "" then - filelen = assert(get_file_length(filename), string.format( - "unable to find length of flash drive '%s' image file '%s'", - label, filename)) + filelen = assert( + get_file_length(filename), + string.format("unable to find length of flash drive '%s' image file '%s'", label, filename) + ) if len and len ~= filelen then - error(string.format("flash drive '%s' length (%u) and image file '%s' length (%u) do not match", label, - len, filename, filelen)) + error( + string.format( + "flash drive '%s' length (%u) and image file '%s' length (%u) do not match", + label, + len, + filename, + filelen + ) + ) else length[label] = filelen end elseif not len then - error(string.format( - "flash drive '%s' nas no length or image file", label)) + error(string.format("flash drive '%s' nas no length or image file", label)) end end end -local function resolve_flash_starts(label_order, image_filename, start, length) - local auto_start = 1<<55 +local function resolve_flash_starts(label_order, start) + local auto_start = 1 << 55 if next(start) == nil then - for i, label in ipairs(label_order) do + for _, label in ipairs(label_order) do start[label] = auto_start auto_start = auto_start + (1 << 52) end else local missing = {} local found = {} - for i, label in ipairs(label_order) do + for _, label in ipairs(label_order) do local quoted = string.format("'%s'", label) if start[label] then - found[#found+1] = quoted + found[#found + 1] = quoted else - missing[#missing+1] = quoted + missing[#missing + 1] = quoted end end if #missing > 0 then - error(string.format("flash drive start set for %s but missing for %s", - table.concat(found, ", "), table.concat(missing, ", "))) + error( + string.format( + "flash drive start set for %s but missing for %s", + table.concat(found, ", "), + table.concat(missing, ", ") + ) + ) end end end -local function dump_value_proofs(machine, desired_proofs, htif_console_getchar) - if #desired_proofs > 0 then - assert(not htif_console_getchar, - "proofs are meaningless in interactive mode") - end - for i, desired in ipairs(desired_proofs) do +local function dump_value_proofs(machine, desired_proofs, has_htif_console_getchar) + if #desired_proofs > 0 then assert(not has_htif_console_getchar, "proofs are meaningless in interactive mode") end + for _, desired in ipairs(desired_proofs) do local proof = machine:get_proof(desired.address, desired.log2_size) - local out = desired.filename and assert(io.open(desired.filename, "wb")) - or io.stdout + local out = desired.filename and assert(io.open(desired.filename, "wb")) or io.stdout out:write("{\n") util.dump_json_proof(proof, out, 1) out:write("}\n") @@ -1087,14 +1225,11 @@ local function append(a, b) end local function create_machine(config_or_dir, runtime) - if remote then - return remote.machine(config_or_dir, runtime) - end + if remote then return remote.machine(config_or_dir, runtime) end return cartesi.machine(config_or_dir, runtime) end -local machine - +local remote_shutdown_deleter = {} if remote_address then stderr("Connecting to %s remote cartesi machine at '%s'\n", remote_protocol, remote_address) local protocol = require("cartesi." .. remote_protocol) @@ -1107,30 +1242,31 @@ if remote_address then stderr("Connected: remote version is %d.%d.%d\n", v.major, v.minor, v.patch) local shutdown = function() remote.shutdown() end if remote_shutdown then - remote_shutdown = setmetatable({}, { __gc = function() - stderr("Shutting down remote cartesi machine\n") - pcall(shutdown) - end }) + setmetatable(remote_shutdown_deleter, { + __gc = function() + stderr("Shutting down remote cartesi machine\n") + pcall(shutdown) + end, + }) end end local runtime = { concurrency = { - update_merkle_tree = concurrency_update_merkle_tree - } + update_merkle_tree = concurrency_update_merkle_tree, + }, } +local main_machine if remote and not remote_create then - machine = remote.get_machine() + main_machine = remote.get_machine() elseif load_dir then stderr("Loading machine: please wait\n") - machine = create_machine(load_dir, runtime) + main_machine = create_machine(load_dir, runtime) else -- Resolve all device starts and lengths - resolve_flash_lengths(flash_label_order, flash_image_filename, flash_start, - flash_length) - resolve_flash_starts(flash_label_order, flash_image_filename, flash_start, - flash_length) + resolve_flash_lengths(flash_label_order, flash_image_filename, flash_length) + resolve_flash_starts(flash_label_order, flash_start) -- Build machine config local config = { @@ -1138,20 +1274,20 @@ else -- Request automatic default values for versioning CSRs mimpid = -1, marchid = -1, - mvendorid = -1 + mvendorid = -1, }, rom = { image_filename = rom_image_filename, - bootargs = rom_bootargs + bootargs = rom_bootargs, }, ram = { image_filename = ram_image_filename, - length = ram_length + length = ram_length, }, htif = { console_getchar = htif_console_getchar, yield_automatic = htif_yield_automatic, - yield_manual = htif_yield_manual + yield_manual = htif_yield_manual, }, rollup = rollup, uarch = uarch, @@ -1159,31 +1295,27 @@ else } local mtdparts = {} for i, label in ipairs(flash_label_order) do - config.flash_drive[#config.flash_drive+1] = { + config.flash_drive[#config.flash_drive + 1] = { image_filename = flash_image_filename[label], shared = flash_shared[label], start = flash_start[label], - length = flash_length[label] + length = flash_length[label], } - mtdparts[#mtdparts+1] = string.format("flash.%d:-(%s)", i-1, label) + mtdparts[#mtdparts + 1] = string.format("flash.%d:-(%s)", i - 1, label) end if #mtdparts > 0 then - config.rom.bootargs = append(config.rom.bootargs, "mtdparts=" .. - table.concat(mtdparts, ";")) + config.rom.bootargs = append(config.rom.bootargs, "mtdparts=" .. table.concat(mtdparts, ";")) end - config.rom.bootargs = append( - append(config.rom.bootargs, append_rom_bootargs), - quiet and ' splash=no' or '') + config.rom.bootargs = append(append(config.rom.bootargs, append_rom_bootargs), quiet and " splash=no" or "") if #exec_arguments > 0 then - config.rom.bootargs = append(config.rom.bootargs, "-- " .. - table.concat(exec_arguments, " ")) + config.rom.bootargs = append(config.rom.bootargs, "-- " .. table.concat(exec_arguments, " ")) end if load_config then local env = {} - local ok, ret = loadfile(load_config, 't', env) + local ok, ret = loadfile(load_config, "t", env) if ok then local chunk = ok ok, ret = pcall(chunk) @@ -1192,22 +1324,22 @@ else stderr("Failed to load machine config (%s):\n", load_config) error(ret) end - config = setmetatable(ret, {__index = config}) + config = setmetatable(ret, { __index = config }) end - machine = create_machine(config, runtime) + main_machine = create_machine(config, runtime) end -- obtain config from instantiated machine -local config = machine:get_initial_config() +local main_config = main_machine:get_initial_config() -for _,r in ipairs(memory_range_replace) do - machine:replace_memory_range(r) +for _, r in ipairs(memory_range_replace) do + main_machine:replace_memory_range(r) end if type(store_config) == "string" then store_config = assert(io.open(store_config, "w")) - store_machine_config(config, function (...) store_config:write(string.format(...)) end) + store_machine_config(main_config, function(...) store_config:write(string.format(...)) end) store_config:close() end @@ -1226,9 +1358,7 @@ local htif_yield_mode = { [cartesi.machine.HTIF_YIELD_AUTOMATIC] = "Automatic", } -local function is_power_of_two(value) - return value > 0 and ((value & (value - 1)) == 0) -end +local function is_power_of_two(value) return value > 0 and ((value & (value - 1)) == 0) end local function ilog2(value) value = assert(math.tointeger(value), "expected integer") @@ -1244,12 +1374,16 @@ end local function check_rollup_memory_range_config(range, name) assert(range, string.format("rollup range %s must be defined", name)) assert(not range.shared, string.format("rollup range %s cannot be shared", name)) - assert(is_power_of_two(range.length), string.format("rollup range %s length not a power of two (%u)", name, - range.length)) + assert( + is_power_of_two(range.length), + string.format("rollup range %s length not a power of two (%u)", name, range.length) + ) local log = ilog2(range.length) local aligned_start = (range.start >> log) << log - assert(aligned_start == range.start, - string.format("rollup range %s start not aligned to its power of two size", name)) + assert( + aligned_start == range.start, + string.format("rollup range %s start not aligned to its power of two size", name) + ) range.image_filename = nil end @@ -1269,7 +1403,7 @@ end local function get_and_print_yield(machine, htif) local cmd, reason, data = get_yield(machine) if cmd == cartesi.machine.HTIF_YIELD_AUTOMATIC and reason == cartesi.machine.HTIF_YIELD_REASON_PROGRESS then - stderr("Progress: %6.2f" .. (htif.console_getchar and "\n" or "\r"), data/10) + stderr("Progress: %6.2f" .. (htif.console_getchar and "\n" or "\r"), data / 10) else local cmd_str = htif_yield_mode[cmd] or "Unknown" local reason_str = htif_yield_reason[reason] or "unknown" @@ -1286,10 +1420,8 @@ local function save_rollup_hashes(machine, range, filename) local zeros = string.rep("\0", hash_len) local offset = 0 while offset < range.length do - local hash = machine:read_memory(range.start+offset, 32) - if hash == zeros then - break - end + local hash = machine:read_memory(range.start + offset, 32) + if hash == zeros then break end assert(f:write(hash)) offset = offset + hash_len end @@ -1299,9 +1431,7 @@ end local function instantiate_filename(pattern, values) -- replace escaped % with something safe pattern = string.gsub(pattern, "%\\%%", "\0") - pattern = string.gsub(pattern, "%%(%a)", function(s) - return values[s] or s - end) + pattern = string.gsub(pattern, "%%(%a)", function(s) return values[s] or s end) -- restore escaped % return (string.gsub(pattern, "\0", "%")) end @@ -1336,73 +1466,75 @@ local function load_rollup_query(machine, config, inspect) end local function save_rollup_advance_state_voucher(machine, config, advance) - local values = { e = advance.epoch_index, i = advance.next_input_index-1, o = advance.voucher_index } + local values = { e = advance.epoch_index, i = advance.next_input_index - 1, o = advance.voucher_index } local name = instantiate_filename(advance.voucher, values) stderr("Storing %s\n", name) local f = assert(io.open(name, "wb")) -- skip address and offset to reach payload length - local length = string.unpack(">I8", machine:read_memory(config.start+3*32-8, 8)) + local length = string.unpack(">I8", machine:read_memory(config.start + 3 * 32 - 8, 8)) -- add address, offset, and payload length to amount to be read - length = length+3*32 + length = length + 3 * 32 assert(f:write(machine:read_memory(config.start, length))) f:close() end local function save_rollup_advance_state_notice(machine, config, advance) - local values = { e = advance.epoch_index, i = advance.next_input_index-1, o = advance.notice_index } + local values = { e = advance.epoch_index, i = advance.next_input_index - 1, o = advance.notice_index } local name = instantiate_filename(advance.notice, values) stderr("Storing %s\n", name) local f = assert(io.open(name, "wb")) -- skip offset to reach payload length - local length = string.unpack(">I8", machine:read_memory(config.start+2*32-8, 8)) + local length = string.unpack(">I8", machine:read_memory(config.start + 2 * 32 - 8, 8)) -- add offset and payload length to amount to be read - length = length+2*32 + length = length + 2 * 32 assert(f:write(machine:read_memory(config.start, length))) f:close() end local function dump_exception(machine, config) -- skip offset to reach payload length - local length = string.unpack(">I8", machine:read_memory(config.start+2*32-8, 8)) + local length = string.unpack(">I8", machine:read_memory(config.start + 2 * 32 - 8, 8)) -- add offset and payload length to amount to be read - local payload = machine:read_memory(config.start+2*32, length) - stderr("Rollup exception with payload: %q\n", payload); + local payload = machine:read_memory(config.start + 2 * 32, length) + stderr("Rollup exception with payload: %q\n", payload) end local function save_rollup_advance_state_report(machine, config, advance) - local values = { e = advance.epoch_index, i = advance.next_input_index-1, o = advance.report_index } + local values = { e = advance.epoch_index, i = advance.next_input_index - 1, o = advance.report_index } local name = instantiate_filename(advance.report, values) stderr("Storing %s\n", name) local f = assert(io.open(name, "wb")) -- skip offset to reach payload length - local length = string.unpack(">I8", machine:read_memory(config.start+2*32-8, 8)) + local length = string.unpack(">I8", machine:read_memory(config.start + 2 * 32 - 8, 8)) -- add offset and payload length to amount to be read - length = length+2*32 + length = length + 2 * 32 assert(f:write(machine:read_memory(config.start, length))) f:close() end local function save_rollup_inspect_state_report(machine, config, inspect) - local values = { o = inspect.report_index } + local values = { o = inspect.report_index } local name = instantiate_filename(inspect.report, values) stderr("Storing %s\n", name) local f = assert(io.open(name, "wb")) -- skip offset to reach payload length - local length = string.unpack(">I8", machine:read_memory(config.start+2*32-8, 8)) + local length = string.unpack(">I8", machine:read_memory(config.start + 2 * 32 - 8, 8)) -- add offset and payload length to amount to be read - length = length+2*32 + length = length + 2 * 32 assert(f:write(machine:read_memory(config.start, length))) f:close() end -local function store_machine(machine, config, store_dir) +local function store_machine(machine, config, dir) assert(not config.htif.console_getchar, "hashes are meaningless in interactive mode") stderr("Storing machine: please wait\n") local h = util.hexhash(machine:get_root_hash()) - local name = instantiate_filename(store_dir, { h = h }) + local name = instantiate_filename(dir, { h = h }) machine:store(name) end +local machine = main_machine +local config = main_config if json_steps then assert(not rollup_advance, "json-steps and rollup advance state are incompatible") assert(not rollup_inspect, "json-steps and rollup inspect state are incompatible") @@ -1422,9 +1554,9 @@ if json_steps then local final_mcycle = machine:read_mcycle() local final_uarch_cycle = machine:read_uarch_cycle() -- Save log recorded during micro step - if steps_count > 1 then json_steps:write(',\n') end - util.dump_json_log(log, init_mcycle, init_uarch_cycle, final_mcycle, final_uarch_cycle, json_steps, 1) - if machine:read_uarch_halt_flag() then + if steps_count > 1 then json_steps:write(",\n") end + util.dump_json_log(log, init_mcycle, init_uarch_cycle, final_mcycle, final_uarch_cycle, json_steps, 1) + if machine:read_uarch_halt_flag() then -- microarchitecture halted because it finished interpreting a whole mcycle machine:reset_iflags_Y() -- move past any potential yield -- Reset uarch_halt_flag in order to allow interpreting the next mcycle @@ -1436,27 +1568,23 @@ if json_steps then end stderr("%u.%u -> %u.%u\n", init_mcycle, init_uarch_cycle, final_mcycle, final_uarch_cycle) end - json_steps:write('\n]\n') + json_steps:write("\n]\n") json_steps:close() - if store_dir then - store_machine(machine, config, store_dir) - end + if store_dir then store_machine(machine, config, store_dir) end else local gdb_stub if gdb_address then - assert(periodic_hashes_start == 0 and periodic_hashes_period == math.maxinteger, - "periodic hashing is not supported when debugging") - gdb_stub = require"cartesi.gdbstub".new(machine) - local address, port = gdb_address:match('^(.*):(%d+)$') + assert( + periodic_hashes_start == 0 and periodic_hashes_period == math.maxinteger, + "periodic hashing is not supported when debugging" + ) + gdb_stub = require("cartesi.gdbstub").new(machine) + local address, port = gdb_address:match("^(.*):(%d+)$") assert(address and port, "invalid address for GDB") gdb_stub:listen_and_wait_gdb(address, tonumber(port)) end - if config.htif.console_getchar then - stderr("Running in interactive mode!\n") - end - if store_config == stderr then - store_machine_config(config, stderr) - end + if config.htif.console_getchar then stderr("Running in interactive mode!\n") end + if store_config == stderr then store_machine_config(config, stderr) end if rollup_advance or rollup_inspect then check_rollup_htif_config(config.htif) assert(config.rollup, "rollup device must be present") @@ -1495,9 +1623,9 @@ else while math.ult(cycles, max_mcycle) do local next_mcycle = math.min(next_hash_mcycle, max_mcycle) if gdb_stub and gdb_stub:is_connected() then - gdb_stub:run(next_mcycle) + gdb_stub:run(next_mcycle) else - machine:run(next_mcycle) + machine:run(next_mcycle) end cycles = machine:read_mcycle() -- deal with halt @@ -1512,7 +1640,7 @@ else break -- deal with yield manual elseif machine:read_iflags_Y() then - local cmd, reason = get_and_print_yield(machine, config.htif) + local _, reason = get_and_print_yield(machine, config.htif) -- there are advance state inputs to feed if reason == cartesi.machine.HTIF_YIELD_REASON_TX_EXCEPTION then dump_exception(machine, config.rollup.tx_buffer) @@ -1529,14 +1657,10 @@ else assert(reason == cartesi.machine.HTIF_YIELD_REASON_RX_ACCEPTED, "invalid manual yield reason") end stderr("\nEpoch %d before input %d\n", rollup_advance.epoch_index, rollup_advance.next_input_index) - if rollup_advance.hashes then - print_root_hash(machine) - end + if rollup_advance.hashes then print_root_hash(machine) end machine:snapshot() load_rollup_input_and_metadata(machine, config.rollup, rollup_advance) - if rollup_advance.hashes then - print_root_hash(machine) - end + if rollup_advance.hashes then print_root_hash(machine) end machine:reset_iflags_Y() machine:write_htif_fromhost_data(0) -- tell machine it is an rollup_advance state, but this is default rollup_advance.voucher_index = 0 @@ -1561,7 +1685,7 @@ else end -- deal with yield automatic elseif machine:read_iflags_X() then - local cmd, reason = get_and_print_yield(machine, config.htif) + local _, reason = get_and_print_yield(machine, config.htif) -- we have fed an advance state input if rollup_advance and rollup_advance.next_input_index > rollup_advance.input_index_begin then if reason == cartesi.machine.HTIF_YIELD_REASON_TX_VOUCHER then @@ -1575,7 +1699,7 @@ else rollup_advance.report_index = rollup_advance.report_index + 1 end -- ignore other reasons - -- we have feed the inspect state query + -- we have feed the inspect state query elseif rollup_inspect and not rollup_inspect.query then if reason == cartesi.machine.HTIF_YIELD_REASON_TX_REPORT then save_rollup_inspect_state_report(machine, config.rollup.tx_buffer, rollup_inspect) @@ -1585,9 +1709,7 @@ else end -- otherwise ignore end - if machine:read_iflags_Y() then - break - end + if machine:read_iflags_Y() then break end if cycles == next_hash_mcycle then print_root_hash(machine) next_hash_mcycle = next_hash_mcycle + periodic_hashes_period @@ -1600,44 +1722,36 @@ else if machine:run_uarch(max_uarch_cycle) == cartesi.UARCH_BREAK_REASON_HALTED then -- Microarchitecture halted. This means that one "macro" instruction was totally executed -- The mcycle counter was incremented, unless the machine was already halted - if machine:read_iflags_H() and not previously_halted then - stderr("Halted\n") - end + if machine:read_iflags_H() and not previously_halted then stderr("Halted\n") end stderr("Cycles: %u\n", machine:read_mcycle()) - if auto_reset_uarch_state then + if auto_reset_uarch_state then machine:reset_uarch_state() else stderr("uCycles: %u\n", machine:read_uarch_cycle()) end end end - if gdb_stub then - gdb_stub:close() - end + if gdb_stub then gdb_stub:close() end if step_uarch then assert(not config.htif.console_getchar, "micro step proof is meaningless in interactive mode") stderr("Gathering micro step log: please wait\n") - util.dump_log(machine:step_uarch{ proofs = true, annotations = true }, io.stderr) - end - if dump_pmas then - machine:dump_pmas() + util.dump_log(machine:step_uarch({ proofs = true, annotations = true }), io.stderr) end + if dump_pmas then machine:dump_pmas() end if final_hash then assert(not config.htif.console_getchar, "hashes are meaningless in interactive mode") print_root_hash(machine, stderr_unsilenceable) end dump_value_proofs(machine, final_proof, config.htif.console_getchar) - if store_dir then - store_machine(machine, config, store_dir) - end + if store_dir then store_machine(machine, config, store_dir) end if assert_rolling_template then local cmd, reason = get_yield(machine) - if not (cmd == cartesi.machine.HTIF_YIELD_MANUAL and reason == cartesi.machine.HTIF_YIELD_REASON_RX_ACCEPTED) then + if + not (cmd == cartesi.machine.HTIF_YIELD_MANUAL and reason == cartesi.machine.HTIF_YIELD_REASON_RX_ACCEPTED) + then exit_code = 2 end end - if not remote or remote_destroy then - machine:destroy() - end + if not remote or remote_destroy then machine:destroy() end os.exit(exit_code, true) end diff --git a/src/cartesi/gdbstub.lua b/src/cartesi/gdbstub.lua index 804d1ec45..a028ddbd6 100644 --- a/src/cartesi/gdbstub.lua +++ b/src/cartesi/gdbstub.lua @@ -17,10 +17,10 @@ local GDBSTUB_DEBUG_PROTOCOL = false local signals = { - SIGINT = 2, -- signal sent to GDB when reaching a fixed number of cycle steps - SIGQUIT = 3, -- signal sent to GDB when max cycle is reached - SIGTRAP = 5, -- signal sent to GDB when a breakpoint is reached - SIGTERM = 15, -- signal sent to GDB when the machine halts + SIGINT = 2, -- signal sent to GDB when reaching a fixed number of cycle steps + SIGQUIT = 3, -- signal sent to GDB when max cycle is reached + SIGTRAP = 5, -- signal sent to GDB when a breakpoint is reached + SIGTERM = 15, -- signal sent to GDB when the machine halts } local GDBStub = {} @@ -28,542 +28,494 @@ GDBStub.__index = GDBStub -- Returns x with the order of the bytes reversed. -- Used to convert 64 bit integers between little-endian and big-endian. -local function bswap64(x) - return ('I8'):pack(x)) -end +local function bswap64(x) return ("I8"):pack(x)) end -local function reg2hex(x) - return ('%016x'):format(bswap64(x)) -end +local function reg2hex(x) return ("%016x"):format(bswap64(x)) end -local function byte2hex(x) - return ('%02x'):format(x) -end +local function byte2hex(x) return ("%02x"):format(x) end -local function chr2hex(c) - return ('%02x'):format(c:byte()) -end +local function chr2hex(c) return ("%02x"):format(c:byte()) end -local function str2hex(s) - return s:gsub('.', chr2hex) -end +local function str2hex(s) return s:gsub(".", chr2hex) end -local function hex2int(x) - return tonumber(x, 16) -end +local function hex2int(x) return tonumber(x, 16) end -local function hex2reg(x) - return bswap64(hex2int(x)) -end +local function hex2reg(x) return bswap64(hex2int(x)) end -local function hex2chr(x) - return string.char(tonumber(x, 16)) -end +local function hex2chr(x) return string.char(tonumber(x, 16)) end -local function hex2str(s) - return s:gsub('%x%x', hex2chr) -end +local function hex2str(s) return s:gsub("%x%x", hex2chr) end -local function xorchr(c) - return string.char(c:byte() ~ 0x20) -end +local function xorchr(c) return string.char(c:byte() ~ 0x20) end local function stderr(fmt, ...) - io.stderr:write(string.format(fmt, ...), '\n') - io.stderr:flush() + io.stderr:write(string.format(fmt, ...), "\n") + io.stderr:flush() end -- Creates a new GDBStub. function GDBStub.new(machine) - return setmetatable({ - machine=machine, - breakpoints={} - }, GDBStub) + return setmetatable({ + machine = machine, + breakpoints = {}, + }, GDBStub) end -- Listens at address:port and waits GDB to connect. function GDBStub:listen_and_wait_gdb(address, port) - address = address or '127.0.0.1' - port = port or 1234 - -- listens and wair for GDB connection - local socket = require("socket") - local server = assert(socket.bind(address, port)) - stderr('Waiting GDB to connect at %s:%d...', address, port) - local conn = assert(server:accept()) - -- GDB connected, we can close the server - assert(server:close()) - -- the first received byte should be '+' - local c = assert(conn:receive(1)) - assert(c == '+', 'expected acknowledge character on connection start') - stderr('GDB connected!') - -- enable TCP nodelay, necessary to have fast interactive session - assert(conn:setoption('tcp-nodelay', true)) - self.conn = conn + address = address or "127.0.0.1" + port = port or 1234 + -- listens and wair for GDB connection + local socket = require("socket") + local server = assert(socket.bind(address, port)) + stderr("Waiting GDB to connect at %s:%d...", address, port) + local conn = assert(server:accept()) + -- GDB connected, we can close the server + assert(server:close()) + -- the first received byte should be '+' + local c = assert(conn:receive(1)) + assert(c == "+", "expected acknowledge character on connection start") + stderr("GDB connected!") + -- enable TCP nodelay, necessary to have fast interactive session + assert(conn:setoption("tcp-nodelay", true)) + self.conn = conn end -- Receive a packet from GDB. function GDBStub:_recv() - -- receive packet data - local escaped_data, sum = {}, 0 - while true do - local c = assert(self.conn:receive(1)) - if c == '#' then break end - sum = sum + c:byte() - escaped_data[#escaped_data+1] = c - end - escaped_data = table.concat(escaped_data) - -- validate checksum - local checksum = assert(self.conn:receive(2)) - if sum % 256 ~= hex2int(checksum) then - assert(self.conn:send('-')) -- request retransmission - stderr('Received a packet with invalid checksum from GDB') - return nil - end - -- send packet acknowledge - if not self.noack then - assert(self.conn:send('+')) -- send acknowledge packet - end - -- escape packet data - local data = escaped_data:gsub('}(.)', xorchr) - if GDBSTUB_DEBUG_PROTOCOL then - stderr('Packet recv: %s', data) - end - return data + -- receive packet data + local escaped_data, sum = {}, 0 + while true do + local c = assert(self.conn:receive(1)) + if c == "#" then break end + sum = sum + c:byte() + escaped_data[#escaped_data + 1] = c + end + escaped_data = table.concat(escaped_data) + -- validate checksum + local checksum = assert(self.conn:receive(2)) + if sum % 256 ~= hex2int(checksum) then + assert(self.conn:send("-")) -- request retransmission + stderr("Received a packet with invalid checksum from GDB") + return nil + end + -- send packet acknowledge + if not self.noack then + assert(self.conn:send("+")) -- send acknowledge packet + end + -- escape packet data + local data = escaped_data:gsub("}(.)", xorchr) + if GDBSTUB_DEBUG_PROTOCOL then stderr("Packet recv: %s", data) end + return data end -- Send a packet to GDB. function GDBStub:_send(data) - -- escape packet data - local escape_set = {['#']=true, ['$']=true, ['}']=true, ['*']=true} - local escaped_data = data:gsub('.', function(c) - return escape_set[c] and '}'..xorchr(c) or c - end) - -- compute checksum - local sum = 0 - for c in escaped_data:gmatch('.') do - sum = sum + c:byte() - end - local checksum = byte2hex(sum % 256) - -- send packet - local packet_data = '$'..escaped_data..'#'..checksum - assert(self.conn:send(packet_data)) - if GDBSTUB_DEBUG_PROTOCOL then - stderr('Packet sent: %s', data) - end - -- wait for packet acknowledge - if not self.noack then - local c = assert(self.conn:receive(1)) - assert(c == '+', 'GDB did not acknowledged last packet') - end - return true + -- escape packet data + local escape_set = { ["#"] = true, ["$"] = true, ["}"] = true, ["*"] = true } + local escaped_data = data:gsub(".", function(c) return escape_set[c] and "}" .. xorchr(c) or c end) + -- compute checksum + local sum = 0 + for c in escaped_data:gmatch(".") do + sum = sum + c:byte() + end + local checksum = byte2hex(sum % 256) + -- send packet + local packet_data = "$" .. escaped_data .. "#" .. checksum + assert(self.conn:send(packet_data)) + if GDBSTUB_DEBUG_PROTOCOL then stderr("Packet sent: %s", data) end + -- wait for packet acknowledge + if not self.noack then + local c = assert(self.conn:receive(1)) + assert(c == "+", "GDB did not acknowledged last packet") + end + return true end -- Sent to GDB when a packet is handled with success. -function GDBStub:_send_ok() - return self:_send('OK') -end +function GDBStub:_send_ok() return self:_send("OK") end -- Sent to GDB when a packet is not handled, due some error. -function GDBStub:_send_error() - return self:_send('E01') -end +function GDBStub:_send_error() return self:_send("E01") end -- Sent to GDB when a packet is not supported. -function GDBStub:_send_unsupported() - return self:_send('') -end +function GDBStub:_send_unsupported() return self:_send("") end -- Sent to GDB when stopping the machine, using `signal` as the reason for stopping. -function GDBStub:_send_signal(signal) - return self:_send('S'..byte2hex(signal)) -end +function GDBStub:_send_signal(signal) return self:_send("S" .. byte2hex(signal)) end -- Sent to GDB when replying custom command packets. -function GDBStub:_send_rcmd_reply(res) - return self:_send('O'..str2hex(tostring(res))) -end +function GDBStub:_send_rcmd_reply(res) return self:_send("O" .. str2hex(tostring(res))) end -- GDB is asking for a reason why the target halted. -function GDBStub:_handle_target_halt() - return self:_send_signal(signals.SIGTRAP) -end +function GDBStub:_handle_target_halt() return self:_send_signal(signals.SIGTRAP) end -- GDB is asking to execute "v" commands. function GDBStub:_handle_v_command(_, command) - if command == 'vMustReplyEmpty' then -- GDB is testing if our protocol responds with unknown packets - return self:_send('') -- Must reply with an empty packet - elseif command == 'vCont?' then -- GDB is checking if we support vCont actions. - return self:_send_unsupported() - elseif command:find('^vKill') then -- GDB is killing the machine - return self:_handle_kill() - end + if command == "vMustReplyEmpty" then -- GDB is testing if our protocol responds with unknown packets + return self:_send("") -- Must reply with an empty packet + elseif command == "vCont?" then -- GDB is checking if we support vCont actions. + return self:_send_unsupported() + elseif command:find("^vKill") then -- GDB is killing the machine + return self:_handle_kill() + end end -- GDB is querying machine or protocol information. function GDBStub:_handle_query(_, query) - if query:find('^qSupported:') then -- GDB is checking for supported features. - local supported_features = { - ["hwbreak"] = true, -- we only support hardware breakpoints - } - local res = { - 'QStartNoAckMode+' -- we want to disable acknowledge packets, since its redundant with TCP - } - for feature in query:gmatch('([A-Za-z-]+)%+;?') do - if supported_features[feature] then - table.insert(res, feature..'+') - end - end - -- reply with features we support - res = table.concat(res, ';') - return self:_send(res) - elseif query == 'qTStatus' then -- GDB is asking whether a trace experiment is currently running - return self:_send_unsupported() - elseif query == 'qfThreadInfo' or query == 'qC' or query:find('^qL') then -- GDB is asking thread info - return self:_send_unsupported() - elseif query == 'qAttached' then -- GDB is asking if we are attached to an existing process - return self:_send('1') -- reply with '1', indicating that our remote server is attached - elseif query == 'qSymbol::' then -- GDB is prepared to serve symbol lookup requests - return self:_send_ok() - elseif query == 'qOffsets' then-- GDB is requesting section offsets that the target used when relocating the image - -- let GDB decide the text offset based in the binary file being debugged - return self:_send_unsupported() - elseif query:find('^qRcmd,') then -- custom command - local payload = hex2str(query:sub(7)) - if payload:find('^stepc %d+$') then -- step a fixed number of cycles - self.mcycle_limit = self.machine:read_mcycle() + tonumber(payload:match('^stepc (%d+)$')) - return self:_send_ok() - elseif payload:find('^stepu %d+$') then -- step until a cycle number - self.mcycle_limit = math.max(self.machine:read_mcycle(), tonumber(payload:match('^stepu (%d+)$'))) - return self:_send_ok() - elseif payload == 'stepc_clear' then -- remove stepping breakpoint - self.mcycle_limit = nil - return self:_send_ok() - elseif payload == 'cycles' then -- print current cycle - self:_send_rcmd_reply(string.format('%u\n', self.machine:read_mcycle())) - return self:_send_ok() - elseif payload:find('^csr [%w_]+$') then -- read machine CSRs - local csr_name = payload:match('^csr ([%w_]+)$') - local read_method_name = 'read_'..csr_name - local read_method = self.machine[read_method_name] - if not read_method then return self:_send_unsupported() end - local ok, res = pcall(read_method, self.machine) - if not ok or res == nil then return self:_send_unsupported() end - if math.type(res) == 'integer' then - self:_send_rcmd_reply(string.format('0x%x (%d)\n', res, res)) - else - self:_send_rcmd_reply(tostring(res)..'\n') - end - return self:_send_ok() - elseif payload:find('^csr [%w_]+%=.*$') then -- write machine CSRs - local csr_name, val = payload:match('^csr ([%w_]+)%=(.*)$') - local write_method_name = 'write_'..csr_name - local read_method_name = 'read_'..csr_name - local write_method = self.machine[write_method_name] - local read_method = self.machine[read_method_name] - if not write_method or not read_method then return self:_send_unsupported() end - val = tonumber(val) - if not val or math.type(val) ~= 'integer' then - self:_send_rcmd_reply('ERROR: malformed CSR integer\n') - return self:_send_ok() - end - local write_ok = pcall(write_method, self.machine, val) - if not write_ok then return self:_send_unsupported() end - -- print the new CSR value - local ok, res = pcall(read_method, self.machine) - if ok and res ~= nil then - if math.type(res) == 'integer' then - self:_send_rcmd_reply(string.format('%s = 0x%x (%d)\n', csr_name, res, res)) - else - self:_send_rcmd_reply(tostring(res)..'\n') + if query:find("^qSupported:") then -- GDB is checking for supported features. + local supported_features = { + ["hwbreak"] = true, -- we only support hardware breakpoints + } + local res = { + "QStartNoAckMode+", -- we want to disable acknowledge packets, since its redundant with TCP + } + for feature in query:gmatch("([A-Za-z-]+)%+;?") do + if supported_features[feature] then table.insert(res, feature .. "+") end end - end - return self:_send_ok() - elseif payload == 'hash' then -- print machine state hash - if not self.performed_first_hash then - self:_send_rcmd_reply('GDB may complain about packet errors due to command timeout, ignore them.\n') - self:_send_rcmd_reply('Performing first hash, this may take a while...\n') - self.performed_first_hash = true - end - local hash = self.machine:get_root_hash() - self:_send_rcmd_reply(string.format("%u: %s\n", self.machine:read_mcycle(), str2hex(hash))) - return self:_send_ok() - elseif payload:find('^store .*$') then -- store the machine state - local store_dir = payload:match('^store (.*)$') - self:_send_rcmd_reply('GDB may complain about packet errors due to command timeout, ignore them.\n') - self:_send_rcmd_reply('Storing the machine, this may take a while...\n') - local ok, res = pcall(self.machine.store, self.machine, store_dir) - if not ok then - self:_send_rcmd_reply(string.format('ERROR: machine store failed: %s\n', res)) - else - self:_send_rcmd_reply(string.format('machine stated saved to "%s"\n', store_dir)) - end - return self:_send_ok() - elseif payload:find([[^lua ["'].*["']$]]) then -- execute arbitrary lua code - local source = payload:match([[^lua ["'](.*)["']$]]) - local env = {machine=self.machine} - setmetatable(env, {__index = _ENV}) - local func, err = load(source, "@gdb_command_chunk", "t", env) - if not func then - self:_send_rcmd_reply(string.format('ERROR: %s\n', err)) - else - local ok, ret = pcall(func) - if not ok then - self:_send_rcmd_reply(string.format('ERROR: %s\n', ret)) - elseif ret ~= nil then - self:_send_rcmd_reply(tostring(ret)..'\n') - end - end - return self:_send_ok() - elseif payload:find('^breakpc [xXa-fA-F0-9]+$') then - local pcstr = payload:match('^breakpc ([xXa-fA-F0-9]+)$') - local pc = tonumber(pcstr) - if pc then - if self.breakpoints[pc] then - self.breakpoints[pc] = nil - self:_send_rcmd_reply(string.format("disabled PC breakpoint at 0x%x\n", pc)) - else - self:_send_rcmd_reply(string.format("enabled PC breakpoint at 0x%x\n", pc)) - self.breakpoints[pc] = true + -- reply with features we support + res = table.concat(res, ";") + return self:_send(res) + elseif query == "qTStatus" then -- GDB is asking whether a trace experiment is currently running + return self:_send_unsupported() + elseif query == "qfThreadInfo" or query == "qC" or query:find("^qL") then -- GDB is asking thread info + return self:_send_unsupported() + elseif query == "qAttached" then -- GDB is asking if we are attached to an existing process + return self:_send("1") -- reply with '1', indicating that our remote server is attached + elseif query == "qSymbol::" then -- GDB is prepared to serve symbol lookup requests + return self:_send_ok() + elseif query == "qOffsets" then -- GDB is requesting section offsets that the target used when relocating the image + -- let GDB decide the text offset based in the binary file being debugged + return self:_send_unsupported() + elseif query:find("^qRcmd,") then -- custom command + local payload = hex2str(query:sub(7)) + if payload:find("^stepc %d+$") then -- step a fixed number of cycles + self.mcycle_limit = self.machine:read_mcycle() + tonumber(payload:match("^stepc (%d+)$")) + return self:_send_ok() + elseif payload:find("^stepu %d+$") then -- step until a cycle number + self.mcycle_limit = math.max(self.machine:read_mcycle(), tonumber(payload:match("^stepu (%d+)$"))) + return self:_send_ok() + elseif payload == "stepc_clear" then -- remove stepping breakpoint + self.mcycle_limit = nil + return self:_send_ok() + elseif payload == "cycles" then -- print current cycle + self:_send_rcmd_reply(string.format("%u\n", self.machine:read_mcycle())) + return self:_send_ok() + elseif payload:find("^csr [%w_]+$") then -- read machine CSRs + local csr_name = payload:match("^csr ([%w_]+)$") + local read_method_name = "read_" .. csr_name + local read_method = self.machine[read_method_name] + if not read_method then return self:_send_unsupported() end + local ok, res = pcall(read_method, self.machine) + if not ok or res == nil then return self:_send_unsupported() end + if math.type(res) == "integer" then + self:_send_rcmd_reply(string.format("0x%x (%d)\n", res, res)) + else + self:_send_rcmd_reply(tostring(res) .. "\n") + end + return self:_send_ok() + elseif payload:find("^csr [%w_]+%=.*$") then -- write machine CSRs + local csr_name, val = payload:match("^csr ([%w_]+)%=(.*)$") + local write_method_name = "write_" .. csr_name + local read_method_name = "read_" .. csr_name + local write_method = self.machine[write_method_name] + local read_method = self.machine[read_method_name] + if not write_method or not read_method then return self:_send_unsupported() end + val = tonumber(val) + if not val or math.type(val) ~= "integer" then + self:_send_rcmd_reply("ERROR: malformed CSR integer\n") + return self:_send_ok() + end + local write_ok = pcall(write_method, self.machine, val) + if not write_ok then return self:_send_unsupported() end + -- print the new CSR value + local ok, res = pcall(read_method, self.machine) + if ok and res ~= nil then + if math.type(res) == "integer" then + self:_send_rcmd_reply(string.format("%s = 0x%x (%d)\n", csr_name, res, res)) + else + self:_send_rcmd_reply(tostring(res) .. "\n") + end + end + return self:_send_ok() + elseif payload == "hash" then -- print machine state hash + if not self.performed_first_hash then + self:_send_rcmd_reply("GDB may complain about packet errors due to command timeout, ignore them.\n") + self:_send_rcmd_reply("Performing first hash, this may take a while...\n") + self.performed_first_hash = true + end + local hash = self.machine:get_root_hash() + self:_send_rcmd_reply(string.format("%u: %s\n", self.machine:read_mcycle(), str2hex(hash))) + return self:_send_ok() + elseif payload:find("^store .*$") then -- store the machine state + local store_dir = payload:match("^store (.*)$") + self:_send_rcmd_reply("GDB may complain about packet errors due to command timeout, ignore them.\n") + self:_send_rcmd_reply("Storing the machine, this may take a while...\n") + local ok, res = pcall(self.machine.store, self.machine, store_dir) + if not ok then + self:_send_rcmd_reply(string.format("ERROR: machine store failed: %s\n", res)) + else + self:_send_rcmd_reply(string.format('machine stated saved to "%s"\n', store_dir)) + end + return self:_send_ok() + elseif payload:find([[^lua ["'].*["']$]]) then -- execute arbitrary lua code + local source = payload:match([[^lua ["'](.*)["']$]]) + local env = { machine = self.machine } + setmetatable(env, { __index = _ENV }) + local func, err = load(source, "@gdb_command_chunk", "t", env) + if not func then + self:_send_rcmd_reply(string.format("ERROR: %s\n", err)) + else + local ok, ret = pcall(func) + if not ok then + self:_send_rcmd_reply(string.format("ERROR: %s\n", ret)) + elseif ret ~= nil then + self:_send_rcmd_reply(tostring(ret) .. "\n") + end + end + return self:_send_ok() + elseif payload:find("^breakpc [xXa-fA-F0-9]+$") then + local pcstr = payload:match("^breakpc ([xXa-fA-F0-9]+)$") + local pc = tonumber(pcstr) + if pc then + if self.breakpoints[pc] then + self.breakpoints[pc] = nil + self:_send_rcmd_reply(string.format("disabled PC breakpoint at 0x%x\n", pc)) + else + self:_send_rcmd_reply(string.format("enabled PC breakpoint at 0x%x\n", pc)) + self.breakpoints[pc] = true + end + else + self:_send_rcmd_reply(string.format("ERROR: malformed PC address '%s'\n", pcstr)) + end + return self:_send_ok() + else -- invalid command + return self:_send_unsupported() end - else - self:_send_rcmd_reply(string.format("ERROR: malformed PC address '%s'\n", pcstr)) - end - return self:_send_ok() - else -- invalid command - return self:_send_unsupported() end - end end -- GDB is setting a query feature. function GDBStub:_handle_query_set(_, query) - if query == 'QStartNoAckMode' then - if self:_send_ok() then - self.noack = true - return true + if query == "QStartNoAckMode" then + if self:_send_ok() then + self.noack = true + return true + end end - end end -- GDB is setting the thread for the next resume commands. function GDBStub:_handle_set_thread(payload) - local c, thread_id = payload:match('^(.)(.*)$') - assert(c and thread_id, 'unexpected set thread payload '..payload) - -- a thread-id can also be a literal '-1' to indicate all threads, or '0' to pick any thread. - if thread_id == '0' or thread_id == '-1' then - -- indicates that all following c commands refer to the given thread id - return self:_send_ok() - else - return self:_send_error() - end + local c, thread_id = payload:match("^(.)(.*)$") + assert(c and thread_id, "unexpected set thread payload " .. payload) + -- a thread-id can also be a literal '-1' to indicate all threads, or '0' to pick any thread. + if thread_id == "0" or thread_id == "-1" then + -- indicates that all following c commands refer to the given thread id + return self:_send_ok() + else + return self:_send_error() + end end -- GDB is writing a machine register. function GDBStub:_handle_write_reg(payload) - local reg, val = payload:match('^(%x+)=(%x+)$') - if not (reg and val) then return end - reg, val = hex2int(reg), hex2reg(val) - if reg > 0 and reg < 32 then -- machine registers - self.machine:write_x(reg, val) - return self:_send_ok() - elseif reg == 32 then -- machine program counter - self.machine:write_pc(val) - return self:_send_ok() - end + local reg, val = payload:match("^(%x+)=(%x+)$") + if not (reg and val) then return end + reg, val = hex2int(reg), hex2reg(val) + if reg > 0 and reg < 32 then -- machine registers + self.machine:write_x(reg, val) + return self:_send_ok() + elseif reg == 32 then -- machine program counter + self.machine:write_pc(val) + return self:_send_ok() + end end -- GDB is asking for all machine registers. function GDBStub:_handle_read_all_regs() - local res = {} - -- read general purposes registers - for i=0,31 do - table.insert(res, reg2hex(self.machine:read_x(i))) - end - -- read program counter - table.insert(res, reg2hex(self.machine:read_pc())) - res = table.concat(res) - return self:_send(res) + local res = {} + -- read general purposes registers + for i = 0, 31 do + table.insert(res, reg2hex(self.machine:read_x(i))) + end + -- read program counter + table.insert(res, reg2hex(self.machine:read_pc())) + res = table.concat(res) + return self:_send(res) end -- GDB is writing all machine registers. function GDBStub:_handle_write_all_regs(payload) - -- parse registers - local n = 0 - local regs = {} - for val in payload:gmatch('%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x') do - regs[n] = hex2reg(val) - n = n + 1 - end - if n ~= 33 then - stderr('GDB sent an unexpected number of registers (%d)', n) - return - end - -- write general purposes registers - for i=1,31 do - self.machine:write_x(i, regs[i]) - end - -- write program counter - self.machine:write_pc(regs[32]) - return self:_send_ok() + -- parse registers + local n = 0 + local regs = {} + for val in payload:gmatch("%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x") do + regs[n] = hex2reg(val) + n = n + 1 + end + if n ~= 33 then + stderr("GDB sent an unexpected number of registers (%d)", n) + return + end + -- write general purposes registers + for i = 1, 31 do + self.machine:write_x(i, regs[i]) + end + -- write program counter + self.machine:write_pc(regs[32]) + return self:_send_ok() end -- GDB wants to read machine memory. function GDBStub:_handle_read_mem(payload) - local address, length = payload:match('^(%x+),(%x+)$') - if not (address and length) then return end - address, length = hex2int(address), hex2int(length) - -- GDB may want to access invalid address ranges when debugging - local ok, mem = pcall(function() - return self.machine:read_virtual_memory(address, length) - end) - if not ok then return self:_send_error() end - local hexmem = str2hex(mem) - return self:_send(hexmem) + local address, length = payload:match("^(%x+),(%x+)$") + if not (address and length) then return end + address, length = hex2int(address), hex2int(length) + -- GDB may want to access invalid address ranges when debugging + local ok, mem = pcall(function() return self.machine:read_virtual_memory(address, length) end) + if not ok then return self:_send_error() end + local hexmem = str2hex(mem) + return self:_send(hexmem) end -- GDB wants to write machine memory. function GDBStub:_handle_write_mem(payload) - local address, length, hexmem = payload:match('^(%x+),(%x+):(%x+)$') - if not (address and length) then return end - address, length = hex2int(address), hex2int(length) - local mem = hex2str(hexmem) - assert(#mem == length) - -- GDB may want to access invalid address ranges when debugging - local ok = pcall(function() - self.machine:write_virtual_memory(address, mem) - end) - if ok then return self:_send_error() end - return self:_send_ok() + local address, length, hexmem = payload:match("^(%x+),(%x+):(%x+)$") + if not (address and length) then return end + address, length = hex2int(address), hex2int(length) + local mem = hex2str(hexmem) + assert(#mem == length) + -- GDB may want to access invalid address ranges when debugging + local ok = pcall(function() self.machine:write_virtual_memory(address, mem) end) + if ok then return self:_send_error() end + return self:_send_ok() end -- GDB is asking to let the machine continue. function GDBStub:_handle_continue() - local machine = self.machine - local mcycle = machine:read_mcycle() - local mcycle_end = self.max_mcycle - local ult = math.ult -- localized to speed up Lua loop - if self.mcycle_limit and ult(self.mcycle_limit, self.max_mcycle) then - -- we want to advance just a fixed number of cycles - mcycle_end = self.mcycle_limit - end - if next(self.breakpoints) then -- at least one breakpoint is set - local breakpoints = self.breakpoints -- localized to speed up Lua loop - -- need to run cycle by cycle, while checking breakpoints - while ult(mcycle, mcycle_end) do - machine:run(mcycle+1) - if breakpoints[machine:read_pc()] then -- breakpoint reached - return self:_send_signal(signals.SIGTRAP) - elseif machine:read_iflags_H() then -- machined halted + local machine = self.machine + local mcycle = machine:read_mcycle() + local mcycle_end = self.max_mcycle + local ult = math.ult -- localized to speed up Lua loop + if self.mcycle_limit and ult(self.mcycle_limit, self.max_mcycle) then + -- we want to advance just a fixed number of cycles + mcycle_end = self.mcycle_limit + end + if next(self.breakpoints) then -- at least one breakpoint is set + local breakpoints = self.breakpoints -- localized to speed up Lua loop + -- need to run cycle by cycle, while checking breakpoints + while ult(mcycle, mcycle_end) do + machine:run(mcycle + 1) + if breakpoints[machine:read_pc()] then -- breakpoint reached + return self:_send_signal(signals.SIGTRAP) + elseif machine:read_iflags_H() then -- machined halted + return self:_send_signal(signals.SIGTERM) + elseif machine:read_iflags_Y() or machine:read_iflags_X() then -- machine yielded + self.yielded = true + return true -- a reply will be sent to GDB in the next run loop + end + mcycle = machine:read_mcycle() + end + else -- no breakpoint set, we can run through the fast path + machine:run(mcycle_end) + end + if machine:read_iflags_H() then -- machine halted return self:_send_signal(signals.SIGTERM) - elseif machine:read_iflags_Y() or machine:read_iflags_X() then -- machine yielded + elseif machine:read_mcycle() == self.max_mcycle then -- reached max cycles + return self:_send_signal(signals.SIGQUIT) + elseif machine:read_iflags_Y() or machine:read_iflags_X() then -- machine yielded self.yielded = true return true -- a reply will be sent to GDB in the next run loop - end - mcycle = machine:read_mcycle() + else -- reached step cycles limit + return self:_send_signal(signals.SIGINT) end - else -- no breakpoint set, we can run through the fast path - machine:run(mcycle_end) - end - if machine:read_iflags_H() then -- machine halted - return self:_send_signal(signals.SIGTERM) - elseif machine:read_mcycle() == self.max_mcycle then -- reached max cycles - return self:_send_signal(signals.SIGQUIT) - elseif machine:read_iflags_Y() or machine:read_iflags_X() then -- machine yielded - self.yielded = true - return true -- a reply will be sent to GDB in the next run loop - else -- reached step cycles limit - return self:_send_signal(signals.SIGINT) - end end -- GDB is requesting to continue past halting signal. function GDBStub:_handle_continue_signal(payload) - local signum = payload:match('^(%x+)$') - if not signum then return end - return self:_handle_continue() + local signum = payload:match("^(%x+)$") + if not signum then return end + return self:_handle_continue() end local function parse_breakpoint_address(payload) - local type, address, kind = payload:match('^(%d+),(%x+),(%x+)$') - if address and type == '0' or (kind == '4' or kind == '2') then - return hex2int(address) - end + local type, address, kind = payload:match("^(%d+),(%x+),(%x+)$") + if address and type == "0" or (kind == "4" or kind == "2") then return hex2int(address) end end -- GDB is adding a breakpoint. function GDBStub:_handle_insert_breakpoint(payload) - local address = parse_breakpoint_address(payload) - if not address then return end - self.breakpoints[address] = true - return self:_send_ok() + local address = parse_breakpoint_address(payload) + if not address then return end + self.breakpoints[address] = true + return self:_send_ok() end -- GDB is removing a breakpoint. function GDBStub:_handle_remove_breakpoint(payload) - local address = parse_breakpoint_address(payload) - if not address then return end - self.breakpoints[address] = nil - return self:_send_ok() + local address = parse_breakpoint_address(payload) + if not address then return end + self.breakpoints[address] = nil + return self:_send_ok() end -- GDB is requesting to kill the machine. function GDBStub:_handle_kill() - stderr('GDB killed!') - self:_send_ok() - self:close() - os.exit(0) -- exit immediately - return true + stderr("GDB killed!") + self:_send_ok() + self:close() + os.exit(0) -- exit immediately + return true end -- GDB is detaching (debug session ended). function GDBStub:_handle_detach() - self:_send_ok() - self:close() - return true + self:_send_ok() + self:close() + return true end -- GDB is asking if extended debugging is supported. -function GDBStub:_handle_extended_debugging() - return self:_send_ok() -end +function GDBStub:_handle_extended_debugging() return self:_send_ok() end -- Packet handler for the GDB protocol. local gdbstub_handlers = { - ['!'] = GDBStub._handle_extended_debugging, - ['k'] = GDBStub._handle_kill, - ['?'] = GDBStub._handle_target_halt, - ['c'] = GDBStub._handle_continue, - ['C'] = GDBStub._handle_continue_signal, - ['D'] = GDBStub._handle_detach, - ['m'] = GDBStub._handle_read_mem, - ['M'] = GDBStub._handle_write_mem, - -- ['p'] = GDBStub._handle_read_reg, - ['P'] = GDBStub._handle_write_reg, - ['g'] = GDBStub._handle_read_all_regs, - ['G'] = GDBStub._handle_write_all_regs, - ['Z'] = GDBStub._handle_insert_breakpoint, - ['z'] = GDBStub._handle_remove_breakpoint, - ['v'] = GDBStub._handle_v_command, - ['q'] = GDBStub._handle_query, - ['Q'] = GDBStub._handle_query_set, - ['H'] = GDBStub._handle_set_thread, - -- explicitly unsupported, but GDB may send packets - ['X'] = false, -- write data to memory, where the data is transmitted in binary + ["!"] = GDBStub._handle_extended_debugging, + ["k"] = GDBStub._handle_kill, + ["?"] = GDBStub._handle_target_halt, + ["c"] = GDBStub._handle_continue, + ["C"] = GDBStub._handle_continue_signal, + ["D"] = GDBStub._handle_detach, + ["m"] = GDBStub._handle_read_mem, + ["M"] = GDBStub._handle_write_mem, + -- ['p'] = GDBStub._handle_read_reg, + ["P"] = GDBStub._handle_write_reg, + ["g"] = GDBStub._handle_read_all_regs, + ["G"] = GDBStub._handle_write_all_regs, + ["Z"] = GDBStub._handle_insert_breakpoint, + ["z"] = GDBStub._handle_remove_breakpoint, + ["v"] = GDBStub._handle_v_command, + ["q"] = GDBStub._handle_query, + ["Q"] = GDBStub._handle_query_set, + ["H"] = GDBStub._handle_set_thread, + -- explicitly unsupported, but GDB may send packets + ["X"] = false, -- write data to memory, where the data is transmitted in binary } -- Handle every packet received from GDB. function GDBStub:_handle_packet(data) - local c, payload = data:sub(1,1), data:sub(2) - local handler = gdbstub_handlers[data] or gdbstub_handlers[c] - if handler == false then -- explicitly unsupported - self:_send_unsupported() - elseif handler then -- packet handler found - local ok = handler(self, payload, data) - if not ok then -- the handler could not process the packet - stderr('Received an unexpected packet from GDB: %s', data) - self:_send_unsupported() + local c, payload = data:sub(1, 1), data:sub(2) + local handler = gdbstub_handlers[data] or gdbstub_handlers[c] + if handler == false then -- explicitly unsupported + self:_send_unsupported() + elseif handler then -- packet handler found + local ok = handler(self, payload, data) + if not ok then -- the handler could not process the packet + stderr("Received an unexpected packet from GDB: %s", data) + self:_send_unsupported() + end end - end end -- Runs the machine until GDB detaches. @@ -573,44 +525,40 @@ end -- Returns false if GDB session ended (GDB detached), -- otherwise true if GDB session is still going on (when yielding). function GDBStub:run(max_mcycle) - if not self.conn then return false end - -- set max mcycle for continue operations - self.max_mcycle = max_mcycle or math.maxinteger - -- when resuming from a yield, we have a pending GDB continue packet to reply - if self.yielded then - self.yielded = nil - self:_handle_continue() -- resume handling last continue packet - end - -- run while GDB is connected and the machine has not yielded - while self.conn and not self.yielded do - local c = assert(self.conn:receive(1)) - if c == '$' then -- incoming packet - local data = self:_recv() - if data then -- acknowledged packet - self:_handle_packet(data) - end - elseif c == '-' then -- GDB requested packet requested retransmission - -- GDB usually asks for retransmission for long running commands (or broken protocol) - if GDBSTUB_DEBUG_PROTOCOL then - stderr('GDB requested packet retransmission\n') - end - else - error('Received unexpected protocol character from GDB: '..c) + if not self.conn then return false end + -- set max mcycle for continue operations + self.max_mcycle = max_mcycle or math.maxinteger + -- when resuming from a yield, we have a pending GDB continue packet to reply + if self.yielded then + self.yielded = nil + self:_handle_continue() -- resume handling last continue packet end - end - return self.yielded + -- run while GDB is connected and the machine has not yielded + while self.conn and not self.yielded do + local c = assert(self.conn:receive(1)) + if c == "$" then -- incoming packet + local data = self:_recv() + if data then -- acknowledged packet + self:_handle_packet(data) + end + elseif c == "-" then -- GDB requested packet requested retransmission + -- GDB usually asks for retransmission for long running commands (or broken protocol) + if GDBSTUB_DEBUG_PROTOCOL then stderr("GDB requested packet retransmission\n") end + else + error("Received unexpected protocol character from GDB: " .. c) + end + end + return self.yielded end -- Returns true if GDB is connected. -function GDBStub:is_connected() - return self.conn ~= nil -end +function GDBStub:is_connected() return self.conn ~= nil end -- Closes the GDB connection. function GDBStub:close() - if not self.conn then return end - assert(self.conn:close()) - self.conn = nil + if not self.conn then return end + assert(self.conn:close()) + self.conn = nil end return GDBStub diff --git a/src/cartesi/proof.lua b/src/cartesi/proof.lua index c5e9c85b7..2e661824b 100644 --- a/src/cartesi/proof.lua +++ b/src/cartesi/proof.lua @@ -1,13 +1,13 @@ -local cartesi = require"cartesi" +local cartesi = require("cartesi") local _M = {} function _M.roll_hash_up_tree(proof, target_hash) local hash = target_hash - for log2_size = proof.log2_target_size, proof.log2_root_size-1 do + for log2_size = proof.log2_target_size, proof.log2_root_size - 1 do local bit = (proof.target_address & (1 << log2_size)) ~= 0 local first, second - local i = proof.log2_root_size-log2_size + local i = proof.log2_root_size - log2_size if bit then first, second = proof.sibling_hashes[i], hash else @@ -20,28 +20,24 @@ end function _M.slice_assert(root_hash, proof) assert(root_hash == proof.root_hash, "proof root_hash mismatch") - assert(_M.roll_hash_up_tree(proof, proof.target_hash) == root_hash, - "node not in tree") + assert(_M.roll_hash_up_tree(proof, proof.target_hash) == root_hash, "node not in tree") end function _M.word_slice_assert(root_hash, proof, word) assert(proof.log2_target_size == 3, "not a word proof") assert(root_hash == proof.root_hash, "proof root_hash mismatch") assert(cartesi.keccak(word) == proof.target_hash, "proof target_hash mismatch") - assert(_M.roll_hash_up_tree(proof, proof.target_hash) == root_hash, - "node not in tree") + assert(_M.roll_hash_up_tree(proof, proof.target_hash) == root_hash, "node not in tree") end function _M.splice_assert(root_hash, proof, new_target_hash, new_root_hash) _M.slice_assert(root_hash, proof) - assert(_M.roll_hash_up_tree(proof, new_target_hash) == new_root_hash, - "new root hash mismatch") + assert(_M.roll_hash_up_tree(proof, new_target_hash) == new_root_hash, "new root hash mismatch") end function _M.word_splice_assert(root_hash, proof, old_word, new_word, new_root_hash) _M.word_slice_assert(root_hash, proof, old_word) - assert(_M.roll_hash_up_tree(proof, cartesi.keccak(new_word)) == new_root_hash, - "new root hash mismatch") + assert(_M.roll_hash_up_tree(proof, cartesi.keccak(new_word)) == new_root_hash, "new root hash mismatch") end return _M diff --git a/src/cartesi/util.lua b/src/cartesi/util.lua index 641ef34e7..38a772eb1 100644 --- a/src/cartesi/util.lua +++ b/src/cartesi/util.lua @@ -16,16 +16,12 @@ local _M = {} -local function indentout(f, indent, fmt, ...) - f:write(string.rep(" ", indent), string.format(fmt, ...)) -end +local function indentout(f, indent, fmt, ...) f:write(string.rep(" ", indent), string.format(fmt, ...)) end _M.indentout = indentout local function hexstring(hash) - return (string.gsub(hash, ".", function(c) - return string.format("%02x", string.byte(c)) - end)) + return (string.gsub(hash, ".", function(c) return string.format("%02x", string.byte(c)) end)) end local hexhash = hexstring @@ -35,8 +31,11 @@ _M.hexhash = hexstring local function dump_json_sibling_hashes(sibling_hashes, out, indent) for i, h in ipairs(sibling_hashes) do indentout(out, indent, '"%s"', hexhash(h)) - if sibling_hashes[i+1] then out:write(',\n') - else out:write('\n') end + if sibling_hashes[i + 1] then + out:write(",\n") + else + out:write("\n") + end end end @@ -46,8 +45,8 @@ local function dump_json_proof(proof, out, indent) indentout(out, indent, '"log2_root_size": %u,\n', proof.log2_root_size) indentout(out, indent, '"target_hash": "%s",\n', hexhash(proof.target_hash)) indentout(out, indent, '"sibling_hashes": [\n') - dump_json_sibling_hashes(proof.sibling_hashes, out, indent+1) - indentout(out, indent, '],\n') + dump_json_sibling_hashes(proof.sibling_hashes, out, indent + 1) + indentout(out, indent, "],\n") indentout(out, indent, '"root_hash": "%s"\n', hexhash(proof.root_hash)) end @@ -57,75 +56,84 @@ local function dump_json_log_notes(notes, out, indent) local n = #notes for i, note in ipairs(notes) do indentout(out, indent, '"%s"', note) - if i < n then out:write(',\n') - else out:write("\n") end + if i < n then + out:write(",\n") + else + out:write("\n") + end end end local function dump_json_log_brackets(brackets, out, indent) local n = #brackets for i, bracket in ipairs(brackets) do - indentout(out, indent, '{\n') - indentout(out, indent+1, '"type": "%s",\n', bracket.type) - indentout(out, indent+1, '"where": %u,\n', bracket.where) - indentout(out, indent+1, '"text": "%s"\n', bracket.text) - indentout(out, indent, '}') - if i < n then out:write(',\n') - else out:write("\n") end + indentout(out, indent, "{\n") + indentout(out, indent + 1, '"type": "%s",\n', bracket.type) + indentout(out, indent + 1, '"where": %u,\n', bracket.where) + indentout(out, indent + 1, '"text": "%s"\n', bracket.text) + indentout(out, indent, "}") + if i < n then + out:write(",\n") + else + out:write("\n") + end end end local function dump_json_log_access(access, out, indent) - indentout(out, indent, '{\n') - indentout(out, indent+1, '"type": "%s",\n', access.type) - indentout(out, indent+1, '"address": %u,\n', access.address) - indentout(out, indent+1, '"read": "%s"', hexstring(access.read)) + indentout(out, indent, "{\n") + indentout(out, indent + 1, '"type": "%s",\n', access.type) + indentout(out, indent + 1, '"address": %u,\n', access.address) + indentout(out, indent + 1, '"read": "%s"', hexstring(access.read)) if access.type == "write" then out:write(",\n") - indentout(out, indent+1, '"written": "%s"', hexstring(access.written)) + indentout(out, indent + 1, '"written": "%s"', hexstring(access.written)) end if access.proof then out:write(",\n") - indentout(out, indent+1, '"proof": {\n') - dump_json_proof(access.proof, out, indent+2) - indentout(out, indent+1, '}\n') + indentout(out, indent + 1, '"proof": {\n') + dump_json_proof(access.proof, out, indent + 2) + indentout(out, indent + 1, "}\n") else out:write("\n") end - indentout(out, indent, '}') + indentout(out, indent, "}") end local function dump_json_log_accesses(accesses, out, indent) local n = #accesses for i, access in ipairs(accesses) do dump_json_log_access(access, out, indent) - if i < n then out:write(',\n') - else out:write('\n') end + if i < n then + out:write(",\n") + else + out:write("\n") + end end end -function _M.dump_json_log(log, init_mcycle, init_uarch_cycle, final_mcycle,final_uarch_cycle, out, indent) +function _M.dump_json_log(log, init_mcycle, init_uarch_cycle, final_mcycle, final_uarch_cycle, out, indent) indent = indent or 0 - indentout(out, indent, '{\n') - indentout(out, indent+1, '"init_mcycle": %u,\n', init_mcycle) - indentout(out, indent+1, '"init_uarch_cycle": %u,\n', init_uarch_cycle) - indentout(out, indent+1, '"final_mcycle": %u,\n', final_mcycle) - indentout(out, indent+1, '"final_uarch_cycle": %u,\n', final_uarch_cycle) - indentout(out, indent+1, '"accesses": [\n') - dump_json_log_accesses(log.accesses, out, indent+2) - indentout(out, indent+1, "]"); + indentout(out, indent, "{\n") + indentout(out, indent + 1, '"init_mcycle": %u,\n', init_mcycle) + indentout(out, indent + 1, '"init_uarch_cycle": %u,\n', init_uarch_cycle) + indentout(out, indent + 1, '"final_mcycle": %u,\n', final_mcycle) + indentout(out, indent + 1, '"final_uarch_cycle": %u,\n', final_uarch_cycle) + indentout(out, indent + 1, '"accesses": [\n') + dump_json_log_accesses(log.accesses, out, indent + 2) + indentout(out, indent + 1, "]") if log.log_type.annotations then out:write(",\n") - indentout(out, indent+1, '"notes": [\n') - dump_json_log_notes(log.notes, out, indent+2) - indentout(out, indent+1, '],\n') - indentout(out, indent+1, '"brackets": [\n') - dump_json_log_brackets(log.brackets, out, indent+2) - indentout(out, indent+1, ']\n') + indentout(out, indent + 1, '"notes": [\n') + dump_json_log_notes(log.notes, out, indent + 2) + indentout(out, indent + 1, "],\n") + indentout(out, indent + 1, '"brackets": [\n') + dump_json_log_brackets(log.brackets, out, indent + 2) + indentout(out, indent + 1, "]\n") else out:write("\n") end - indentout(out, indent, '}') + indentout(out, indent, "}") end function _M.parse_number(n) @@ -136,10 +144,15 @@ function _M.parse_number(n) end base = tonumber(base) if not base then return nil end - if rest == "Ki" then return base << 10 - elseif rest == "Mi" then return base << 20 - elseif rest == "Gi" then return base << 30 - elseif rest == "" then return base end + if rest == "Ki" then + return base << 10 + elseif rest == "Mi" then + return base << 20 + elseif rest == "Gi" then + return base << 30 + elseif rest == "" then + return base + end local shift = string.match(rest, "^%s*%<%<%s*(%d+)$") if shift then shift = tonumber(shift) @@ -177,18 +190,19 @@ function _M.parse_options(s, keys) return options end -local function hexhash8(hash) - return string.sub(hexhash(hash), 1, 8) -end +local function hexhash8(hash) return string.sub(hexhash(hash), 1, 8) end local function accessdatastring(data, log2_size) if log2_size == 3 then data = string.unpack(" %s\n", i, - notes[i] or "", ai.address, ai.address, read, written) + indentout( + out, + indent, + "%d: write %s@0x%x(%u): %s -> %s\n", + i, + notes[i] or "", + ai.address, + ai.address, + read, + written + ) end i = i + 1 end diff --git a/src/clua-cartesi.cpp b/src/clua-cartesi.cpp index a3c1e2224..a22f5c4d0 100644 --- a/src/clua-cartesi.cpp +++ b/src/clua-cartesi.cpp @@ -22,6 +22,7 @@ #include "clua.h" #include "machine-c-api.h" #include "riscv-constants.h" +#include "rtc.h" /// \file /// \brief Scripting interface for the Cartesi SDK. @@ -122,6 +123,9 @@ CM_API int luaopen_cartesi(lua_State *L) { clua_setintegerfield(L, CM_UARCH_BREAK_REASON_REACHED_TARGET_CYCLE, "UARCH_BREAK_REASON_REACHED_TARGET_CYCLE", -1); clua_setintegerfield(L, CM_UARCH_BREAK_REASON_HALTED, "UARCH_BREAK_REASON_HALTED", -1); + clua_setintegerfield(L, UINT64_MAX, "MAX_MCYCLE", -1); + clua_setintegerfield(L, RTC_FREQ_DIV, "RTC_FREQ_DIV", -1); + clua_setintegerfield(L, RTC_CLOCK_FREQ, "RTC_CLOCK_FREQ", -1); clua_setintegerfield(L, MVENDORID_INIT, "MVENDORID", -1); clua_setintegerfield(L, MARCHID_INIT, "MARCHID", -1); clua_setintegerfield(L, MIMPID_INIT, "MIMPID", -1); diff --git a/src/clua-i-virtual-machine.cpp b/src/clua-i-virtual-machine.cpp index 1a4e1f282..3768e6e6b 100644 --- a/src/clua-i-virtual-machine.cpp +++ b/src/clua-i-virtual-machine.cpp @@ -645,10 +645,24 @@ static const auto machine_obj_index = cartesi::clua_make_luaL_Reg_array({ {"reset_uarch_state", machine_obj_index_reset_uarch_state}, }); +/// \brief This is the machine __close metamethod implementation. +/// \param L Lua state. +static int machine_obj_close(lua_State *L) { + auto &m = clua_check>(L, 1); + TRY_EXECUTE(cm_destroy(m.get(), err_msg)); + clua_close>(L); + return 0; +} + int clua_i_virtual_machine_init(lua_State *L, int ctxidx) { if (!clua_typeexists>(L, ctxidx)) { clua_createtype>(L, "cartesi machine object", ctxidx); clua_setmethods>(L, machine_obj_index.data(), 0, ctxidx); + // Override __close to actually destroy the machine + static const auto machine_class_meta = cartesi::clua_make_luaL_Reg_array({ + {"__close", machine_obj_close}, + }); + clua_setmetamethods>(L, machine_class_meta.data(), 0, ctxidx); } return 1; } diff --git a/src/clua-machine-util.cpp b/src/clua-machine-util.cpp index 35df3c797..aca2d1a76 100644 --- a/src/clua-machine-util.cpp +++ b/src/clua-machine-util.cpp @@ -776,7 +776,7 @@ static void push_cm_rom_config(lua_State *L, const cm_rom_config *r) { if (r->bootargs != nullptr) { clua_setstringfield(L, r->bootargs, "bootargs", -1); } - if (r->image_filename) { + if (r->image_filename != nullptr) { clua_setstringfield(L, r->image_filename, "image_filename", -1); } } @@ -960,7 +960,7 @@ cm_memory_range_config *clua_check_cm_memory_range_config(lua_State *L, int tabi } m->shared = opt_boolean_field(L, tabidx, "shared"); m->start = check_uint_field(L, tabidx, "start"); - m->length = check_uint_field(L, tabidx, "length"); + m->length = opt_uint_field(L, tabidx, "length", UINT64_C(-1)); m->image_filename = opt_copy_string_field(L, tabidx, "image_filename"); return m; } diff --git a/src/clua.h b/src/clua.h index 1827dbba5..fc5c6493b 100644 --- a/src/clua.h +++ b/src/clua.h @@ -143,6 +143,18 @@ int clua_gc(lua_State *L) { return 0; } +/// \brief Close an object of a previously defined type +/// \tparam T Associated C++ type +/// \param L Lua state. +template +int clua_close(lua_State *L) { + T *ptr = static_cast(lua_touserdata(L, 1)); + ptr->~T(); + lua_pushnil(L); + lua_setmetatable(L, 1); + return 0; +} + /// \brief Prints an object of a previously defined type /// \tparam T Associated C++ type /// \param L Lua state. @@ -291,6 +303,7 @@ void clua_createtype(lua_State *L, const char *name, int ctxidx) { // create new type auto default_meta = clua_make_luaL_Reg_array({ {"__gc", &clua_gc}, + {"__close", &clua_close}, {"__tostring", &clua_tostring}, }); lua_pushstring(L, clua_rawname()); // T_rawname diff --git a/src/machine.cpp b/src/machine.cpp index e625f06c0..dbdec27a8 100644 --- a/src/machine.cpp +++ b/src/machine.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -342,10 +343,6 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) : write_ilrsc(m_c.processor.ilrsc); write_iflags(m_c.processor.iflags); - if (m_c.rom.image_filename.empty()) { - throw std::invalid_argument{"ROM image filename is undefined"}; - } - // Register RAM if (m_c.ram.image_filename.empty()) { register_pma_entry(make_callocd_memory_pma_entry("RAM"s, PMA_RAM_START, m_c.ram.length).set_flags(m_ram_flags)); @@ -355,14 +352,26 @@ machine::machine(const machine_config &c, const machine_runtime_config &r) : } // Register ROM - pma_entry &rom = - register_pma_entry(make_callocd_memory_pma_entry("ROM"s, PMA_ROM_START, PMA_ROM_LENGTH, m_c.rom.image_filename) - .set_flags(m_rom_flags)); + pma_entry &rom = register_pma_entry((m_c.rom.image_filename.empty() ? + make_callocd_memory_pma_entry("ROM"s, PMA_ROM_START, PMA_ROM_LENGTH) : + make_callocd_memory_pma_entry("ROM"s, PMA_ROM_START, PMA_ROM_LENGTH, m_c.rom.image_filename)) + .set_flags(m_rom_flags)); // Register all flash drives int i = 0; - for (const auto &f : m_c.flash_drive) { - register_pma_entry(make_flash_drive_pma_entry("flash drive "s + std::to_string(i++), f)); + for (auto &f : m_c.flash_drive) { + const std::string flash_description = "flash drive "s + std::to_string(i++); + // Auto detect flash drive image length + if (f.length == UINT64_C(-1)) { + std::error_code ec; + f.length = std::filesystem::file_size(f.image_filename, ec); + if (ec) { + throw std::system_error{ec.value(), ec.category(), + "unable to obtain length of image file '"s + f.image_filename + "' when initializing "s + + flash_description}; + } + } + register_pma_entry(make_flash_drive_pma_entry(flash_description, f)); } // Register rollup memory ranges @@ -1560,7 +1569,6 @@ void machine::dump_pmas(void) const { } std::array filename{}; (void) sprintf(filename.data(), "%016" PRIx64 "--%016" PRIx64 ".bin", pma->get_start(), pma->get_length()); - std::cerr << "writing to " << filename.data() << '\n'; auto fp = unique_fopen(filename.data(), "wb"); for (uint64_t page_start_in_range = 0; page_start_in_range < pma->get_length(); page_start_in_range += PMA_PAGE_SIZE) { diff --git a/src/rollup-memory-range.lua b/src/rollup-memory-range.lua index 867e2907f..5695242a3 100755 --- a/src/rollup-memory-range.lua +++ b/src/rollup-memory-range.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2021 Cartesi Pte. Ltd. -- @@ -16,16 +16,15 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local util = require"cartesi.util" -local json = require"dkjson" +local util = require("cartesi.util") +local json = require("dkjson") -local function stderr(fmt, ...) - io.stderr:write(string.format(fmt, ...)) -end +local function stderr(fmt, ...) io.stderr:write(string.format(fmt, ...)) end -- Print help and exit local function help() - stderr([=[ + stderr( + [=[ Usage: %s [action] [what] @@ -85,7 +84,9 @@ JSON object to stdout. [ , , ... I8", value)) end -local function errorf(...) - error(string.format(...)) -end +local function errorf(...) error(string.format(...)) end local function read_json() - local j, p, e = json.decode(io.read("*a")) + local j, _, e = json.decode(io.read("*a")) if not j then error(e) end return j end local function unhexhash(addr, name) - if not addr then - errorf("missing %s", name) - end - if string.sub(addr, 1, 2) ~= "0x" then - errorf("invalid %s %s (missing 0x prefix)", name, addr) - end - if #addr ~= 42 then - errorf("%s must contain 40 hex digits (%s has %g digits)", name, addr, #addr-2) - end + if not addr then errorf("missing %s", name) end + if string.sub(addr, 1, 2) ~= "0x" then errorf("invalid %s %s (missing 0x prefix)", name, addr) end + if #addr ~= 42 then errorf("%s must contain 40 hex digits (%s has %g digits)", name, addr, #addr - 2) end local bin, err = unhex(string.sub(addr, 3)) - if not bin then - errorf("invalid %s %s (%s)", name, addr, err) - end + if not bin then errorf("invalid %s %s (%s)", name, addr, err) end return bin end local function check_number(number, name) - if not number then - errorf("missing %s", name) - end + if not number then errorf("missing %s", name) end number = util.parse_number(number) - if not number then - errorf("invalid %s %s", name, tostring(number)) - end + if not number then errorf("invalid %s %s", name, tostring(number)) end return number end - -local function encode_input_metadata(arg) +local function encode_input_metadata() local j = read_json() j.msg_sender = unhexhash(j.msg_sender, "msg_sender") j.block_number = check_number(j.block_number, "block_number") @@ -222,15 +198,7 @@ local function encode_input_metadata(arg) write_be256(j.input_index) end -local function encode_string(arg) - local j = read_json() - local payload = assert(j.payload, "missing payload") - write_be256(32) -- offset - write_be256(#payload) - io.stdout:write(payload) -end - -local function encode_voucher(arg) +local function encode_voucher() local j = read_json() local payload = assert(j.payload, "missing payload") local destination = unhexhash(j.destination, "destination") @@ -241,51 +209,50 @@ local function encode_voucher(arg) io.stdout:write(payload) end -local function read_address() - return string.sub(assert(io.stdin:read(32)), 13) -end +local function read_address() return string.sub(assert(io.stdin:read(32)), 13) end local function read_hash() local s = io.stdin:read(32) if s and #s == 32 then return s end end -local function read_be256() - return string.unpack(">I8", string.sub(io.stdin:read(32), 25)) -end +local function read_be256() return string.unpack(">I8", string.sub(io.stdin:read(32), 25)) end -local function decode_input_metadata(arg) +local function decode_input_metadata() local msg_sender = read_address() local block_number = read_be256() local time_stamp = read_be256() local epoch_index = read_be256() local input_index = read_be256() - io.stdout:write(json.encode({ - msg_sender = hexhash(msg_sender), - block_number = block_number, - time_stamp = time_stamp, - epoch_index = epoch_index, - input_index = input_index, - }, { - indent = true, - keyorder = { - "msg_sender", - "block_number", - "time_stamp", - "epoch_index", - "input_index" - } - }), "\n") + io.stdout:write( + json.encode({ + msg_sender = hexhash(msg_sender), + block_number = block_number, + time_stamp = time_stamp, + epoch_index = epoch_index, + input_index = input_index, + }, { + indent = true, + keyorder = { + "msg_sender", + "block_number", + "time_stamp", + "epoch_index", + "input_index", + }, + }), + "\n" + ) end -local function decode_string(arg) +local function decode_string() assert(read_be256() == 32) -- skip offset local length = read_be256() - local payload = length == 0 and '' or assert(io.stdin:read(length)) + local payload = length == 0 and "" or assert(io.stdin:read(length)) io.stdout:write(json.encode({ payload = payload }, { indent = true }), "\n") end -local function encode_string(arg) +local function encode_string() local j = read_json() assert(j.payload, "missing payload") write_be256(32) @@ -293,30 +260,33 @@ local function encode_string(arg) io.stdout:write(j.payload) end -local function decode_voucher(arg) +local function decode_voucher() local destination = hexhash(read_address()) local offset = read_be256() assert(offset == 64, "expected offset 64, got " .. offset) -- skip offset local length = read_be256() - local payload = length == 0 and '' or assert(io.stdin:read(length)) - io.stdout:write(json.encode({ - destination = destination, - payload = payload - }, { - indent = true, - keyorder = { - "destination", - "payload" - } - }), "\n") + local payload = length == 0 and "" or assert(io.stdin:read(length)) + io.stdout:write( + json.encode({ + destination = destination, + payload = payload, + }, { + indent = true, + keyorder = { + "destination", + "payload", + }, + }), + "\n" + ) end -local function decode_hashes(arg) +local function decode_hashes() local t = {} while 1 do local hash = read_hash() if not hash then break end - t[#t+1] = hexhash(hash) + t[#t + 1] = hexhash(hash) end io.stdout:write(json.encode(t, { indent = true }), "\n") end diff --git a/src/spec/all-tests.lua b/src/spec/all-tests.lua new file mode 100644 index 000000000..179419824 --- /dev/null +++ b/src/spec/all-tests.lua @@ -0,0 +1,32 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local lester = require("spec.util.lester") + +-- Parse arguments from command line. +lester.parse_args() + +require("spec.keccak-tests") +require("spec.config-tests") +require("spec.htif-tests") +require("spec.machine-tests") +require("spec.clua-tests") +require("spec.step-tests") + +lester.report() -- Print overall statistic of the tests run. +lester.exit() -- Exit with success if all tests passed. diff --git a/src/spec/clua-tests.lua b/src/spec/clua-tests.lua new file mode 100644 index 000000000..fcc86c0b0 --- /dev/null +++ b/src/spec/clua-tests.lua @@ -0,0 +1,44 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local lester = require("spec.util.lester") +local fs = require("spec.util.fs") +local cartesi = require("cartesi") +local describe, it, expect = lester.describe, lester.it, lester.expect + +describe("machine clua", function() + local dummy_machine = cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + }) + + it("should fail when trying to pass non machine to a a machine API", function() + local read_mcycle = dummy_machine.read_mcycle + expect.fail(function() read_mcycle(1) end, "expected cartesi machine object") + expect.fail(function() read_mcycle(nil) end, "expected cartesi machine object") + expect.fail(function() read_mcycle() end, "expected cartesi machine object") + expect.fail(function() read_mcycle({}) end, "expected cartesi machine object") + expect.fail(function() read_mcycle(setmetatable({}, {})) end, "expected cartesi machine object") + end) + + it("should be able to convert a machine to a string", function() + local s = tostring(dummy_machine) + expect.truthy(s) + expect.equal(s:match("[a-z ]+"), "cartesi machine object") + end) +end) diff --git a/src/spec/config-tests.lua b/src/spec/config-tests.lua new file mode 100644 index 000000000..58dbe46a5 --- /dev/null +++ b/src/spec/config-tests.lua @@ -0,0 +1,1120 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local lester = require("spec.util.lester") +local fs = require("spec.util.fs") +local cartesi = require("cartesi") +local util = require("cartesi.util") +local describe, it, expect = lester.describe, lester.it, lester.expect + +local default_initial_config = cartesi.machine.get_default_config() + +local expected_initial_config = { + processor = { + -- these are non zero and depends in our implementation + marchid = default_initial_config.processor.marchid, + mimpid = default_initial_config.processor.mimpid, + mvendorid = default_initial_config.processor.mvendorid, + misa = default_initial_config.processor.misa, + mstatus = default_initial_config.processor.mstatus, + pc = default_initial_config.processor.pc, + iflags = default_initial_config.processor.iflags, + ilrsc = default_initial_config.processor.ilrsc, + -- these we know in advance + fcsr = 0, + icycleinstret = 0, + mcause = 0, + mcounteren = 0, + mcycle = 0, + medeleg = 0, + menvcfg = 0, + mepc = 0, + mideleg = 0, + mie = 0, + mip = 0, + mscratch = 0, + mtval = 0, + mtvec = 0, + satp = 0, + scause = 0, + scounteren = 0, + senvcfg = 0, + sepc = 0, + sscratch = 0, + stval = 0, + stvec = 0, + x = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + f = { [0] = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + }, + ram = { + image_filename = "", + length = 0x4000, + }, + rom = { + bootargs = "", + image_filename = fs.rom_image, + }, + tlb = { + image_filename = "", + }, + flash_drive = {}, + htif = { + console_getchar = false, + yield_automatic = false, + yield_manual = false, + fromhost = 0, + tohost = 0, + }, + clint = { + mtimecmp = 0, + }, + uarch = { + processor = { + cycle = 0, + pc = default_initial_config.uarch.processor.pc, + x = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + }, + ram = { + image_filename = "", + length = 0, + }, + }, +} + +local test_config = { + processor = { + -- these are hardwired constants, and cannot change + marchid = default_initial_config.processor.marchid, + mimpid = default_initial_config.processor.mimpid, + mvendorid = default_initial_config.processor.mvendorid, + -- these can be changed, and are set to random values + pc = 0x070c1efa257e32e4, + misa = 0xff13504ee4da72f1, + fcsr = 0x8b337085bc73d6f6, + icycleinstret = 0xf4310770998bfaab, + ilrsc = 0x5bf71f0fc1c516e1, + mcause = 0x73b2dcca2277c070, + mcounteren = 0x2aeb5bbda1f4be71, + mcycle = 0x072a8a6e298b61cb, + medeleg = 0x3d00a03901459100, + menvcfg = 0x4cf38ec0407ba557, + mepc = 0x23aab25abacae88d, + mideleg = 0x2830ed05187f8ab9, + mie = 0x4b615ac9c32e2a91, + mip = 0x4abb22a9f342d65c, + mscratch = 0xa0d39fc9763cdd91, + mstatus = 0xd02e272900ea57d5, + mtval = 0x41cea506fd53c830, + mtvec = 0xa395b0c3b234bfbc, + satp = 0x2fd3e21cd171c484, + scause = 0xc41a4593c61098ca, + scounteren = 0x9fdf00eae96a888d, + senvcfg = 0xe8c06242796cffa3, + sepc = 0x0e1151b658feb88a, + sscratch = 0x59951bc8a8fb4921, + stval = 0xb1c067c2c1709a51, + stvec = 0xa9ca605ecb0807b6, + iflags = 3, + x = { + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + }, + f = { + [0] = 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + }, + }, + ram = { + image_filename = "", + length = fs.get_file_length(fs.linux_image), + }, + rom = { + image_filename = fs.rom_image, + bootargs = "test", + }, + flash_drive = { + { + image_filename = fs.rootfs_image, + length = fs.get_file_length(fs.rootfs_image), + start = 0x80000000000000, + shared = false, + }, + { + image_filename = "", + length = 0x4000, + start = 0x90000000000000, + shared = false, + }, + }, + uarch = { + processor = { + cycle = 7, + pc = 0x2000, + x = { + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + }, + }, + ram = { image_filename = fs.uarch_ram_image, length = 0x20000 }, + }, + htif = { + yield_automatic = true, + yield_manual = true, + console_getchar = false, + fromhost = 0x5555555555555555, + tohost = 0xaaaaaaaaaaaaaaaa, + }, + rollup = { + rx_buffer = { image_filename = "", start = 0x60000000, length = 0x1000, shared = false }, + tx_buffer = { image_filename = "", start = 0x60002000, length = 0x1000, shared = false }, + input_metadata = { image_filename = "", start = 0x60004000, length = 0x1000, shared = false }, + voucher_hashes = { image_filename = "", start = 0x60006000, length = 0x1000, shared = false }, + notice_hashes = { image_filename = "", start = 0x60008000, length = 0x1000, shared = false }, + }, + tlb = { image_filename = "" }, + clint = { mtimecmp = 8192 }, +} + +describe("machine config", function() + it("should set initial configs correctly", function() + do + local machine = cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + }, { + concurrency = { update_merkle_tree = 1 }, + }) + expect.equal(machine:get_initial_config(), expected_initial_config) + machine:destroy() + end + collectgarbage() + end) + + it("should set initial configs correctly for -1 values", function() + local machine = cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { + marchid = -1, + mvendorid = -1, + mimpid = -1, + }, + }, { + concurrency = { update_merkle_tree = 1 }, + }) + expect.equal(machine:get_initial_config(), expected_initial_config) + end) + + it("should set missing config fields correctly", function() + local config = { + processor = { + x = {}, + f = {}, + }, + ram = { + length = 0x4000, + }, + rom = { + image_filename = fs.rom_image, + }, + flash_drive = { { + length = 0x4000, + start = 0x80000000000000, + } }, + uarch = { + processor = { + x = {}, + }, + rom = { length = 0 }, + ram = { length = 0 }, + }, + rollup = { + rx_buffer = { start = 0x60000000, length = 0x2000 }, + tx_buffer = { start = 0x60002000, length = 0x2000 }, + input_metadata = { start = 0x60004000, length = 0x2000 }, + voucher_hashes = { start = 0x60006000, length = 0x2000 }, + notice_hashes = { start = 0x60008000, length = 0x2000 }, + }, + tlb = {}, + clint = {}, + htif = {}, + } + local expected_machine_config = { + processor = expected_initial_config.processor, + ram = expected_initial_config.ram, + rom = expected_initial_config.rom, + flash_drive = { + { + length = 0x4000, + start = 0x80000000000000, + image_filename = "", + shared = false, + }, + }, + uarch = expected_initial_config.uarch, + rollup = { + rx_buffer = { start = 0x60000000, length = 0x2000, image_filename = "", shared = false }, + tx_buffer = { start = 0x60002000, length = 0x2000, image_filename = "", shared = false }, + input_metadata = { start = 0x60004000, length = 0x2000, image_filename = "", shared = false }, + voucher_hashes = { start = 0x60006000, length = 0x2000, image_filename = "", shared = false }, + notice_hashes = { start = 0x60008000, length = 0x2000, image_filename = "", shared = false }, + }, + tlb = expected_initial_config.tlb, + clint = expected_initial_config.clint, + htif = expected_initial_config.htif, + } + local machine = cartesi.machine(config, {}) + expect.equal(machine:get_initial_config(), expected_machine_config) + end) + + it("should match with initial config", function() + local machine = cartesi.machine(test_config) + expect.equal(machine:get_initial_config(), test_config) + end) + + it("should match halt flags, yield flags and config", function() + local machine = cartesi.machine({ + ram = { length = 0x100000 }, + rom = { image_filename = fs.rom_image }, + }) + -- Get machine default config and test for known fields + local initial_config = machine:get_initial_config() + expect.equal(initial_config.processor.marchid, default_initial_config.processor.marchid) + expect.equal(initial_config.processor.pc, default_initial_config.processor.pc) + expect.equal(initial_config.ram.length, 0x100000) + expect.not_equal(initial_config.rom.image_filename, "") + -- Check machine is not halted + expect.falsy(machine:read_iflags_H()) + -- Check machine is not yielded + expect.falsy(machine:read_iflags_Y() or machine:read_iflags_X()) + end) + + it("should fail when attempting to create machine with invalid configs", function() + -- rom + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = "some/invalid/image.bin" }, + }) + end, + "error opening image file" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.linux_image }, + }) + end, + "is too large for range" + ) + + -- ram + expect.fail( + function() + cartesi.machine({ + ram = { image_filename = "some/invalid/image.bin", length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + }) + end, + "error opening image file" + ) + expect.fail( + function() + cartesi.machine({ + ram = { image_filename = fs.linux_image, length = 0 }, + rom = { image_filename = fs.rom_image }, + }) + end, + "length cannot be zero" + ) + expect.fail( + function() + cartesi.machine({ + ram = { image_filename = fs.linux_image, length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + }) + end, + "too large for range" + ) + expect.fail( + function() + cartesi.machine({ + ram = {}, + rom = { image_filename = fs.rom_image }, + }) + end, + "invalid length" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0 }, + rom = { image_filename = fs.rom_image }, + }) + end, + "length cannot be zero" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 4095 }, + rom = { image_filename = fs.rom_image }, + }) + end, + "must be multiple of page size" + ) + expect.fail( + function() + cartesi.machine({ + ram = { image_filename = true, length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + }) + end, + "invalid image_filename" + ) + expect.fail( + function() + cartesi.machine({ + ram = 0, + rom = { image_filename = fs.rom_image }, + }) + end, + "missing ram" + ) + + -- processor + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = 0, + }) + end, + "missing processor" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { pc = true }, + }) + end, + "invalid pc" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { x = { true } }, + }) + end, + "invalid entry" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { marchid = 0 }, + }) + end, + "marchid mismatch" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { mimpid = 0 }, + }) + end, + "mimpid mismatch" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { mvendorid = 0 }, + }) + end, + "mvendorid mismatch" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { x = 0 }, + }) + end, + "invalid processor.x" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + processor = { f = 0 }, + }) + end, + "invalid processor.f" + ) + + -- uarch.processor + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + uarch = { + processor = { x = 0 }, + ram = { length = 0x4000 }, + rom = { length = 0x4000 }, + }, + }) + end, + "invalid uarch.processor.x" + ) + + -- flash drive + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { {}, {}, {}, {}, {}, {}, {}, {}, {} }, + }) + end, + "too many flash drives" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { false }, + }) + end, + "memory range not a table" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { {} }, + }) + end, + "invalid start" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { { start = 0x80000000000000, length = 0 } }, + }) + end, + "length cannot be zero" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { { start = 0x100000000000000, length = 0x4000 } }, + }) + end, + "must use at most 56 bits to be addressable" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { { start = 0, length = 0x4000 } }, + }) + end, + "overlaps with range of existing" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { { start = 0, length = 4095 } }, + }) + end, + "must be multiple of page size" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { + { + image_filename = "some/invalid/image.bin", + start = 0x80000000000000, + length = 0x4000, + }, + }, + }) + end, + "could not open image file" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { + { + image_filename = "some/invalid/image.bin", + start = 0x80000000000000, + }, + }, + }) + end, + "unable to obtain length of image file" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { + { + image_filename = fs.rootfs_image, + start = 0x80000000000000, + length = 0, + }, + }, + }) + end, + "length cannot be zero" + ) + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + flash_drive = { + { + image_filename = fs.rootfs_image, + start = 0x80000000000000, + length = 0x4000, + }, + }, + }) + end, + "does not match range length" + ) + + -- rollup + expect.fail( + function() + cartesi.machine({ + ram = { length = 0x4000 }, + rom = { image_filename = fs.rom_image }, + rollup = { + rx_buffer = { start = 0x60000000, length = 0x2000 }, + tx_buffer = { start = 0x60002000, length = 0x2000 }, + input_metadata = { start = 0x60004000, length = 0x2000 }, + voucher_hashes = { start = 0x60006000, length = 0x2000 }, + notice_hashes = { start = 0x60008000, length = 0 }, + }, + }) + end, + "incomplete rollup configuration" + ) + + -- stored + expect.fail(function() cartesi.machine("some/invalid/machine") end, "unable to open") + end) +end) + +describe("machine state", function() + local machine = cartesi.machine(test_config) + local P = 0xf6b75ab2bc471c7 -- random prime used to test register write + + it("should read CSRs", function() + -- check read_... + expect.equal(machine:read_marchid(), test_config.processor.marchid) + expect.equal(machine:read_mimpid(), test_config.processor.mimpid) + expect.equal(machine:read_mvendorid(), test_config.processor.mvendorid) + expect.equal(machine:read_pc(), test_config.processor.pc) + expect.equal(machine:read_misa(), test_config.processor.misa) + expect.equal(machine:read_fcsr(), test_config.processor.fcsr) + expect.equal(machine:read_icycleinstret(), test_config.processor.icycleinstret) + expect.equal(machine:read_ilrsc(), test_config.processor.ilrsc) + expect.equal(machine:read_mcause(), test_config.processor.mcause) + expect.equal(machine:read_mcounteren(), test_config.processor.mcounteren) + expect.equal(machine:read_mcycle(), test_config.processor.mcycle) + expect.equal(machine:read_medeleg(), test_config.processor.medeleg) + expect.equal(machine:read_menvcfg(), test_config.processor.menvcfg) + expect.equal(machine:read_mepc(), test_config.processor.mepc) + expect.equal(machine:read_mideleg(), test_config.processor.mideleg) + expect.equal(machine:read_mie(), test_config.processor.mie) + expect.equal(machine:read_mip(), test_config.processor.mip) + expect.equal(machine:read_mscratch(), test_config.processor.mscratch) + expect.equal(machine:read_mstatus(), test_config.processor.mstatus) + expect.equal(machine:read_mtval(), test_config.processor.mtval) + expect.equal(machine:read_mtvec(), test_config.processor.mtvec) + expect.equal(machine:read_satp(), test_config.processor.satp) + expect.equal(machine:read_scause(), test_config.processor.scause) + expect.equal(machine:read_scounteren(), test_config.processor.scounteren) + expect.equal(machine:read_senvcfg(), test_config.processor.senvcfg) + expect.equal(machine:read_sepc(), test_config.processor.sepc) + expect.equal(machine:read_sscratch(), test_config.processor.sscratch) + expect.equal(machine:read_stval(), test_config.processor.stval) + expect.equal(machine:read_stvec(), test_config.processor.stvec) + expect.equal(machine:read_iflags(), test_config.processor.iflags) + expect.equal(machine:read_uarch_cycle(), test_config.uarch.processor.cycle) + expect.equal(machine:read_uarch_pc(), test_config.uarch.processor.pc) + expect.equal(machine:read_uarch_ram_length(), test_config.uarch.ram.length) + + -- check read_csr + expect.equal(machine:read_csr("marchid"), test_config.processor.marchid) + expect.equal(machine:read_csr("mimpid"), test_config.processor.mimpid) + expect.equal(machine:read_csr("mvendorid"), test_config.processor.mvendorid) + expect.equal(machine:read_csr("pc"), test_config.processor.pc) + expect.equal(machine:read_csr("misa"), test_config.processor.misa) + expect.equal(machine:read_csr("fcsr"), test_config.processor.fcsr) + expect.equal(machine:read_csr("icycleinstret"), test_config.processor.icycleinstret) + expect.equal(machine:read_csr("ilrsc"), test_config.processor.ilrsc) + expect.equal(machine:read_csr("mcause"), test_config.processor.mcause) + expect.equal(machine:read_csr("mcounteren"), test_config.processor.mcounteren) + expect.equal(machine:read_csr("mcycle"), test_config.processor.mcycle) + expect.equal(machine:read_csr("medeleg"), test_config.processor.medeleg) + expect.equal(machine:read_csr("menvcfg"), test_config.processor.menvcfg) + expect.equal(machine:read_csr("mepc"), test_config.processor.mepc) + expect.equal(machine:read_csr("mideleg"), test_config.processor.mideleg) + expect.equal(machine:read_csr("mie"), test_config.processor.mie) + expect.equal(machine:read_csr("mip"), test_config.processor.mip) + expect.equal(machine:read_csr("mscratch"), test_config.processor.mscratch) + expect.equal(machine:read_csr("mstatus"), test_config.processor.mstatus) + expect.equal(machine:read_csr("mtval"), test_config.processor.mtval) + expect.equal(machine:read_csr("mtvec"), test_config.processor.mtvec) + expect.equal(machine:read_csr("satp"), test_config.processor.satp) + expect.equal(machine:read_csr("scause"), test_config.processor.scause) + expect.equal(machine:read_csr("scounteren"), test_config.processor.scounteren) + expect.equal(machine:read_csr("senvcfg"), test_config.processor.senvcfg) + expect.equal(machine:read_csr("sepc"), test_config.processor.sepc) + expect.equal(machine:read_csr("sscratch"), test_config.processor.sscratch) + expect.equal(machine:read_csr("stval"), test_config.processor.stval) + expect.equal(machine:read_csr("stvec"), test_config.processor.stvec) + expect.equal(machine:read_csr("iflags"), test_config.processor.iflags) + expect.equal(machine:read_csr("uarch_cycle"), test_config.uarch.processor.cycle) + expect.equal(machine:read_csr("uarch_pc"), test_config.uarch.processor.pc) + expect.equal(machine:read_csr("uarch_ram_length"), test_config.uarch.ram.length) + + -- check if CSR addresses are valid + local get_csr_addr = cartesi.machine.get_csr_address + expect.equal(machine:read_word(get_csr_addr("marchid")), test_config.processor.marchid) + expect.equal(machine:read_word(get_csr_addr("mimpid")), test_config.processor.mimpid) + expect.equal(machine:read_word(get_csr_addr("mvendorid")), test_config.processor.mvendorid) + expect.equal(machine:read_word(get_csr_addr("pc")), test_config.processor.pc) + expect.equal(machine:read_word(get_csr_addr("misa")), test_config.processor.misa) + expect.equal(machine:read_word(get_csr_addr("fcsr")), test_config.processor.fcsr) + expect.equal(machine:read_word(get_csr_addr("icycleinstret")), test_config.processor.icycleinstret) + expect.equal(machine:read_word(get_csr_addr("ilrsc")), test_config.processor.ilrsc) + expect.equal(machine:read_word(get_csr_addr("mcause")), test_config.processor.mcause) + expect.equal(machine:read_word(get_csr_addr("mcounteren")), test_config.processor.mcounteren) + expect.equal(machine:read_word(get_csr_addr("mcycle")), test_config.processor.mcycle) + expect.equal(machine:read_word(get_csr_addr("medeleg")), test_config.processor.medeleg) + expect.equal(machine:read_word(get_csr_addr("menvcfg")), test_config.processor.menvcfg) + expect.equal(machine:read_word(get_csr_addr("mepc")), test_config.processor.mepc) + expect.equal(machine:read_word(get_csr_addr("mideleg")), test_config.processor.mideleg) + expect.equal(machine:read_word(get_csr_addr("mie")), test_config.processor.mie) + expect.equal(machine:read_word(get_csr_addr("mip")), test_config.processor.mip) + expect.equal(machine:read_word(get_csr_addr("mscratch")), test_config.processor.mscratch) + expect.equal(machine:read_word(get_csr_addr("mstatus")), test_config.processor.mstatus) + expect.equal(machine:read_word(get_csr_addr("mtval")), test_config.processor.mtval) + expect.equal(machine:read_word(get_csr_addr("mtvec")), test_config.processor.mtvec) + expect.equal(machine:read_word(get_csr_addr("satp")), test_config.processor.satp) + expect.equal(machine:read_word(get_csr_addr("scause")), test_config.processor.scause) + expect.equal(machine:read_word(get_csr_addr("scounteren")), test_config.processor.scounteren) + expect.equal(machine:read_word(get_csr_addr("senvcfg")), test_config.processor.senvcfg) + expect.equal(machine:read_word(get_csr_addr("sepc")), test_config.processor.sepc) + expect.equal(machine:read_word(get_csr_addr("sscratch")), test_config.processor.sscratch) + expect.equal(machine:read_word(get_csr_addr("stval")), test_config.processor.stval) + expect.equal(machine:read_word(get_csr_addr("stvec")), test_config.processor.stvec) + expect.equal(machine:read_word(get_csr_addr("iflags")), test_config.processor.iflags) + expect.equal(machine:read_word(get_csr_addr("uarch_cycle")), test_config.uarch.processor.cycle) + expect.equal(machine:read_word(get_csr_addr("uarch_pc")), test_config.uarch.processor.pc) + expect.equal(machine:read_word(get_csr_addr("uarch_ram_length")), test_config.uarch.ram.length) + end) + + it("should write CSRs", function() + local pc = P & ~3 -- make sure it is 4-byte aligned + local a = P + + -- check write_... + expect.equal(machine:write_pc(pc) or machine:read_pc(), pc) + expect.equal(machine:write_misa(a) or machine:read_misa(), a) + expect.equal(machine:write_fcsr(a) or machine:read_fcsr(), a) + expect.equal(machine:write_icycleinstret(a) or machine:read_icycleinstret(), a) + expect.equal(machine:write_ilrsc(a) or machine:read_ilrsc(), a) + expect.equal(machine:write_mcause(a) or machine:read_mcause(), a) + expect.equal(machine:write_mcounteren(a) or machine:read_mcounteren(), a) + expect.equal(machine:write_mcycle(a) or machine:read_mcycle(), a) + expect.equal(machine:write_medeleg(a) or machine:read_medeleg(), a) + expect.equal(machine:write_menvcfg(a) or machine:read_menvcfg(), a) + expect.equal(machine:write_mepc(a) or machine:read_mepc(), a) + expect.equal(machine:write_mideleg(a) or machine:read_mideleg(), a) + expect.equal(machine:write_mie(a) or machine:read_mie(), a) + expect.equal(machine:write_mip(a) or machine:read_mip(), a) + expect.equal(machine:write_mscratch(a) or machine:read_mscratch(), a) + expect.equal(machine:write_mstatus(a) or machine:read_mstatus(), a) + expect.equal(machine:write_mtval(a) or machine:read_mtval(), a) + expect.equal(machine:write_mtvec(a) or machine:read_mtvec(), a) + expect.equal(machine:write_satp(a) or machine:read_satp(), a) + expect.equal(machine:write_scause(a) or machine:read_scause(), a) + expect.equal(machine:write_scounteren(a) or machine:read_scounteren(), a) + expect.equal(machine:write_senvcfg(a) or machine:read_senvcfg(), a) + expect.equal(machine:write_sepc(a) or machine:read_sepc(), a) + expect.equal(machine:write_sscratch(a) or machine:read_sscratch(), a) + expect.equal(machine:write_stval(a) or machine:read_stval(), a) + expect.equal(machine:write_stvec(a) or machine:read_stvec(), a) + expect.equal(machine:write_uarch_cycle(a) or machine:read_uarch_cycle(), a) + expect.equal(machine:write_uarch_pc(pc) or machine:read_uarch_pc(), pc) + expect.equal(machine:write_iflags(0) or machine:read_iflags(), 0) + + -- update values for next writes + pc = pc + 4 + a = ~a + + -- check write_csr + expect.equal(machine:write_csr("pc", pc) or machine:read_pc(), pc) + expect.equal(machine:write_csr("misa", a) or machine:read_misa(), a) + expect.equal(machine:write_csr("fcsr", a) or machine:read_fcsr(), a) + expect.equal(machine:write_csr("icycleinstret", a) or machine:read_icycleinstret(), a) + expect.equal(machine:write_csr("ilrsc", a) or machine:read_ilrsc(), a) + expect.equal(machine:write_csr("mcause", a) or machine:read_mcause(), a) + expect.equal(machine:write_csr("mcounteren", a) or machine:read_mcounteren(), a) + expect.equal(machine:write_csr("mcycle", a) or machine:read_mcycle(), a) + expect.equal(machine:write_csr("medeleg", a) or machine:read_medeleg(), a) + expect.equal(machine:write_csr("menvcfg", a) or machine:read_menvcfg(), a) + expect.equal(machine:write_csr("mepc", a) or machine:read_mepc(), a) + expect.equal(machine:write_csr("mideleg", a) or machine:read_mideleg(), a) + expect.equal(machine:write_csr("mie", a) or machine:read_mie(), a) + expect.equal(machine:write_csr("mip", a) or machine:read_mip(), a) + expect.equal(machine:write_csr("mscratch", a) or machine:read_mscratch(), a) + expect.equal(machine:write_csr("mstatus", a) or machine:read_mstatus(), a) + expect.equal(machine:write_csr("mtval", a) or machine:read_mtval(), a) + expect.equal(machine:write_csr("mtvec", a) or machine:read_mtvec(), a) + expect.equal(machine:write_csr("satp", a) or machine:read_satp(), a) + expect.equal(machine:write_csr("scause", a) or machine:read_scause(), a) + expect.equal(machine:write_csr("scounteren", a) or machine:read_scounteren(), a) + expect.equal(machine:write_csr("senvcfg", a) or machine:read_senvcfg(), a) + expect.equal(machine:write_csr("sepc", a) or machine:read_sepc(), a) + expect.equal(machine:write_csr("sscratch", a) or machine:read_sscratch(), a) + expect.equal(machine:write_csr("stval", a) or machine:read_stval(), a) + expect.equal(machine:write_csr("stvec", a) or machine:read_stvec(), a) + expect.equal(machine:write_csr("uarch_cycle", a) or machine:read_uarch_cycle(), a) + expect.equal(machine:write_csr("uarch_pc", pc) or machine:read_uarch_pc(), pc) + expect.equal(machine:write_csr("iflags", 0) or machine:read_iflags(), 0) + end) + + it("should read/set/reset iflags", function() + expect.equal(machine:read_iflags_H(), false) + expect.equal(machine:read_iflags_X(), false) + expect.equal(machine:read_iflags_Y(), false) + expect.equal(machine:set_iflags_H() or machine:read_iflags_H(), true) + expect.equal(machine:set_iflags_X() or machine:read_iflags_X(), true) + expect.equal(machine:set_iflags_Y() or machine:read_iflags_Y(), true) + expect.equal(machine:reset_iflags_X() or machine:read_iflags_X(), false) + expect.equal(machine:reset_iflags_Y() or machine:read_iflags_Y(), false) + end) + + it("should read/write x registers", function() + expect.equal(machine:read_x(0), 0) + for i, defval in ipairs(test_config.processor.x) do + local addr = cartesi.machine.get_x_address(i) + local val = i * P + expect.equal(machine:read_x(i), defval) + expect.equal(machine:read_word(addr), defval) + expect.equal(machine:write_x(i, val) or machine:read_x(i), val) + expect.equal(machine:read_word(addr), val) + end + end) + + it("should read/write f registers", function() + for i = 0, 31 do + local addr = cartesi.machine.get_f_address(i) + local defval = test_config.processor.f[i] + local val = (i + 1) * P + expect.equal(machine:read_f(i), defval) + expect.equal(machine:read_word(addr), defval) + expect.equal(machine:write_f(i, val) or machine:read_f(i), val) + expect.equal(machine:read_word(addr), val) + end + end) + + it("should read/write uarch x registers", function() + expect.equal(machine:read_uarch_x(0), 0) + for i, defval in ipairs(test_config.uarch.processor.x) do + local val = i * P + expect.equal(machine:read_uarch_x(i), defval) + expect.equal(machine:write_uarch_x(i, val) or machine:read_uarch_x(i), val) + end + end) + + it("should read/write htif device", function() + expect.equal(machine:read_htif_fromhost(), test_config.htif.fromhost) + expect.equal(machine:read_csr("htif_fromhost"), test_config.htif.fromhost) + expect.equal(machine:write_htif_fromhost(P) or machine:read_htif_fromhost(), P) + expect.equal(machine:write_htif_fromhost_data(0) or machine:read_htif_fromhost(), P & ~0xffffffffffff) + expect.equal(machine:write_csr("htif_fromhost", ~P) or machine:read_htif_fromhost(), ~P) + + expect.equal(machine:read_htif_tohost(), test_config.htif.tohost) + expect.equal(machine:read_htif_tohost_data(), test_config.htif.tohost & 0xffffffffffff) + expect.equal(machine:read_htif_tohost_cmd(), (test_config.htif.tohost >> 48) & 0xff) + expect.equal(machine:read_htif_tohost_dev(), (test_config.htif.tohost >> 56) & 0xff) + expect.equal(machine:read_csr("htif_tohost"), test_config.htif.tohost) + expect.equal(machine:write_htif_tohost(P) or machine:read_htif_tohost(), P) + expect.equal(machine:write_csr("htif_tohost", ~P) or machine:read_htif_tohost(), ~P) + + expect.equal(machine:read_htif_ihalt(), 0x1) + expect.equal(machine:read_csr("htif_ihalt"), 0x1) + -- expect.equal(machine:write_htif_ihalt(P) or machine:read_htif_ihalt(), P) -- missing method? + expect.equal(machine:write_csr("htif_ihalt", ~P) or machine:read_htif_ihalt(), ~P) + + expect.equal(machine:read_htif_iyield(), 0x3) + expect.equal(machine:read_csr("htif_iyield"), 0x3) + -- expect.equal(machine:write_htif_iyield(P) or machine:read_htif_iyield(), P) -- missing method? + expect.equal(machine:write_csr("htif_iyield", ~P) or machine:read_htif_iyield(), ~P) + + expect.equal(machine:read_htif_iconsole(), 0x2) + expect.equal(machine:read_csr("htif_iconsole"), 0x2) + -- expect.equal(machine:write_htif_iconsole(P) or machine:read_htif_iconsole(), P) -- missing method? + expect.equal(machine:write_csr("htif_iconsole", ~P) or machine:read_htif_iconsole(), ~P) + end) + + it("should read/write clint device", function() + expect.equal(machine:read_clint_mtimecmp(), test_config.clint.mtimecmp) + expect.equal(machine:read_csr("clint_mtimecmp"), test_config.clint.mtimecmp) + expect.equal(machine:write_clint_mtimecmp(P) or machine:read_clint_mtimecmp(), P) + expect.equal(machine:write_csr("clint_mtimecmp", ~P) or machine:read_clint_mtimecmp(), ~P) + end) + + it("should fail when attempting to perform invalid writes", function() + expect.fail(function() machine:write_csr("unknown_csr", 0) end, "unknown csr") + expect.fail(function() machine:write_csr("marchid", 0) end, "is read-only") + expect.fail(function() machine:write_csr("mimpid", 0) end, "is read-only") + expect.fail(function() machine:write_csr("mvendorid", 0) end, "is read-only") + expect.fail(function() machine:write_csr("uarch_ram_length", 0) end, "is read-only") + expect.fail(function() machine:write_pc() end, "got no value") + expect.fail(function() machine:write_x(1) end, "got no value") + expect.fail(function() machine:write_x(1, nil) end, "got nil") + expect.fail(function() machine:write_x(nil, 1) end, "got nil") + expect.fail(function() machine:write_x(1, false) end, "got boolean") + expect.fail(function() machine:write_x(0, 0) end, "register index out of range") + expect.fail(function() machine:write_x(32, 0) end, "register index out of range") + expect.fail(function() machine:write_f(-1, 0) end, "register index out of range") + expect.fail(function() machine:write_f(32, 0) end, "register index out of range") + expect.fail(function() machine:write_uarch_x(-1, 0) end, "register index out of range") + expect.fail(function() machine:write_uarch_x(0, 0) end, "register index out of range") + end) + + it("should fail when attempting to perform invalid reads", function() + expect.fail(function() machine:read_csr("unknown_csr") end, "unknown csr") + expect.fail(function() machine:read_x(-1, 0) end, "register index out of range") + expect.fail(function() machine:read_x(32, 0) end, "register index out of range") + expect.fail(function() machine:read_f(-1, 0) end, "register index out of range") + expect.fail(function() machine:read_f(32, 0) end, "register index out of range") + expect.fail(function() machine:read_uarch_x(-1, 0) end, "register index out of range") + expect.fail(function() machine:read_uarch_x(32, 0) end, "register index out of range") + end) + + it("it should fail when attempting to get address for invalid registers", function() + expect.fail(function() cartesi.machine.get_csr_address() end, "got no value") + expect.fail(function() cartesi.machine.get_csr_address(false) end, "got boolean") + expect.fail(function() cartesi.machine.get_csr_address("") end, "unknown csr") + expect.fail(function() cartesi.machine.get_csr_address("unknown_csr") end, "unknown csr") + expect.fail(function() cartesi.machine.get_x_address(-1) end, "register index out of range") + expect.fail(function() cartesi.machine.get_x_address(32) end, "register index out of range") + expect.fail(function() cartesi.machine.get_f_address(-1) end, "register index out of range") + expect.fail(function() cartesi.machine.get_f_address(32) end, "register index out of range") + end) + + machine:destroy() +end) + +describe("machine rollback", function() + it("should fail when attempting to perform a snapshot or rollback", function() + local machine = cartesi.machine(test_config) + expect.fail(function() machine:snapshot() end, "snapshot is not supported") + expect.fail(function() machine:rollback() end, "rollback is not supported") + end) +end) + +describe("machine store", function() + local function remove_temporary_files() + fs.remove_files({ + "temp_machine/0000000000001000-f000.bin", + "temp_machine/0000000000020000-6000.bin", + "temp_machine/0000000060000000-1000.bin", + "temp_machine/0000000060002000-1000.bin", + "temp_machine/0000000060004000-1000.bin", + "temp_machine/0000000060006000-1000.bin", + "temp_machine/0000000060008000-1000.bin", + "temp_machine/0000000070000000-20000.bin", + "temp_machine/0000000080000000-ee1000.bin", + "temp_machine/0080000000000000-4400000.bin", + "temp_machine/0090000000000000-4000.bin", + "temp_machine/config.protobuf", + "temp_machine/hash", + "temp_machine", + }) + end + + lester.before(remove_temporary_files) + lester.after(remove_temporary_files) + + it("should match hashes and configs between loaded and stored machines", function() + local saved_machine = cartesi.machine(test_config) + local saved_machine_hash = util.hexhash(saved_machine:get_root_hash()) + local saved_machine_config = saved_machine:get_initial_config() + saved_machine:store("temp_machine") + + local loaded_machine = cartesi.machine("temp_machine") + local loaded_machine_hash = util.hexhash(loaded_machine:get_root_hash()) + local loaded_machine_config = loaded_machine:get_initial_config() + + expect.equal(loaded_machine_hash, saved_machine_hash) + + -- all image filenames are lost and changed when using store + saved_machine_config.flash_drive[1].image_filename = "temp_machine/0080000000000000-4400000.bin" + saved_machine_config.flash_drive[2].image_filename = "temp_machine/0090000000000000-4000.bin" + saved_machine_config.ram.image_filename = "temp_machine/0000000080000000-ee1000.bin" + saved_machine_config.rollup.input_metadata.image_filename = "temp_machine/0000000060004000-1000.bin" + saved_machine_config.rollup.notice_hashes.image_filename = "temp_machine/0000000060008000-1000.bin" + saved_machine_config.rollup.rx_buffer.image_filename = "temp_machine/0000000060000000-1000.bin" + saved_machine_config.rollup.tx_buffer.image_filename = "temp_machine/0000000060002000-1000.bin" + saved_machine_config.rollup.voucher_hashes.image_filename = "temp_machine/0000000060006000-1000.bin" + saved_machine_config.rom.image_filename = "temp_machine/0000000000001000-f000.bin" + saved_machine_config.tlb.image_filename = "temp_machine/0000000000020000-6000.bin" + saved_machine_config.uarch.ram.image_filename = "temp_machine/0000000070000000-20000.bin" + + -- bootargs are lost when using store() + saved_machine_config.rom.bootargs = "" + + expect.equal(loaded_machine_config, saved_machine_config) + end) + + it("should fail when trying to saving into an invalid directory", function() + local machine = cartesi.machine(test_config) + expect.fail(function() machine:store("some/invalid/directory") end, "error creating directory") + end) +end) diff --git a/src/spec/htif-tests.lua b/src/spec/htif-tests.lua new file mode 100644 index 000000000..841ed4179 --- /dev/null +++ b/src/spec/htif-tests.lua @@ -0,0 +1,202 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local cartesi = require("cartesi") +local fs = require("spec.util.fs") +local lester = require("spec.util.lester") +local has_luaposix, unistd = pcall(require, "posix.unistd") +local describe, it, expect = lester.describe, lester.it, lester.expect + +describe("machine htif", function() + -- This test will fetch the rollup buffers from the PMA entries; check + -- that `rx_buffer` and `input_metadata` are filled with a byte patern; + -- then write a byte pattern into `tx_buffer`, `voucher_hashes` and + -- `notice_hashes`. + it("should write/read rollup buffers", function() + local ROLLUP_BUFFER_LENGTH = 4096 + local machine_config = { + ram = { image_filename = fs.tests_path .. "htif_rollup.bin", length = 0x4000000 }, + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + htif = { yield_automatic = true }, + rollup = { + rx_buffer = { start = 0x60000000, length = ROLLUP_BUFFER_LENGTH, shared = false }, + tx_buffer = { start = 0x60001000, length = ROLLUP_BUFFER_LENGTH, shared = false }, + input_metadata = { start = 0x60002000, length = ROLLUP_BUFFER_LENGTH, shared = false }, + voucher_hashes = { start = 0x60003000, length = ROLLUP_BUFFER_LENGTH, shared = false }, + notice_hashes = { start = 0x60004000, length = ROLLUP_BUFFER_LENGTH, shared = false }, + }, + } + local machine = cartesi.machine(machine_config) + -- fill input with `pattern` + local pattern = string.rep("\xef\xcd\xab\x89\x67\x45\x23\x01", ROLLUP_BUFFER_LENGTH / 8) + local rollup = machine_config.rollup + machine:write_memory(rollup.rx_buffer.start, pattern, rollup.rx_buffer.length) + -- fill input_metadata with `pattern` + machine:write_memory(rollup.input_metadata.start, pattern, rollup.input_metadata.length) + machine:run(math.maxinteger) + -- check that buffers got filled in with `pattern` + expect.equal(pattern, machine:read_memory(rollup.tx_buffer.start, rollup.tx_buffer.length)) + expect.equal(pattern, machine:read_memory(rollup.voucher_hashes.start, rollup.voucher_hashes.length)) + expect.equal(pattern, machine:read_memory(rollup.notice_hashes.start, rollup.notice_hashes.length)) + expect.truthy(machine:read_iflags_H()) + expect.equal(machine:read_mcycle(), 8981) + expect.equal(machine:read_htif_tohost_data() >> 1, 0) + end) + + local YIELD_MANUAL = cartesi.machine.HTIF_YIELD_MANUAL + local YIELD_AUTOMATIC = cartesi.machine.HTIF_YIELD_AUTOMATIC + local yields = { + { mcycle = 13, data = 10, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_PROGRESS }, + { mcycle = 44, data = 11, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_PROGRESS }, + { mcycle = 75, data = 12, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_PROGRESS }, + { mcycle = 107, data = 13, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_RX_ACCEPTED }, + { mcycle = 139, data = 14, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_RX_REJECTED }, + { mcycle = 171, data = 15, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_TX_VOUCHER }, + { mcycle = 203, data = 16, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_TX_NOTICE }, + { mcycle = 235, data = 17, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_TX_REPORT }, + { mcycle = 267, data = 18, cmd = YIELD_MANUAL, reason = cartesi.machine.HTIF_YIELD_REASON_TX_EXCEPTION }, + { mcycle = 298, data = 20, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_PROGRESS }, + { mcycle = 329, data = 21, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_PROGRESS }, + { mcycle = 360, data = 22, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_PROGRESS }, + { mcycle = 392, data = 23, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_RX_ACCEPTED }, + { mcycle = 424, data = 24, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_RX_REJECTED }, + { mcycle = 456, data = 25, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_TX_VOUCHER }, + { mcycle = 488, data = 26, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_TX_NOTICE }, + { mcycle = 520, data = 27, cmd = YIELD_AUTOMATIC, reason = cartesi.machine.HTIF_YIELD_REASON_TX_REPORT }, + } + local function make_yield_test(yield_automatic_enable, yield_manual_enable) + local test_name = + string.format("should sink for yield (automatic=%s manual=%s)", yield_automatic_enable, yield_manual_enable) + it(test_name, function() + local machine_config = { + ram = { image_filename = fs.tests_path .. "htif_yield.bin", length = 0x4000000 }, + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + htif = { yield_automatic = yield_automatic_enable, yield_manual = yield_manual_enable }, + } + local machine = cartesi.machine(machine_config) + local break_reason + for _, v in ipairs(yields) do + if + (v.cmd == YIELD_MANUAL and yield_manual_enable) + or (v.cmd == YIELD_AUTOMATIC and yield_automatic_enable) + then + while not machine:read_iflags_Y() and not machine:read_iflags_X() and not machine:read_iflags_H() do + break_reason = machine:run() + end + -- mcycle should be as expected + local mcycle = machine:read_mcycle() + expect.equal(mcycle, v.mcycle) + + if yield_automatic_enable and v.cmd == YIELD_AUTOMATIC then + expect.equal(break_reason, cartesi.BREAK_REASON_YIELDED_AUTOMATICALLY) + expect.truthy(machine:read_iflags_X()) + expect.falsy(machine:read_iflags_Y()) + elseif yield_manual_enable and v.cmd == YIELD_MANUAL then + expect.equal(break_reason, cartesi.BREAK_REASON_YIELDED_MANUALLY) + expect.truthy(machine:read_iflags_Y()) + expect.falsy(machine:read_iflags_X()) + else + expect.truthy(false) + end + -- data should be as expected + local data = machine:read_htif_tohost_data() + local reason = data >> 32 + data = data << 32 >> 32 + expect.equal(data, v.data) + expect.equal(reason, v.reason) + expect.equal(machine:read_htif_tohost_cmd(), v.cmd) + -- trying to run it without resetting iflags.Y should not advance + if machine:read_iflags_Y() then + machine:run() + expect.equal(machine:read_mcycle(), mcycle) + expect.truthy(machine:read_iflags_Y()) + end + -- now reset it so the machine can be advanced + machine:reset_iflags_Y() + machine:reset_iflags_X() + end + end + -- finally run to completion + while not machine:read_iflags_Y() and not machine:read_iflags_H() do + break_reason = machine:run() + end + -- should be halted + expect.equal(break_reason, cartesi.BREAK_REASON_HALTED) + expect.truthy(machine:read_iflags_H()) + -- at the expected mcycle + expect.equal(machine:read_mcycle(), 561) + -- with the expected payload + expect.equal((machine:read_htif_tohost_data() >> 1), 42) + end) + end + + make_yield_test(false, false) + make_yield_test(false, true) + make_yield_test(true, false) + make_yield_test(true, true) + + it("should write to console when getchar is disabled", function() + local machine = cartesi.machine({ + ram = { image_filename = fs.tests_path .. "htif_console.bin", length = 0x4000000 }, + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + htif = { console_getchar = false }, + }) + machine:run(math.maxinteger) + -- should be halted + expect.truthy(machine:read_iflags_H()) + -- with the expected payload + expect.equal((machine:read_htif_tohost_data() >> 1), 42) + -- at the expected mcycle + expect.equal(machine:read_mcycle(), 2141) + io.write("\n") + end) + + -- This test is only enabled if luaposix is installed in the system + it("should read/write to console when getchar is enabled", function() + -- create new FD for stdin and write in it, + -- later the cartesi machine console will consume this value + local read_fd, write_fd = unistd.pipe() + unistd.dup2(read_fd, unistd.STDIN_FILENO) + unistd.write(write_fd, "CTSI") + local machine = cartesi.machine({ + ram = { image_filename = fs.tests_path .. "htif_console.bin", length = 0x4000000 }, + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + htif = { console_getchar = true }, + }) + machine:run(math.maxinteger) + -- should be halted + expect.truthy(machine:read_iflags_H()) + -- with the expected payload + expect.equal((machine:read_htif_tohost_data() >> 1), 42) + -- at the expected mcycle + expect.equal(machine:read_mcycle(), 2141) + io.write("\n") + + -- we cannot initialize TTY twice + expect.fail( + function() + cartesi.machine({ + ram = { image_filename = fs.tests_path .. "htif_console.bin", length = 0x4000000 }, + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + htif = { console_getchar = true }, + }) + end, + "TTY already initialized" + ) + end, has_luaposix) +end) diff --git a/src/spec/keccak-tests.lua b/src/spec/keccak-tests.lua new file mode 100644 index 000000000..a7096faee --- /dev/null +++ b/src/spec/keccak-tests.lua @@ -0,0 +1,69 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local lester = require("spec.util.lester") +local util = require("cartesi.util") +local describe, it, expect = lester.describe, lester.it, lester.expect +local keccak = require("cartesi").keccak + +local function hexkeccak(...) return util.hexhash(keccak(...)) end + +describe("keccak", function() + it("should fail when passing invalid arguments", function() + expect.fail(function() keccak("a", "b", "c") end, "too many arguments") + expect.fail(function() keccak(1, 2) end, "too many arguments") + expect.fail(function() keccak() end, "too few arguments") + end) + + it("should match hashes for uint64 integers", function() + expect.equal(hexkeccak(0), "011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce") + expect.equal(hexkeccak(1), "30f692b256e24009bcb34d0ee84da73c298afacc0924e01105e2eb0f01a87fe2") + expect.equal(hexkeccak(-1), "ad0bfb4b0a66700aeb759d88c315168cc0a11ee99e2a680e548ecf0a464e7daf") + expect.equal(hexkeccak(0x8000000000000000), "f9b31243137c51434c88c419b2a3d7d2103a13948255efab17ca486946dfbf49") + expect.equal(hexkeccak(0xf0f10d89ba1e7bce), "86433232ac2024ad7962ccc2fbb7c0219499b98ec24049a81ca6484d206eb288") + end) + + it("should match hashes for one string", function() + expect.equal(hexkeccak(""), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + expect.equal(hexkeccak("0"), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d") + expect.equal(hexkeccak("test"), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + expect.equal(hexkeccak(hexkeccak("")), "79482f93ea0d714e293366322922962af38ecdd95cff648355c1af4b40a78b32") + end) + + it("should match hashes for two strings", function() + expect.equal(hexkeccak("", ""), "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + expect.equal(hexkeccak("0", ""), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d") + expect.equal(hexkeccak("", "0"), "044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d") + expect.equal(hexkeccak("test", ""), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + expect.equal(hexkeccak("tes", "t"), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + expect.equal(hexkeccak("te", "st"), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + expect.equal(hexkeccak("t", "est"), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + expect.equal(hexkeccak("", "test"), "9c22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658") + end) + + it("should match hashes for large ranges", function() + expect.equal( + hexkeccak(string.rep("a", 8191)), + "b52a6c73f463177a28d89360fb470808ba6572ec75de6db05a3bb044ca4d1009" + ) + expect.equal( + hexkeccak(string.rep("a", 4096), string.rep("a", 4095)), + "b52a6c73f463177a28d89360fb470808ba6572ec75de6db05a3bb044ca4d1009" + ) + end) +end) diff --git a/src/spec/machine-tests.lua b/src/spec/machine-tests.lua new file mode 100644 index 000000000..c23aa986b --- /dev/null +++ b/src/spec/machine-tests.lua @@ -0,0 +1,96 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local lester = require("spec.util.lester") +local fs = require("spec.util.fs") +local util = require("cartesi.util") +local cartesi = require("cartesi") +local keccak = require("cartesi").keccak +local describe, it, expect = lester.describe, lester.it, lester.expect + +describe("machine run", function() + it("should not break due to mtime interrupts", function() + local machine = cartesi.machine({ + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + ram = { image_filename = fs.tests_path .. "mtime_interrupt.bin", length = 1 << 20 }, + }) + machine:run() + expect.truthy(machine:read_iflags_H()) + expect.equal(machine:read_htif_tohost_data() >> 1, 0) + expect.equal(machine:read_mcycle(), cartesi.RTC_FREQ_DIV * 2 + 20) + end) + + it("should run up to mcycle limit", function() + local machine = cartesi.machine({ + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + ram = { image_filename = fs.tests_path .. "mcycle_overflow.bin", length = 1 << 20 }, + }) + -- Stop the machine before the first RAM instruction + local WFI_CYCLE = 7 + expect.equal(machine:run(WFI_CYCLE), cartesi.BREAK_REASON_REACHED_TARGET_MCYCLE) + machine:write_mcycle(cartesi.MAX_MCYCLE - 5) + -- Run once to trigger an interrupt, which might cause an overflow on the + -- next call to machine:run + expect.equal(machine:run(cartesi.MAX_MCYCLE - 4), cartesi.BREAK_REASON_REACHED_TARGET_MCYCLE) + expect.equal(machine:run(cartesi.MAX_MCYCLE), cartesi.BREAK_REASON_REACHED_TARGET_MCYCLE) + expect.equal(machine:read_mcycle(), cartesi.MAX_MCYCLE) + end) + + it("shouldn't change state in max mcycle", function() + local machine = cartesi.machine({ + rom = { image_filename = fs.tests_path .. "bootstrap.bin" }, + ram = { length = 1 << 20 }, + }) + machine:write_mcycle(cartesi.MAX_MCYCLE) + local hash_before = machine:get_root_hash() + expect.equal(machine:run(cartesi.MAX_MCYCLE), cartesi.BREAK_REASON_REACHED_TARGET_MCYCLE) + local hash_after = machine:get_root_hash() + expect.equal(hash_before, hash_after) + end) +end) + +describe("machine dump", function() + local pmas_file_names = { + "0000000000000000--0000000000001000.bin", -- shadow state + "0000000000001000--000000000000f000.bin", -- rom + "0000000000010000--0000000000001000.bin", -- shadow pmas + "0000000000020000--0000000000006000.bin", -- shadow tlb + "0000000002000000--00000000000c0000.bin", -- clint + "0000000040008000--0000000000001000.bin", -- htif + "0000000080000000--0000000000100000.bin", -- ram + } + local config = { + rom = { image_filename = fs.rom_image }, + ram = { length = 1 << 20 }, + } + + -- Auto remove PMA bin files after each test + lester.after(function() fs.remove_files(pmas_file_names) end) + + it("should match pmas dumps", function() + local machine = cartesi.machine(config) + machine:dump_pmas() + for _, file_name in ipairs(pmas_file_names) do + local mem_start, mem_size = file_name:match("^(%x+)%-%-(%x+)%.bin$") + mem_start, mem_size = tonumber(mem_start, 16), tonumber(mem_size, 16) + local file_mem = fs.read_file(file_name) + local machine_mem = machine:read_memory(mem_start, mem_size) + expect.equal(util.hexhash(keccak(file_mem)), util.hexhash(keccak(machine_mem))) + end + end) +end) diff --git a/src/spec/step-tests.lua b/src/spec/step-tests.lua new file mode 100644 index 000000000..1c1b8b746 --- /dev/null +++ b/src/spec/step-tests.lua @@ -0,0 +1,58 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local lester = require("spec.util.lester") +local fs = require("spec.util.fs") +local cartesi = require("cartesi") +local describe, it, expect = lester.describe, lester.it, lester.expect + +describe("machine step_uarch", function() + local only_rom_config = { + ram = { length = 0x4000000 }, + rom = { image_filename = fs.rom_image }, + uarch = { + ram = { image_filename = fs.uarch_ram_image, length = 0x20000 }, + }, + } + it("should verify state transition and access log", function() + local machine = cartesi.machine(only_rom_config) + local old_hash = machine:get_root_hash() + local access_log = machine:step_uarch({ proofs = true, annotations = true }) + expect.truthy(access_log.brackets) + expect.truthy(access_log.accesses) + expect.truthy(access_log.notes) + local new_hash = machine:get_root_hash() + local res = cartesi.machine.verify_state_transition(old_hash, access_log, new_hash, {}) + expect.equal(res, 1) + res = cartesi.machine.verify_access_log(access_log, {}) + expect.equal(res, 1) + end) + + for _, proofs in ipairs({ true, false }) do + it(string.format("should do nothing on max mcycle (proofs=%s)", proofs), function() + local machine = cartesi.machine(only_rom_config) + machine:write_mcycle(cartesi.MAX_MCYCLE) + local log = machine:step_uarch({ proofs = proofs }) + expect.equal(#log.accesses, 7) + local old_hash = machine:get_root_hash() + expect.equal(machine:read_mcycle(), cartesi.MAX_MCYCLE) + local new_hash = machine:get_root_hash() + expect.equal(old_hash, new_hash) + end) + end +end) diff --git a/src/spec/util/fs.lua b/src/spec/util/fs.lua new file mode 100644 index 000000000..966b83fee --- /dev/null +++ b/src/spec/util/fs.lua @@ -0,0 +1,55 @@ +#!/usr/bin/env lua5.4 + +-- Copyright 2023 Cartesi Pte. Ltd. +-- +-- This file is part of the machine-emulator. The machine-emulator is free +-- software: you can redistribute it and/or modify it under the terms of the GNU +-- Lesser General Public License as published by the Free Software Foundation, +-- either version 3 of the License, or (at your option) any later version. +-- +-- The machine-emulator is distributed in the hope that it will be useful, but +-- WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +-- FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +-- for more details. +-- +-- You should have received a copy of the GNU Lesser General Public License +-- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. +-- + +local fs = {} + +function fs.adjust_images_path(path) + if not path then return "" end + return string.gsub(path, "/*$", "") .. "/" +end + +function fs.remove_files(filenames) + for _, filename in pairs(filenames) do + os.remove(filename) + end +end + +function fs.read_file(filename) + local file = assert(io.open(filename, "rb")) + if not file then return nil end + local contents = file:read("*a") + file:close() + return contents +end + +function fs.get_file_length(filename) + local file = io.open(filename, "rb") + if not file then return nil end + local size = file:seek("end") + file:close() + return size +end + +fs.images_path = fs.adjust_images_path(os.getenv("CARTESI_IMAGES_PATH")) +fs.tests_path = fs.adjust_images_path(os.getenv("CARTESI_TESTS_PATH")) +fs.rom_image = fs.images_path .. "rom.bin" +fs.linux_image = fs.images_path .. "linux.bin" +fs.rootfs_image = fs.images_path .. "rootfs.ext2" +fs.uarch_ram_image = fs.images_path .. "uarch-ram.bin" + +return fs diff --git a/src/spec/util/lester.lua b/src/spec/util/lester.lua new file mode 100644 index 000000000..273bb2e22 --- /dev/null +++ b/src/spec/util/lester.lua @@ -0,0 +1,594 @@ +--[[ +Minimal test framework for Lua. +lester - v0.1.5 - 18/May/2023 +Eduardo Bart - edub4rt@gmail.com +https://github.com/edubart/lester +Minimal Lua test framework. +See end of file for LICENSE. +]] + +--[[-- +Lester is a minimal unit testing framework for Lua with a focus on being simple to use. + +## Features + +* Minimal, just one file. +* Self contained, no external dependencies. +* Simple and hackable when needed. +* Use `describe` and `it` blocks to describe tests. +* Supports `before` and `after` handlers. +* Colored output. +* Configurable via the script or with environment variables. +* Quiet mode, to use in live development. +* Optionally filter tests by name. +* Show traceback on errors. +* Show time to complete tests. +* Works with Lua 5.1+. +* Efficient. + +## Usage + +Copy `lester.lua` file to a project and require it, +which returns a table that includes all of the functionality: + +```lua +local lester = require 'lester' +local describe, it, expect = lester.describe, lester.it, lester.expect + +-- Customize lester configuration. +lester.show_traceback = false + +-- Parse arguments from command line. +lester.parse_args() + +describe('my project', function() + lester.before(function() + -- This function is run before every test. + end) + + describe('module1', function() -- Describe blocks can be nested. + it('feature1', function() + expect.equal('something', 'something') -- Pass. + end) + + it('feature2', function() + expect.truthy(false) -- Fail. + end) + + local feature3_test_enabled = false + it('feature3', function() -- This test will be skipped. + expect.truthy(false) -- Fail. + end, feature3_test_enabled) + end) +end) + +lester.report() -- Print overall statistic of the tests run. +lester.exit() -- Exit with success if all tests passed. +``` + +## Customizing output with environment variables + +To customize the output of lester externally, +you can set the following environment variables before running a test suite: + +* `LESTER_QUIET="true"`, omit print of passed tests. +* `LESTER_COLOR="false"`, disable colored output. +* `LESTER_SHOW_TRACEBACK="false"`, disable traceback on test failures. +* `LESTER_SHOW_ERROR="false"`, omit print of error description of failed tests. +* `LESTER_STOP_ON_FAIL="true"`, stop on first test failure. +* `LESTER_UTF8TERM="false"`, disable printing of UTF-8 characters. +* `LESTER_FILTER="some text"`, filter the tests that should be run. + +Note that these configurations can be changed via script too, check the documentation. + +## Customizing output with command line arguments + +You can also customize output using command line arguments +if `lester.parse_args()` is called at startup. + +The following command line arguments are available: + +* `--quiet`, omit print of passed tests. +* `--no-quiet`, show print of passed tests. +* `--no-color`, disable colored output. +* `--no-show-traceback`, disable traceback on test failures. +* `--no-show-error`, omit print of error description of failed tests. +* `--stop-on-fail`, stop on first test failure. +* `--no-utf8term`, disable printing of UTF-8 characters. +* `--filter="some text"`, filter the tests that should be run. + +]] + +-- Returns whether the terminal supports UTF-8 characters. +local function is_utf8term() + local lang = os.getenv("LANG") + return (lang and lang:lower():match("utf%-?8$")) and true or false +end + +-- Returns whether a system environment variable is "true". +local function getboolenv(varname, default) + local val = os.getenv(varname) + if val == "true" then + return true + elseif val == "false" then + return false + end + return default +end + +-- The lester module. +local lester = { + --- Weather lines of passed tests should not be printed. False by default. + quiet = getboolenv("LESTER_QUIET", false), + --- Weather the output should be colorized. True by default. + color = getboolenv("LESTER_COLOR", true), + --- Weather a traceback must be shown on test failures. True by default. + show_traceback = getboolenv("LESTER_SHOW_TRACEBACK", true), + --- Weather the error description of a test failure should be shown. True by default. + show_error = getboolenv("LESTER_SHOW_ERROR", true), + --- Weather test suite should exit on first test failure. False by default. + stop_on_fail = getboolenv("LESTER_STOP_ON_FAIL", false), + --- Weather we can print UTF-8 characters to the terminal. True by default when supported. + utf8term = getboolenv("LESTER_UTF8TERM", is_utf8term()), + --- A string with a lua pattern to filter tests. Nil by default. + filter = os.getenv("LESTER_FILTER") or "", + --- Function to retrieve time in seconds with milliseconds precision, `os.clock` by default. + seconds = os.clock, +} + +-- Variables used internally for the lester state. +local lester_start = nil +local last_succeeded = false +local level = 0 +local successes = 0 +local total_successes = 0 +local failures = 0 +local total_failures = 0 +local skipped = 0 +local total_skipped = 0 +local start = 0 +local befores = {} +local afters = {} +local names = {} + +-- Color codes. +local color_codes = { + reset = string.char(27) .. "[0m", + bright = string.char(27) .. "[1m", + red = string.char(27) .. "[31m", + green = string.char(27) .. "[32m", + yellow = string.char(27) .. "[33m", + blue = string.char(27) .. "[34m", + magenta = string.char(27) .. "[35m", +} + +local quiet_o_char = string.char(226, 151, 143) + +-- Colors table, returning proper color code if color mode is enabled. +local colors = setmetatable({}, { __index = function(_, key) return lester.color and color_codes[key] or "" end }) + +--- Table of terminal colors codes, can be customized. +lester.colors = colors + +-- Parse command line arguments from `arg` table. +-- It `arg` is nil then the global `arg` is used. +function lester.parse_args(arg) + for _, opt in ipairs(arg or _G.arg) do + local name, value + if opt:find("^%-%-filter") then + name = "filter" + value = opt:match("^%-%-filter%=(.*)$") + elseif opt:find("^%-%-no%-[a-z0-9-]+$") then + name = opt:match("^%-%-no%-([a-z0-9-]+)$"):gsub("-", "_") + value = false + elseif opt:find("^%-%-[a-z0-9-]+$") then + name = opt:match("^%-%-([a-z0-9-]+)$"):gsub("-", "_") + value = true + end + if + value ~= nil + and lester[name] ~= nil + and (type(lester[name]) == "boolean" or type(lester[name]) == "string") + then + lester[name] = value + end + end +end + +--- Describe a block of tests, which consists in a set of tests. +-- Describes can be nested. +-- @param name A string used to describe the block. +-- @param func A function containing all the tests or other describes. +function lester.describe(name, func) + if level == 0 then -- Get start time for top level describe blocks. + failures = 0 + successes = 0 + skipped = 0 + start = lester.seconds() + if not lester_start then lester_start = start end + end + -- Setup describe block variables. + level = level + 1 + names[level] = name + -- Run the describe block. + func() + -- Cleanup describe block. + afters[level] = nil + befores[level] = nil + names[level] = nil + level = level - 1 + -- Pretty print statistics for top level describe block. + if level == 0 and not lester.quiet and (successes > 0 or failures > 0) then + local io_write = io.write + local colors_reset, colors_green = colors.reset, colors.green + io_write( + failures == 0 and colors_green or colors.red, + "[====] ", + colors.magenta, + name, + colors_reset, + " | ", + colors_green, + successes, + colors_reset, + " successes / " + ) + if skipped > 0 then io_write(colors.yellow, skipped, colors_reset, " skipped / ") end + if failures > 0 then io_write(colors.red, failures, colors_reset, " failures / ") end + io_write(colors.bright, string.format("%.6f", lester.seconds() - start), colors_reset, " seconds\n") + end +end + +-- Error handler used to get traceback for errors. +local function xpcall_error_handler(err) return debug.traceback(tostring(err), 2) end + +-- Pretty print the line on the test file where an error happened. +local function show_error_line(err) + local info = debug.getinfo(3) + local io_write = io.write + local colors_reset = colors.reset + local short_src, currentline = info.short_src, info.currentline + io_write(" (", colors.blue, short_src, colors_reset, ":", colors.bright, currentline, colors_reset) + if err and lester.show_traceback then + local fnsrc = short_src .. ":" .. currentline + for cap1, cap2 in err:gmatch("\t[^\n:]+:(%d+): in function <([^>]+)>\n") do + if cap2 == fnsrc then + io_write("/", colors.bright, cap1, colors_reset) + break + end + end + end + io_write(")") +end + +-- Pretty print the test name, with breadcrumb for the describe blocks. +local function show_test_name(name) + local io_write = io.write + local colors_reset = colors.reset + for _, descname in ipairs(names) do + io_write(colors.magenta, descname, colors_reset, " | ") + end + io_write(colors.bright, name, colors_reset) +end + +--- Declare a test, which consists of a set of assertions. +-- @param name A name for the test. +-- @param func The function containing all assertions. +-- @param enabled If not nil and equals to false, the test will be skipped and this will be reported. +function lester.it(name, func, enabled) + -- Skip the test silently if it does not match the filter. + if lester.filter then + local fullname = table.concat(names, " | ") .. " | " .. name + if not fullname:match(lester.filter) then return end + end + local io_write = io.write + local colors_reset = colors.reset + -- Skip the test if it's disabled, while displaying a message + if enabled == false then + if not lester.quiet then + io_write(colors.yellow, "[SKIP] ", colors_reset) + show_test_name(name) + io_write("\n") + else -- Show just a character hinting that the test was skipped. + local o = (lester.utf8term and lester.color) and quiet_o_char or "o" + io_write(colors.yellow, o, colors_reset) + end + skipped = skipped + 1 + total_skipped = total_skipped + 1 + return + end + -- Execute before handlers. + for _, levelbefores in pairs(befores) do + for _, beforefn in ipairs(levelbefores) do + beforefn(name) + end + end + -- Run the test, capturing errors if any. + local success, err + if lester.show_traceback then + success, err = xpcall(func, xpcall_error_handler) + else + success, err = pcall(func) + if not success and err then err = tostring(err) end + end + -- Count successes and failures. + if success then + successes = successes + 1 + total_successes = total_successes + 1 + else + failures = failures + 1 + total_failures = total_failures + 1 + end + -- Print the test run. + if not lester.quiet then -- Show test status and complete test name. + if success then + io_write(colors.green, "[PASS] ", colors_reset) + else + io_write(colors.red, "[FAIL] ", colors_reset) + end + show_test_name(name) + if not success then show_error_line(err) end + io_write("\n") + else + if success then -- Show just a character hinting that the test succeeded. + local o = (lester.utf8term and lester.color) and quiet_o_char or "o" + io_write(colors.green, o, colors_reset) + else -- Show complete test name on failure. + io_write(last_succeeded and "\n" or "", colors.red, "[FAIL] ", colors_reset) + show_test_name(name) + show_error_line(err) + io_write("\n") + end + end + -- Print error message, colorizing its output if possible. + if err and lester.show_error then + if lester.color then + local errfile, errline, errmsg, rest = err:match("^([^:\n]+):(%d+): ([^\n]+)(.*)") + if errfile and errline and errmsg and rest then + io_write(colors.blue, errfile, colors_reset, ":", colors.bright, errline, colors_reset, ": ") + if errmsg:match("^%w([^:]*)$") then + io_write(colors.red, errmsg, colors_reset) + else + io_write(errmsg) + end + err = rest + end + end + io_write(err, "\n\n") + end + io.flush() + -- Stop on failure. + if not success and lester.stop_on_fail then + if lester.quiet then + io_write("\n") + io.flush() + end + lester.exit() + end + -- Execute after handlers. + for _, levelafters in pairs(afters) do + for _, afterfn in ipairs(levelafters) do + afterfn(name) + end + end + last_succeeded = success +end + +--- Set a function that is called before every test inside a describe block. +-- A single string containing the name of the test about to be run will be passed to `func`. +function lester.before(func) + local levelbefores = befores[level] + if not levelbefores then + levelbefores = {} + befores[level] = levelbefores + end + levelbefores[#levelbefores + 1] = func +end + +--- Set a function that is called after every test inside a describe block. +-- A single string containing the name of the test that was finished will be passed to `func`. +-- The function is executed independently if the test passed or failed. +function lester.after(func) + local levelafters = afters[level] + if not levelafters then + levelafters = {} + afters[level] = levelafters + end + levelafters[#levelafters + 1] = func +end + +--- Pretty print statistics of all test runs. +-- With total success, total failures and run time in seconds. +function lester.report() + local now = lester.seconds() + local colors_reset = colors.reset + io.write( + lester.quiet and "\n" or "", + colors.green, + total_successes, + colors_reset, + " successes / ", + colors.yellow, + total_skipped, + colors_reset, + " skipped / ", + colors.red, + total_failures, + colors_reset, + " failures / ", + colors.bright, + string.format("%.6f", now - (lester_start or now)), + colors_reset, + " seconds\n" + ) + io.flush() + return total_failures == 0 +end + +--- Exit the application with success code if all tests passed, or failure code otherwise. +function lester.exit() + -- Collect garbage before exiting to call __gc handlers + collectgarbage() + collectgarbage() + os.exit(total_failures == 0) +end + +local expect = {} +--- Expect module, containing utility function for doing assertions inside a test. +lester.expect = expect + +--- Converts a value to a human-readable string. +-- If the final string not contains only ASCII characters, +-- then it is converted to a Lua hexdecimal string. +function expect.tohumanstring(v) + local s = tostring(v) + if s:find("[^ -~\n\t]") then -- string contains non printable ASCII + return '"' .. s:gsub(".", function(c) return string.format("\\x%02X", c:byte()) end) .. '"' + end + return s +end + +--- Check if a function fails with an error. +-- If `expected` is nil then any error is accepted. +-- If `expected` is a string then we check if the error contains that string. +-- If `expected` is anything else then we check if both are equal. +function expect.fail(func, expected) + local ok, err = pcall(func) + if ok then + error("expected function to fail", 2) + elseif expected ~= nil then + local found = expected == err + if not found and type(expected) == "string" then found = string.find(tostring(err), expected, 1, true) end + if not found then + error("expected function to fail\nexpected:\n" .. tostring(expected) .. "\ngot:\n" .. tostring(err), 2) + end + end +end + +--- Check if a function does not fail with a error. +function expect.not_fail(func) + local ok, err = pcall(func) + if not ok then error("expected function to not fail\ngot error:\n" .. expect.tohumanstring(err), 2) end +end + +--- Check if a value is not `nil`. +function expect.exist(v) + if v == nil then error("expected value to exist\ngot:\n" .. expect.tohumanstring(v), 2) end +end + +--- Check if a value is `nil`. +function expect.not_exist(v) + if v ~= nil then error("expected value to not exist\ngot:\n" .. expect.tohumanstring(v), 2) end +end + +--- Check if an expression is evaluates to `true`. +function expect.truthy(v) + if not v then error("expected expression to be true\ngot:\n" .. expect.tohumanstring(v), 2) end +end + +--- Check if an expression is evaluates to `false`. +function expect.falsy(v) + if v then error("expected expression to be false\ngot:\n" .. expect.tohumanstring(v), 2) end +end + +--- Returns raw tostring result for a value. +local function rawtostring(v) + local mt = getmetatable(v) + if mt then setmetatable(v, nil) end + local s = tostring(v) + if mt then setmetatable(v, mt) end + return s +end + +-- Returns key suffix for a string_eq table key. +local function strict_eq_key_suffix(k) + if type(k) == "string" then + if k:find("^[a-zA-Z_][a-zA-Z0-9]*$") then -- string is a lua field + return "." .. k + elseif k:find("[^ -~\n\t]") then -- string contains non printable ASCII + return '["' .. k:gsub(".", function(c) return string.format("\\x%02X", c:byte()) end) .. '"]' + else + return '["' .. k .. '"]' + end + else + return string.format("[%s]", rawtostring(k)) + end +end + +--- Compare if two values are equal, considering nested tables. +function expect.strict_eq(t1, t2, name) + if rawequal(t1, t2) then return true end + name = name or "value" + local t1type, t2type = type(t1), type(t2) + if t1type ~= t2type then + return false, string.format("expected types to be equal for %s\nfirst: %s\nsecond: %s", name, t1type, t2type) + end + if t1type == "table" then + if getmetatable(t1) ~= getmetatable(t2) then + return false, + string.format( + "expected metatables to be equal for %s\nfirst: %s\nsecond: %s", + name, + expect.tohumanstring(t1), + expect.tohumanstring(t2) + ) + end + for k, v1 in pairs(t1) do + local ok, err = expect.strict_eq(v1, t2[k], name .. strict_eq_key_suffix(k)) + if not ok then return false, err end + end + for k, v2 in pairs(t2) do + local ok, err = expect.strict_eq(v2, t1[k], name .. strict_eq_key_suffix(k)) + if not ok then return false, err end + end + elseif t1 ~= t2 then + return false, + string.format( + "expected values to be equal for %s\nfirst:\n%s\nsecond:\n%s", + name, + expect.tohumanstring(t1), + expect.tohumanstring(t2) + ) + end + return true +end + +--- Check if two values are equal. +function expect.equal(v1, v2) + local ok, err = expect.strict_eq(v1, v2) + if not ok then error(err, 2) end +end + +--- Check if two values are not equal. +function expect.not_equal(v1, v2) + if expect.strict_eq(v1, v2) then + local v1s, v2s = expect.tohumanstring(v1), expect.tohumanstring(v2) + error("expected values to be not equal\nfirst value:\n" .. v1s .. "\nsecond value:\n" .. v2s, 2) + end +end + +return lester + +--[[ +The MIT License (MIT) + +Copyright (c) 2021-2023 Eduardo Bart (https://github.com/edubart) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +]] diff --git a/src/test-machine-c-api.cpp b/src/test-machine-c-api.cpp index 6c04748fb..7c0e3628f 100644 --- a/src/test-machine-c-api.cpp +++ b/src/test-machine-c-api.cpp @@ -183,7 +183,7 @@ BOOST_FIXTURE_TEST_CASE_NOLINT(create_machine_default_machine_test, default_mach BOOST_CHECK_EQUAL(error_code, CM_ERROR_INVALID_ARGUMENT); std::string result = err_msg; - std::string origin("ROM image filename is undefined"); + std::string origin("RAM length cannot be zero"); BOOST_CHECK_EQUAL(origin, result); cm_delete_error_message(err_msg); diff --git a/src/tests/htif-console.lua b/src/tests/htif-console.lua index 8df4bd11b..5334ab1bd 100644 --- a/src/tests/htif-console.lua +++ b/src/tests/htif-console.lua @@ -1,55 +1,54 @@ -#!/usr/bin/env lua5.3 - -local cartesi = require"cartesi" -local test_util = require "tests.util" - -local config = { - processor = { - mvendorid = -1, - mimpid = -1, - marchid = -1, - }, - ram = { - image_filename = test_util.tests_path .. "htif_console.bin", - length = 0x4000000, - }, - rom = { - image_filename = test_util.tests_path .. "bootstrap.bin" - }, +#!/usr/bin/env lua5.4 + +local cartesi = require("cartesi") +local test_util = require("tests.util") + +local config_base = { + processor = { + mvendorid = -1, + mimpid = -1, + marchid = -1, + }, + ram = { + image_filename = test_util.tests_path .. "htif_console.bin", + length = 0x4000000, + }, + rom = { + image_filename = test_util.tests_path .. "bootstrap.bin", + }, } -local function stderr(...) - io.stderr:write(string.format(...)) -end +local function stderr(...) io.stderr:write(string.format(...)) end local final_mcycle = 2141 local exit_payload = 42 -function test(config, console_getchar_enable) - stderr(" testing console_getchar:%s\n", - console_getchar_enable and "on" or "off" - ) +local function test(config, console_getchar_enable) + stderr(" testing console_getchar:%s\n", console_getchar_enable and "on" or "off") config.htif = { console_getchar = console_getchar_enable, } - local machine = cartesi.machine(config) + local machine = cartesi.machine(config) machine:run(math.maxinteger) -- should be halted assert(machine:read_iflags_H(), "expected iflags_H set") -- with the expected payload - assert((machine:read_htif_tohost_data() >> 1) == exit_payload, - string.format("exit payload: expected %u, got %u\n", exit_payload, - machine:read_htif_tohost_data() >> 1)) + assert( + (machine:read_htif_tohost_data() >> 1) == exit_payload, + string.format("exit payload: expected %u, got %u\n", exit_payload, machine:read_htif_tohost_data() >> 1) + ) -- at the expected mcycle - assert(machine:read_mcycle() == final_mcycle, string.format("mcycle: expected, %u got %u", - final_mcycle, machine:read_mcycle())) + assert( + machine:read_mcycle() == final_mcycle, + string.format("mcycle: expected, %u got %u", final_mcycle, machine:read_mcycle()) + ) stderr(" passed\n") end -for _, getchar in ipairs{true, false} do - test(config, getchar) +for _, getchar in ipairs({ true, false }) do + test(config_base, getchar) end diff --git a/src/tests/htif-rollup.lua b/src/tests/htif-rollup.lua index 76e61a7c7..a63d0eca8 100755 --- a/src/tests/htif-rollup.lua +++ b/src/tests/htif-rollup.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -16,10 +16,10 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require"cartesi" -local test_util = require "tests.util" +local cartesi = require("cartesi") +local test_util = require("tests.util") -local config = { +local config_base = { processor = { mvendorid = -1, mimpid = -1, @@ -34,53 +34,61 @@ local config = { length = 0x4000000, }, rom = { - image_filename = test_util.tests_path .. "bootstrap.bin" + image_filename = test_util.tests_path .. "bootstrap.bin", }, htif = { yield_automatic = true, }, rollup = { rx_buffer = { - start = 0x60000000, length = 0x1000, shared = false, + start = 0x60000000, + length = 0x1000, + shared = false, }, tx_buffer = { - start = 0x60001000, length = 0x1000, shared = false, + start = 0x60001000, + length = 0x1000, + shared = false, }, input_metadata = { - start = 0x60002000, length = 0x1000, shared = false, + start = 0x60002000, + length = 0x1000, + shared = false, }, voucher_hashes = { - start = 0x60003000, length = 0x1000, shared = false, + start = 0x60003000, + length = 0x1000, + shared = false, }, notice_hashes = { - start = 0x60004000, length = 0x1000, shared = false, + start = 0x60004000, + length = 0x1000, + shared = false, }, - } + }, } -local function stderr(...) - io.stderr:write(string.format(...)) -end +local function stderr(...) io.stderr:write(string.format(...)) end local final_mcycle = 8981 local exit_payload = 0 -function check_buffer(machine, pattern, buffer) - local mem = string.rep(pattern, buffer.length/8) +local function check_buffer(machine, pattern, buffer) + local mem = string.rep(pattern, buffer.length / 8) assert(mem == machine:read_memory(buffer.start, buffer.length)) end -function test(config) +local function test(config) local pattern = "\xef\xcd\xab\x89\x67\x45\x23\x01" - local machine = cartesi.machine(config) + local machine = cartesi.machine(config) -- fill input with `pattern` - local rx = config.rollup.rx_buffer; - machine:write_memory(rx.start, string.rep(pattern, rx.length/8), rx.length); + local rx = config.rollup.rx_buffer + machine:write_memory(rx.start, string.rep(pattern, rx.length / 8), rx.length) -- fill input_metadata with `pattern` - local im = config.rollup.input_metadata; - machine:write_memory(im.start, string.rep(pattern, im.length/8), im.length); + local im = config.rollup.input_metadata + machine:write_memory(im.start, string.rep(pattern, im.length / 8), im.length) machine:run(math.maxinteger) -- check that buffers got filled in with `pattern` @@ -100,4 +108,4 @@ function test(config) end stderr("testing rollup\n") -test(config) +test(config_base) diff --git a/src/tests/htif-yield.lua b/src/tests/htif-yield.lua index d2dd9335d..9ad653215 100755 --- a/src/tests/htif-yield.lua +++ b/src/tests/htif-yield.lua @@ -1,65 +1,65 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -local cartesi = require"cartesi" -local test_util = require "tests.util" +local cartesi = require("cartesi") +local test_util = require("tests.util") -- Config yields 5 times with progress -local config = { - processor = { - mvendorid = -1, - mimpid = -1, - marchid = -1, - }, - ram = { - image_filename = test_util.tests_path .. "htif_yield.bin", - length = 0x4000000, - }, - rom = { - image_filename = test_util.tests_path .. "bootstrap.bin" - }, +local config_base = { + processor = { + mvendorid = -1, + mimpid = -1, + marchid = -1, + }, + ram = { + image_filename = test_util.tests_path .. "htif_yield.bin", + length = 0x4000000, + }, + rom = { + image_filename = test_util.tests_path .. "bootstrap.bin", + }, } local YIELD_MANUAL = cartesi.machine.HTIF_YIELD_MANUAL local YIELD_AUTOMATIC = cartesi.machine.HTIF_YIELD_AUTOMATIC -local REASON_PROGRESS = cartesi.machine.HTIF_YIELD_REASON_PROGRESS +local REASON_PROGRESS = cartesi.machine.HTIF_YIELD_REASON_PROGRESS local REASON_RX_ACCEPTED = cartesi.machine.HTIF_YIELD_REASON_RX_ACCEPTED local REASON_RX_REJECTED = cartesi.machine.HTIF_YIELD_REASON_RX_REJECTED -local REASON_TX_VOUCHER = cartesi.machine.HTIF_YIELD_REASON_TX_VOUCHER -local REASON_TX_NOTICE = cartesi.machine.HTIF_YIELD_REASON_TX_NOTICE -local REASON_TX_REPORT = cartesi.machine.HTIF_YIELD_REASON_TX_REPORT -local REASON_TX_EXCEPTION = cartesi.machine.HTIF_YIELD_REASON_TX_EXCEPTION +local REASON_TX_VOUCHER = cartesi.machine.HTIF_YIELD_REASON_TX_VOUCHER +local REASON_TX_NOTICE = cartesi.machine.HTIF_YIELD_REASON_TX_NOTICE +local REASON_TX_REPORT = cartesi.machine.HTIF_YIELD_REASON_TX_REPORT +local REASON_TX_EXCEPTION = cartesi.machine.HTIF_YIELD_REASON_TX_EXCEPTION local yields = { - { mcycle = 13, data = 10, cmd = YIELD_MANUAL, reason = REASON_PROGRESS}, - { mcycle = 44, data = 11, cmd = YIELD_MANUAL, reason = REASON_PROGRESS}, - { mcycle = 75, data = 12, cmd = YIELD_MANUAL, reason = REASON_PROGRESS}, - { mcycle = 107, data = 13, cmd = YIELD_MANUAL, reason = REASON_RX_ACCEPTED}, - { mcycle = 139, data = 14, cmd = YIELD_MANUAL, reason = REASON_RX_REJECTED}, - { mcycle = 171, data = 15, cmd = YIELD_MANUAL, reason = REASON_TX_VOUCHER}, - { mcycle = 203, data = 16, cmd = YIELD_MANUAL, reason = REASON_TX_NOTICE}, - { mcycle = 235, data = 17, cmd = YIELD_MANUAL, reason = REASON_TX_REPORT}, - { mcycle = 267, data = 18, cmd = YIELD_MANUAL, reason = REASON_TX_EXCEPTION}, + { mcycle = 13, data = 10, cmd = YIELD_MANUAL, reason = REASON_PROGRESS }, + { mcycle = 44, data = 11, cmd = YIELD_MANUAL, reason = REASON_PROGRESS }, + { mcycle = 75, data = 12, cmd = YIELD_MANUAL, reason = REASON_PROGRESS }, + { mcycle = 107, data = 13, cmd = YIELD_MANUAL, reason = REASON_RX_ACCEPTED }, + { mcycle = 139, data = 14, cmd = YIELD_MANUAL, reason = REASON_RX_REJECTED }, + { mcycle = 171, data = 15, cmd = YIELD_MANUAL, reason = REASON_TX_VOUCHER }, + { mcycle = 203, data = 16, cmd = YIELD_MANUAL, reason = REASON_TX_NOTICE }, + { mcycle = 235, data = 17, cmd = YIELD_MANUAL, reason = REASON_TX_REPORT }, + { mcycle = 267, data = 18, cmd = YIELD_MANUAL, reason = REASON_TX_EXCEPTION }, - { mcycle = 298, data = 20, cmd = YIELD_AUTOMATIC, reason = REASON_PROGRESS}, - { mcycle = 329, data = 21, cmd = YIELD_AUTOMATIC, reason = REASON_PROGRESS}, - { mcycle = 360, data = 22, cmd = YIELD_AUTOMATIC, reason = REASON_PROGRESS}, - { mcycle = 392, data = 23, cmd = YIELD_AUTOMATIC, reason = REASON_RX_ACCEPTED}, - { mcycle = 424, data = 24, cmd = YIELD_AUTOMATIC, reason = REASON_RX_REJECTED}, - { mcycle = 456, data = 25, cmd = YIELD_AUTOMATIC, reason = REASON_TX_VOUCHER}, - { mcycle = 488, data = 26, cmd = YIELD_AUTOMATIC, reason = REASON_TX_NOTICE}, - { mcycle = 520, data = 27, cmd = YIELD_AUTOMATIC, reason = REASON_TX_REPORT}, + { mcycle = 298, data = 20, cmd = YIELD_AUTOMATIC, reason = REASON_PROGRESS }, + { mcycle = 329, data = 21, cmd = YIELD_AUTOMATIC, reason = REASON_PROGRESS }, + { mcycle = 360, data = 22, cmd = YIELD_AUTOMATIC, reason = REASON_PROGRESS }, + { mcycle = 392, data = 23, cmd = YIELD_AUTOMATIC, reason = REASON_RX_ACCEPTED }, + { mcycle = 424, data = 24, cmd = YIELD_AUTOMATIC, reason = REASON_RX_REJECTED }, + { mcycle = 456, data = 25, cmd = YIELD_AUTOMATIC, reason = REASON_TX_VOUCHER }, + { mcycle = 488, data = 26, cmd = YIELD_AUTOMATIC, reason = REASON_TX_NOTICE }, + { mcycle = 520, data = 27, cmd = YIELD_AUTOMATIC, reason = REASON_TX_REPORT }, } -local function stderr(...) - io.stderr:write(string.format(...)) -end +local function stderr(...) io.stderr:write(string.format(...)) end local final_mcycle = 561 local exit_payload = 42 +local progress_enable = false -function test(config, yield_automatic_enable, yield_manual_enable) - stderr(" testing yield_automatic:%s yield_manual:%s\n", +local function test(config, yield_automatic_enable, yield_manual_enable) + stderr( + " testing yield_automatic:%s yield_manual:%s\n", yield_automatic_enable and "on" or "off", yield_manual_enable and "on" or "off" ) @@ -67,26 +67,27 @@ function test(config, yield_automatic_enable, yield_manual_enable) yield_automatic = yield_automatic_enable, yield_manual = yield_manual_enable, } - local machine = cartesi.machine(config) + local machine = cartesi.machine(config) local break_reason - for i, v in ipairs(yields) do - if (v.reason == REASON_PROGRESS and progress_enable) or - (v.cmd == YIELD_MANUAL and yield_manual_enable) or - (v.cmd == YIELD_AUTOMATIC and yield_automatic_enable) + for _, v in ipairs(yields) do + if + (v.reason == REASON_PROGRESS and progress_enable) + or (v.cmd == YIELD_MANUAL and yield_manual_enable) + or (v.cmd == YIELD_AUTOMATIC and yield_automatic_enable) then - while not machine:read_iflags_Y() and - not machine:read_iflags_X() and - not machine:read_iflags_H() do + while not machine:read_iflags_Y() and not machine:read_iflags_X() and not machine:read_iflags_H() do break_reason = machine:run() end -- mcycle should be as expected local mcycle = machine:read_mcycle() - assert(mcycle == v.mcycle, - string.format("mcycle: expected %d, got %d", v.mcycle, mcycle)) + assert(mcycle == v.mcycle, string.format("mcycle: expected %d, got %d", v.mcycle, mcycle)) if yield_automatic_enable and v.cmd == YIELD_AUTOMATIC then - assert(break_reason == cartesi.BREAK_REASON_YIELDED_AUTOMATICALLY, "expected break reason yielded automatically") + assert( + break_reason == cartesi.BREAK_REASON_YIELDED_AUTOMATICALLY, + "expected break reason yielded automatically" + ) assert(machine:read_iflags_X(), "expected iflags_X set") assert(not machine:read_iflags_Y(), "expected iflags_Y not set") elseif yield_manual_enable and v.cmd == YIELD_MANUAL then @@ -101,8 +102,7 @@ function test(config, yield_automatic_enable, yield_manual_enable) local data = machine:read_htif_tohost_data() local reason = data >> 32 data = data << 32 >> 32 - assert(data == v.data, - string.format("data: expected %d, got %d", v.data, data)) + assert(data == v.data, string.format("data: expected %d, got %d", v.data, data)) assert(reason == v.reason) -- cmd should be as expected assert(machine:read_htif_tohost_cmd() == v.cmd) @@ -125,19 +125,22 @@ function test(config, yield_automatic_enable, yield_manual_enable) assert(break_reason == cartesi.BREAK_REASON_HALTED) assert(machine:read_iflags_H(), "expected iflags_H set") -- at the expected mcycle - assert(machine:read_mcycle() == final_mcycle, string.format("mcycle: expected, %u got %u", - final_mcycle, machine:read_mcycle())) + assert( + machine:read_mcycle() == final_mcycle, + string.format("mcycle: expected, %u got %u", final_mcycle, machine:read_mcycle()) + ) -- with the expected payload - assert((machine:read_htif_tohost_data() >> 1) == exit_payload, - string.format("exit payload: expected %u, got %u\n", exit_payload, - machine:read_htif_tohost_data() >> 1)) + assert( + (machine:read_htif_tohost_data() >> 1) == exit_payload, + string.format("exit payload: expected %u, got %u\n", exit_payload, machine:read_htif_tohost_data() >> 1) + ) stderr(" passed\n") end stderr("testing yield sink\n") -for _, auto in ipairs{true, false} do - for _, manual in ipairs{true, false} do - test(config, auto, manual) +for _, auto in ipairs({ true, false }) do + for _, manual in ipairs({ true, false }) do + test(config_base, auto, manual) end end diff --git a/src/tests/log-with-mtime-transition.lua b/src/tests/log-with-mtime-transition.lua index f5ee93566..a7f4e91d7 100644 --- a/src/tests/log-with-mtime-transition.lua +++ b/src/tests/log-with-mtime-transition.lua @@ -1,31 +1,26 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -local test_util = require "tests.util" -local cartesi = require"cartesi" - -local rom_filename = os.tmpname() -io.open(rom_filename, 'w'):close() -local deleter = setmetatable({}, { __gc = function() os.remove(rom_filename) end } ) +local test_util = require("tests.util") +local cartesi = require("cartesi") local config = { processor = { marchid = -1, mimplid = -1, mvendorid = -1, - mcycle = 99 + mcycle = 99, }, ram = { - length = 1<<12 + length = 1 << 12, }, rom = { - image_filename = rom_filename + image_filename = "", + }, + uarch = { + ram = { length = 1 << 20, image_filename = test_util.create_test_uarch_program() }, }, - uarch = { - ram = { length = 1 << 20, image_filename = test_util.create_test_uarch_program() } - } } -local machine = cartesi.machine(config) -os.remove(config.uarch.ram.image_filename) +local machine = cartesi.machine(config) local old_hash = machine:get_root_hash() local access_log = machine:step_uarch({ proofs = true }) diff --git a/src/tests/machine-bind.lua b/src/tests/machine-bind.lua index eb4cb5951..375894c43 100755 --- a/src/tests/machine-bind.lua +++ b/src/tests/machine-bind.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -21,12 +21,11 @@ -- Note: for jsongrpc machine test to work, jsonrpc-remote-cartesi-machine must run on -- same computer and jsonrpc-remote-cartesi-machine execution path must be provided -local cartesi = require "cartesi" -local cartesi_util = require "cartesi.util" -local test_util = require "tests.util" +local cartesi = require("cartesi") +local test_util = require("tests.util") -local remote_address = nil -local checkin_address = nil +local remote_address +local checkin_address local test_path = "./" local cleanup = {} @@ -34,7 +33,8 @@ local lua_cmd = arg[-1] .. " -e " -- Print help and exit local function help() - io.stderr:write(string.format([=[ + io.stderr:write(string.format( + [=[ Usage: %s [options] @@ -58,66 +58,79 @@ where options are: unix: can be a host name, IPv4 or IPv6 address. -]=], arg[0])) +]=], + arg[0] + )) os.exit() end - local options = { - { "^%-%-h$", function(all) - if not all then return false end - help() - end }, - { "^%-%-help$", function(all) - if not all then return false end - help() - end }, - { "^%-%-remote%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - remote_address = o - return true - end }, - { "^%-%-checkin%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - checkin_address = o - return true - end }, - { "^%-%-test%-path%=(.*)$", function(o) - if not o or #o < 1 then return false end - test_path = o - return true - end }, - { ".*", function(all) - error("unrecognized option " .. all) - end } + { + "^%-%-h$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-help$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-remote%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + remote_address = o + return true + end, + }, + { + "^%-%-checkin%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + checkin_address = o + return true + end, + }, + { + "^%-%-test%-path%=(.*)$", + function(o) + if not o or #o < 1 then return false end + test_path = o + return true + end, + }, + { + ".*", + function(all) error("unrecognized option " .. all) end, + }, } -- Process command line options local arguments = {} -for i, argument in ipairs({...}) do - if argument:sub(1,1) == "-" then - for j, option in ipairs(options) do - if option[2](argument:match(option[1])) then - break - end +for _, argument in ipairs({ ... }) do + if argument:sub(1, 1) == "-" then + for _, option in ipairs(options) do + if option[2](argument:match(option[1])) then break end end else - arguments[#arguments+1] = argument + arguments[#arguments + 1] = argument end end local SHADOW_BASE = 0x0 local cpu_x_addr = {} -local cpu_f_addr = {} -for i=0,31 do +for i = 0, 31 do cpu_x_addr[i] = i * 8 - cpu_f_addr[i] = 0x100 + i * 8 end local function get_cpu_xreg_test_values() local values = {} - for i=0,31 do + for i = 0, 31 do values[i] = i * 8 end return values @@ -125,7 +138,7 @@ end local function get_cpu_uarch_xreg_test_values() local values = {} - for i=0,31 do + for i = 0, 31 do values[i] = 0x10000 + (i * 8) end return values @@ -205,16 +218,18 @@ local function get_cpu_csr_test_values() end local machine_type = assert(arguments[1], "missing machine type") -assert(machine_type == "local" or machine_type == "grpc" or machine_type == "jsonrpc", - "unknown machine type, should be 'local', 'grpc', or 'jsonrpc'") +assert( + machine_type == "local" or machine_type == "grpc" or machine_type == "jsonrpc", + "unknown machine type, should be 'local', 'grpc', or 'jsonrpc'" +) local protocol -if (machine_type == "grpc") then +if machine_type == "grpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") assert(checkin_address, "missing checkin address") assert(test_path ~= nil, "test path must be provided and must be working directory of remote cartesi machine") protocol = require("cartesi.grpc") end -if (machine_type == "jsonrpc") then +if machine_type == "jsonrpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") assert(test_path ~= nil, "test path must be provided and must be working directory of remote cartesi machine") protocol = require("cartesi.jsonrpc") @@ -222,10 +237,11 @@ end local function connect() local remote = protocol.stub(remote_address, checkin_address) - local version = assert(remote.get_version(), - "could not connect to remote cartesi machine at " .. remote_address) + local version = assert(remote.get_version(), "could not connect to remote cartesi machine at " .. remote_address) local shutdown = function() remote.shutdown() end - local mt = { __gc = function() pcall(shutdown) end} + local mt = { + __gc = function() pcall(shutdown) end, + } setmetatable(cleanup, mt) return remote, version end @@ -238,10 +254,11 @@ local pmas_file_names = { "0000000002000000--00000000000c0000.bin", -- clint "0000000040008000--0000000000001000.bin", -- htif "0000000080000000--0000000000100000.bin", -- ram - "0000000070000000--0000000000010000.bin" -- uarch ram + "0000000070000000--0000000000010000.bin", -- uarch ram } -local pmas_sizes = { 4096, 61440, 4096, 24576, 786432, 4096, 1048576, 65536, 65536} +local pmas_sizes = { 4096, 61440, 4096, 24576, 786432, 4096, 1048576, 65536, 65536 } +local remote local function build_machine(type) -- Create new machine local concurrency_update_merkle_tree = 0 @@ -251,23 +268,23 @@ local function build_machine(type) initial_csr_values.x = initial_xreg_values local config = { processor = initial_csr_values, - rom = {image_filename = test_util.images_path .. "rom.bin"}, - ram = {length = 1 << 20}, - uarch = { + rom = { image_filename = test_util.images_path .. "rom.bin" }, + ram = { length = 1 << 20 }, + uarch = { processor = { - x = initial_uarch_xreg_values + x = initial_uarch_xreg_values, }, ram = { length = 1 << 16, image_filename = test_util.create_test_uarch_program() }, - } + }, } local runtime = { concurrency = { - update_merkle_tree = concurrency_update_merkle_tree - } + update_merkle_tree = concurrency_update_merkle_tree, + }, } - local new_machine = nil - if (type ~= "local") then + local new_machine + if type ~= "local" then if not remote then remote = connect() end new_machine = assert(remote.machine(config, runtime)) else @@ -286,132 +303,119 @@ local do_test = test_util.make_do_test(build_machine, machine_type) print("Testing machine bindings for type " .. machine_type) print("\n\ntesting machine initial flags") -do_test("machine should not have halt and yield initial flags set", - function(machine) - -- Check machine is not halted - assert(not machine:read_iflags_H(), "machine shouldn't be halted") - -- Check machine is not yielded - assert(not machine:read_iflags_Y(), "machine shouldn't be yielded") - end -) +do_test("machine should not have halt and yield initial flags set", function(machine) + -- Check machine is not halted + assert(not machine:read_iflags_H(), "machine shouldn't be halted") + -- Check machine is not yielded + assert(not machine:read_iflags_Y(), "machine shouldn't be yielded") +end) print("\n\ntesting machine register initial flag values ") -do_test("machine should have default config shadow register values", - function(machine) - local initial_csr_values = get_cpu_csr_test_values() - local initial_xreg_values = get_cpu_xreg_test_values() - initial_csr_values.x = nil - initial_csr_values.mvendorid = nil - initial_csr_values.marchid = nil - initial_csr_values.mimpid = nil - -- Check initialization and shadow reads - for k, v in pairs(initial_csr_values) do - local r = machine:read_word(cpu_csr_addr[k]) - assert(v == r) - end - for k, v in pairs(initial_xreg_values) do - local r = machine:read_word(cpu_x_addr[k]) - assert(v == r) - end +do_test("machine should have default config shadow register values", function(machine) + local initial_csr_values = get_cpu_csr_test_values() + local initial_xreg_values = get_cpu_xreg_test_values() + initial_csr_values.x = nil + initial_csr_values.mvendorid = nil + initial_csr_values.marchid = nil + initial_csr_values.mimpid = nil + -- Check initialization and shadow reads + for k, v in pairs(initial_csr_values) do + local r = machine:read_word(cpu_csr_addr[k]) + assert(v == r) end -) + for k, v in pairs(initial_xreg_values) do + local r = machine:read_word(cpu_x_addr[k]) + assert(v == r) + end +end) print("\n\ntesting merkle tree get_proof for values for registers") -do_test("should provide proof for values in registers", - function(machine) - local initial_csr_values = get_cpu_csr_test_values() - local initial_xreg_values = get_cpu_xreg_test_values() - initial_csr_values.x = nil - initial_csr_values.mvendorid = nil - initial_csr_values.marchid = nil - initial_csr_values.mimpid = nil - - -- Check proofs - for _, v in pairs(initial_csr_values) do - for el = 3, 63 do - local a = test_util.align(v, el) - assert(test_util.check_proof(assert(machine:get_proof(a, el)), - "no proof"), "proof failed") - end +do_test("should provide proof for values in registers", function(machine) + local initial_csr_values = get_cpu_csr_test_values() + local initial_xreg_values = get_cpu_xreg_test_values() + initial_csr_values.x = nil + initial_csr_values.mvendorid = nil + initial_csr_values.marchid = nil + initial_csr_values.mimpid = nil + + -- Check proofs + for _, v in pairs(initial_csr_values) do + for el = 3, 63 do + local a = test_util.align(v, el) + assert(test_util.check_proof(assert(machine:get_proof(a, el)), "no proof"), "proof failed") end + end - for _, v in pairs(initial_xreg_values) do - for el = 3, 63 do - local a = test_util.align(v, el) - assert(test_util.check_proof( - assert(machine:get_proof(a, el), "no proof")), - "proof failed") - end + for _, v in pairs(initial_xreg_values) do + for el = 3, 63 do + local a = test_util.align(v, el) + assert(test_util.check_proof(assert(machine:get_proof(a, el), "no proof")), "proof failed") end end -) +end) print("\n\ntesting get_csr_address function binding") -do_test("should return address value for csr register", - function(machine) - local module = cartesi - if (machine_type ~= "local") then - if not remote then remote = connect() end - module = remote - end - -- Check CSR address - for k, v in pairs(cpu_csr_addr) do - local u = module.machine.get_csr_address(k) - assert(u == v, "invalid return for " .. v) - end +do_test("should return address value for csr register", function() + local module = cartesi + if machine_type ~= "local" then + if not remote then remote = connect() end + module = remote end -) + -- Check CSR address + for k, v in pairs(cpu_csr_addr) do + local u = module.machine.get_csr_address(k) + assert(u == v, "invalid return for " .. v) + end +end) print("\n\ntesting get_x_address function binding") -do_test("should return address value for x registers", - function(machine) - local module = cartesi - if (machine_type ~= "local") then - if not remote then remote = connect() end - module = remote - end - -- Check x address - for i = 0,31 do - assert(module.machine.get_x_address(i) == SHADOW_BASE+i*8, "invalid return for x"..i) - end +do_test("should return address value for x registers", function() + local module = cartesi + if machine_type ~= "local" then + if not remote then remote = connect() end + module = remote end -) + -- Check x address + for i = 0, 31 do + assert(module.machine.get_x_address(i) == SHADOW_BASE + i * 8, "invalid return for x" .. i) + end +end) print("\n\ntesting get_x_uarch_address function binding") - do_test("should return address value for uarch x registers", - function(machine) - local SHADOW_UARCH_XBASE = 0x340 - local module = cartesi - if (machine_type == "grpc") then - if not remote then remote = connect() end - module = remote - end - -- Check x address - for i = 0,31 do - assert(module.machine.get_uarch_x_address(i) == SHADOW_UARCH_XBASE+i*8, "invalid return for uarch x"..i) - end - end - ) +do_test("should return address value for uarch x registers", function() + local SHADOW_UARCH_XBASE = 0x340 + local module = cartesi + if machine_type == "grpc" then + if not remote then remote = connect() end + module = remote + end + -- Check x address + for i = 0, 31 do + assert(module.machine.get_uarch_x_address(i) == SHADOW_UARCH_XBASE + i * 8, "invalid return for uarch x" .. i) + end +end) local function test_config_memory_range(range, name) - assert(type(range.length) == "number", "invalid "..name..".length") - assert(type(range.start) == "number", "invalid "..name..".start") - assert(range.shared == nil or type(range.shared) == "boolean", "invalid "..name..".shared") - assert(range.image_filename == nil or type(range.image_filename) == "string", "invalid "..name..".image_filename") + assert(type(range.length) == "number", "invalid " .. name .. ".length") + assert(type(range.start) == "number", "invalid " .. name .. ".start") + assert(range.shared == nil or type(range.shared) == "boolean", "invalid " .. name .. ".shared") + assert( + range.image_filename == nil or type(range.image_filename) == "string", + "invalid " .. name .. ".image_filename" + ) end local function test_config(config) assert(type(config) == "table", "config not a table") - for _, field in ipairs{"processor", "htif", "clint", "flash_drive", "ram", "rom"} do - assert(config[field] and type(config[field]) == 'table', "invalid field " .. field) + for _, field in ipairs({ "processor", "htif", "clint", "flash_drive", "ram", "rom" }) do + assert(config[field] and type(config[field]) == "table", "invalid field " .. field) end - local processor = config.processor for i = 1, 31 do - assert(type(config.processor.x[i]) == "number", "x"..i.." is not a number") + assert(type(config.processor.x[i]) == "number", "x" .. i .. " is not a number") end local htif = config.htif - for _, field in ipairs{"console_getchar", "yield_manual", "yield_automatic"} do - assert(htif[field] == nil or type(htif[field]) == "boolean", "invalid htif."..field) + for _, field in ipairs({ "console_getchar", "yield_manual", "yield_automatic" }) do + assert(htif[field] == nil or type(htif[field]) == "boolean", "invalid htif." .. field) end assert(type(htif.tohost) == "number", "invalid htif.tohost") assert(type(htif.fromhost) == "number", "invalid htif.fromhost") @@ -425,7 +429,7 @@ local function test_config(config) assert(rom.bootargs == nil or type(rom.bootargs) == "string", "invalid rom.bootargs") local tlb = config.tlb assert(tlb.image_filename == nil or type(tlb.image_filename) == "string", "invalid tlb.image_filename") - for i, f in ipairs(config.flash_drive) do + for _, f in ipairs(config.flash_drive) do test_config_memory_range(f) end local rollup = config.rollup @@ -439,268 +443,219 @@ local function test_config(config) end print("\n\ntesting get_default_config function binding") -do_test("should return default machine config", - function(machine) - local module = cartesi - if (machine_type ~= "local") then - if not remote then remote = connect() end - module = remote - end - test_config(module.machine.get_default_config()) +do_test("should return default machine config", function() + local module = cartesi + if machine_type ~= "local" then + if not remote then remote = connect() end + module = remote end -) + test_config(module.machine.get_default_config()) +end) print("\n\n test verifying integrity of the merkle tree") -do_test("verify_merkle_tree should return true", - function(machine) - -- Verify starting merkle tree - assert(machine:verify_merkle_tree(), "error, non consistent merkle tree") - end -) +do_test("verify_merkle_tree should return true", function(machine) + -- Verify starting merkle tree + assert(machine:verify_merkle_tree(), "error, non consistent merkle tree") +end) print("\n\n test calculation of initial root hash") -do_test("should return expected value", - function(machine) - -- Get starting root hash - local root_hash = machine:get_root_hash() - print("Root hash: ", test_util.tohex(root_hash)) - - machine:dump_pmas() - local calculated_root_hash = test_util.calculate_emulator_hash(test_path, - pmas_file_names, machine) - for _, file_name in pairs(pmas_file_names) do - os.remove(test_path .. file_name) - end - - assert(test_util.tohex(root_hash) == test_util.tohex(calculated_root_hash), - "initial root hash does not match") +do_test("should return expected value", function(machine) + -- Get starting root hash + local root_hash = machine:get_root_hash() + print("Root hash: ", test_util.tohex(root_hash)) + + machine:dump_pmas() + local calculated_root_hash = test_util.calculate_emulator_hash(test_path, pmas_file_names, machine) + for _, file_name in pairs(pmas_file_names) do + os.remove(test_path .. file_name) end -) + + assert(test_util.tohex(root_hash) == test_util.tohex(calculated_root_hash), "initial root hash does not match") +end) print("\n\n test get_initial_config") -do_test("should have expected values", - function(machine) - -- Check initial config - local initial_config = machine:get_initial_config() - test_config(initial_config) - assert(initial_config.processor.pc == 0x200, - "wrong pc reg initial config value") - assert(initial_config.processor.ilrsc == 0x2e0, - "wrong ilrsc reg initial config value") - assert(initial_config.processor.mstatus == 0x230, - "wrong mstatus reg initial config value") - assert(initial_config.clint.mtimecmp == 0, - "wrong clint mtimecmp initial config value") - assert(initial_config.htif.fromhost == 0, - "wrong htif fromhost initial config value") - assert(initial_config.htif.tohost == 0, - "wrong htif tohost initial config value") - assert(initial_config.htif.yield_automatic == false, - "wrong htif yield automatic initial config value") - assert(initial_config.htif.yield_manual == false, - "wrong htif yield manual initial config value") - assert(initial_config.rom.image_filename == test_util.images_path .. "rom.bin", - "wrong initial config image path name") - end -) +do_test("should have expected values", function(machine) + -- Check initial config + local initial_config = machine:get_initial_config() + test_config(initial_config) + assert(initial_config.processor.pc == 0x200, "wrong pc reg initial config value") + assert(initial_config.processor.ilrsc == 0x2e0, "wrong ilrsc reg initial config value") + assert(initial_config.processor.mstatus == 0x230, "wrong mstatus reg initial config value") + assert(initial_config.clint.mtimecmp == 0, "wrong clint mtimecmp initial config value") + assert(initial_config.htif.fromhost == 0, "wrong htif fromhost initial config value") + assert(initial_config.htif.tohost == 0, "wrong htif tohost initial config value") + assert(initial_config.htif.yield_automatic == false, "wrong htif yield automatic initial config value") + assert(initial_config.htif.yield_manual == false, "wrong htif yield manual initial config value") + assert( + initial_config.rom.image_filename == test_util.images_path .. "rom.bin", + "wrong initial config image path name" + ) +end) print("\n\n test read_csr") -do_test("should return expected values", - function(machine) - local initial_csr_values = get_cpu_csr_test_values() - initial_csr_values.mvendorid = cartesi.MVENDORID - initial_csr_values.marchid = cartesi.MARCHID - initial_csr_values.mimpid = cartesi.MIMPID - initial_csr_values.htif_tohost = 0x0 - initial_csr_values.htif_fromhost = 0x0 - initial_csr_values.htif_ihalt = 0x0 - initial_csr_values.htif_iconsole = 0x0 - initial_csr_values.htif_iyield = 0x0 - - -- Check csr register read - local to_ignore = { - iflags = true, - clint_mtimecmp = true, - htif_ihalt = true, - htif_iconsole = true, - } - for k in pairs(cpu_csr_addr) do - if not to_ignore[k] then - local method_name = "read_" .. k - assert(machine[method_name](machine) == initial_csr_values[k], - "wrong " .. k .. " value") - end +do_test("should return expected values", function(machine) + local initial_csr_values = get_cpu_csr_test_values() + initial_csr_values.mvendorid = cartesi.MVENDORID + initial_csr_values.marchid = cartesi.MARCHID + initial_csr_values.mimpid = cartesi.MIMPID + initial_csr_values.htif_tohost = 0x0 + initial_csr_values.htif_fromhost = 0x0 + initial_csr_values.htif_ihalt = 0x0 + initial_csr_values.htif_iconsole = 0x0 + initial_csr_values.htif_iyield = 0x0 + + -- Check csr register read + local to_ignore = { + iflags = true, + clint_mtimecmp = true, + htif_ihalt = true, + htif_iconsole = true, + } + for k in pairs(cpu_csr_addr) do + if not to_ignore[k] then + local method_name = "read_" .. k + assert(machine[method_name](machine) == initial_csr_values[k], "wrong " .. k .. " value") end end -) +end) print("\n\n dump pmas to files") -do_test("there should exist dumped files of expected size", - function(machine) - -- Dump pmas to files - machine:dump_pmas() +do_test("there should exist dumped files of expected size", function(machine) + -- Dump pmas to files + machine:dump_pmas() - for i = 1, #pmas_file_names do - local dumped_file = test_path .. pmas_file_names[i] - local fd = assert(io.open(dumped_file, "rb")) - local real_file_size = fd:seek("end") - fd:close(dumped_file) + for i = 1, #pmas_file_names do + local dumped_file = test_path .. pmas_file_names[i] + local fd = assert(io.open(dumped_file, "rb")) + local real_file_size = fd:seek("end") + fd:close(dumped_file) - assert(real_file_size == pmas_sizes[i], - "unexpected pmas file size " .. dumped_file) + assert(real_file_size == pmas_sizes[i], "unexpected pmas file size " .. dumped_file) - assert(test_util.file_exists(dumped_file), - "dumping pmas to file failed " .. dumped_file) + assert(test_util.file_exists(dumped_file), "dumping pmas to file failed " .. dumped_file) - os.remove(dumped_file) - end + os.remove(dumped_file) end -) - +end) print("\n\n read and write x registers") -do_test("writen and expected register values should match", - function(machine) - local initial_xreg_values = get_cpu_xreg_test_values() - -- Write/Read X registers - local x1_initial_value = machine:read_x(1) - assert(x1_initial_value == initial_xreg_values[1], "error reading x1 register") - machine:write_x(1, 0x1122) - assert(machine:read_x(1) == 0x1122, "error with writing to x1 register") - machine:write_x(1, x1_initial_value) - assert(machine:read_x(1) == x1_initial_value) - -- Read unexsisting register - local status_invalid_reg, retval = pcall(machine.read_x, machine, 1000) - assert(status_invalid_reg == false, "no error reading invalid x register") - end -) +do_test("writen and expected register values should match", function(machine) + local initial_xreg_values = get_cpu_xreg_test_values() + -- Write/Read X registers + local x1_initial_value = machine:read_x(1) + assert(x1_initial_value == initial_xreg_values[1], "error reading x1 register") + machine:write_x(1, 0x1122) + assert(machine:read_x(1) == 0x1122, "error with writing to x1 register") + machine:write_x(1, x1_initial_value) + assert(machine:read_x(1) == x1_initial_value) + -- Read unexsisting register + local status_invalid_reg = pcall(machine.read_x, machine, 1000) + assert(status_invalid_reg == false, "no error reading invalid x register") +end) print("\n\n read and write uarch x registers") -do_test("writen and expected register values should match", - function(machine) - local initial_xreg_values = get_cpu_uarch_xreg_test_values() - -- Write/Read uarch X registers - local x1_initial_value = machine:read_uarch_x(1) - assert(x1_initial_value == initial_xreg_values[1], "error reading uarch x1 register") - machine:write_uarch_x(1, 0x1122) - assert(machine:read_uarch_x(1) == 0x1122, "error with writing to uarch x1 register") - machine:write_uarch_x(1, x1_initial_value) - assert(machine:read_uarch_x(1) == x1_initial_value) - -- Read unexsisting uarch register - local status_invalid_reg, retval = pcall(machine.read_uarch_x, machine, 1000) - assert(status_invalid_reg == false, "no error reading invalid uarch x register") - end -) +do_test("writen and expected register values should match", function(machine) + local initial_xreg_values = get_cpu_uarch_xreg_test_values() + -- Write/Read uarch X registers + local x1_initial_value = machine:read_uarch_x(1) + assert(x1_initial_value == initial_xreg_values[1], "error reading uarch x1 register") + machine:write_uarch_x(1, 0x1122) + assert(machine:read_uarch_x(1) == 0x1122, "error with writing to uarch x1 register") + machine:write_uarch_x(1, x1_initial_value) + assert(machine:read_uarch_x(1) == x1_initial_value) + -- Read unexsisting uarch register + local status_invalid_reg = pcall(machine.read_uarch_x, machine, 1000) + assert(status_invalid_reg == false, "no error reading invalid uarch x register") +end) print("\n\n read and write csr registers") -do_test("writen and expected register values should match", - function(machine) - -- Check csr register write - local sscratch_initial_value = machine:read_csr('sscratch') - assert(machine:read_sscratch() == sscratch_initial_value, - "error reading csr sscratch") - machine:write_csr('sscratch', 0x1122) - assert(machine:read_csr('sscratch') == 0x1122) - machine:write_csr('sscratch', sscratch_initial_value) - - -- Read unexsisting register - local status_invalid_reg, retval = pcall(machine.read_csr, machine, "invalidreg") - assert(status_invalid_reg == false, "no error reading invalid csr register") - end -) +do_test("writen and expected register values should match", function(machine) + -- Check csr register write + local sscratch_initial_value = machine:read_csr("sscratch") + assert(machine:read_sscratch() == sscratch_initial_value, "error reading csr sscratch") + machine:write_csr("sscratch", 0x1122) + assert(machine:read_csr("sscratch") == 0x1122) + machine:write_csr("sscratch", sscratch_initial_value) + + -- Read unexsisting register + local status_invalid_reg = pcall(machine.read_csr, machine, "invalidreg") + assert(status_invalid_reg == false, "no error reading invalid csr register") +end) print("\n\n perform step and check mcycle register") -do_test("mcycle value should match", - function(machine) - local log_type = {} - local uarch_cycle_initial_value = machine:read_csr('uarch_cycle') +do_test("mcycle value should match", function(machine) + local log_type = {} + local uarch_cycle_initial_value = machine:read_csr("uarch_cycle") - machine:step_uarch(log_type) + machine:step_uarch(log_type) - -- Check mcycle increment - local uarch_cycle_current_value = machine:read_csr('uarch_cycle') - assert(uarch_cycle_current_value == uarch_cycle_initial_value + 1, - "wrong uarch_cycle value") - end -) + -- Check mcycle increment + local uarch_cycle_current_value = machine:read_csr("uarch_cycle") + assert(uarch_cycle_current_value == uarch_cycle_initial_value + 1, "wrong uarch_cycle value") +end) print("\n\n run_uarch tests") -do_test("advance one micro cycle without halting", - function(machine) - assert(machine:read_uarch_cycle() == 0, "uarch cycle should be 0") - assert(machine:read_uarch_halt_flag() == false, "machine should not be halted") - local status = machine:run_uarch(1) - assert(status == cartesi.UARCH_BREAK_REASON_REACHED_TARGET_CYCLE) - assert(machine:read_uarch_cycle() == 1, "uarch cycle should be 1") - assert(machine:read_uarch_halt_flag() == false, "machine should not be halted") - end -) - -do_test("advance micro cycles until halt", - function(machine) - assert(machine:read_uarch_cycle() == 0, "uarch cycle should be 0") - assert(machine:read_uarch_halt_flag() == false, "machine should not be halted") - local status = machine:run_uarch() - assert(status == cartesi.UARCH_BREAK_REASON_HALTED) - assert(machine:read_uarch_cycle() == 4, "uarch cycle should be 4") - assert(machine:read_uarch_halt_flag() == true, "machine should be halted") - end -) - +do_test("advance one micro cycle without halting", function(machine) + assert(machine:read_uarch_cycle() == 0, "uarch cycle should be 0") + assert(machine:read_uarch_halt_flag() == false, "machine should not be halted") + local status = machine:run_uarch(1) + assert(status == cartesi.UARCH_BREAK_REASON_REACHED_TARGET_CYCLE) + assert(machine:read_uarch_cycle() == 1, "uarch cycle should be 1") + assert(machine:read_uarch_halt_flag() == false, "machine should not be halted") +end) + +do_test("advance micro cycles until halt", function(machine) + assert(machine:read_uarch_cycle() == 0, "uarch cycle should be 0") + assert(machine:read_uarch_halt_flag() == false, "machine should not be halted") + local status = machine:run_uarch() + assert(status == cartesi.UARCH_BREAK_REASON_HALTED) + assert(machine:read_uarch_cycle() == 4, "uarch cycle should be 4") + assert(machine:read_uarch_halt_flag() == true, "machine should be halted") +end) print("\n\n run machine to 1000 mcycle") -do_test("mcycle value should be 1000 after execution", - function(machine) - -- Run machine - machine:write_csr('mcycle', 0) - assert(machine:read_csr('mcycle') == 0) - - local test = machine:read_mcycle() - while test < 1000 do - machine:run(1000) - test = machine:read_mcycle() - end - assert(machine:read_csr('mcycle') == 1000) +do_test("mcycle value should be 1000 after execution", function(machine) + -- Run machine + machine:write_csr("mcycle", 0) + assert(machine:read_csr("mcycle") == 0) + + local test = machine:read_mcycle() + while test < 1000 do + machine:run(1000) + test = machine:read_mcycle() end -) + assert(machine:read_csr("mcycle") == 1000) +end) print("\n\n check reading and writing htif registers") -do_test("htif register values should match", - function(machine) - -- Check HTIF interface bindings - assert(machine:read_htif_tohost(), "error reading htif tohost") - assert(machine:read_htif_tohost_dev(), "error reading htif tohost dev") - assert(machine:read_htif_tohost_cmd(), "error reading htif tohost cmd") - assert(machine:read_htif_tohost_data(), "error reading htif tohost data") - assert(machine:read_htif_fromhost(), "error reading htif fromhost") - machine:write_htif_tohost(0x123456) - assert(machine:read_htif_tohost() == 0x123456, "error writing htif tohost") - machine:write_htif_fromhost(0x12345678) - assert(machine:read_htif_fromhost() == 0x12345678, - "error writing htif fromhost") - machine:write_htif_fromhost_data(0x123456789A) - assert(machine:read_htif_ihalt(), "error reading htif ihalt") - assert(machine:read_htif_iyield(), "error reading htif yield") - end -) +do_test("htif register values should match", function(machine) + -- Check HTIF interface bindings + assert(machine:read_htif_tohost(), "error reading htif tohost") + assert(machine:read_htif_tohost_dev(), "error reading htif tohost dev") + assert(machine:read_htif_tohost_cmd(), "error reading htif tohost cmd") + assert(machine:read_htif_tohost_data(), "error reading htif tohost data") + assert(machine:read_htif_fromhost(), "error reading htif fromhost") + machine:write_htif_tohost(0x123456) + assert(machine:read_htif_tohost() == 0x123456, "error writing htif tohost") + machine:write_htif_fromhost(0x12345678) + assert(machine:read_htif_fromhost() == 0x12345678, "error writing htif fromhost") + machine:write_htif_fromhost_data(0x123456789A) + assert(machine:read_htif_ihalt(), "error reading htif ihalt") + assert(machine:read_htif_iyield(), "error reading htif yield") +end) print("\n\n check memory reading/writing") -do_test("written and read values should match", - function(machine) - -- Check mem write and mem read - local memory_read = machine:read_memory(0x80000000, 0x8) - machine:write_memory(0x800000FF, "mydataol12345678", 0x10) - memory_read = machine:read_memory(0x800000FF, 0x10) - assert(memory_read == "mydataol12345678") - - end -) +do_test("written and read values should match", function(machine) + -- Check mem write and mem read + machine:write_memory(0x800000FF, "mydataol12345678", 0x10) + local memory_read = machine:read_memory(0x800000FF, 0x10) + assert(memory_read == "mydataol12345678") +end) print("\n\n dump log to console") -do_test("dumped log content should match", - function(machine) - -- Dump log and check values - local lua_code = [[ " +do_test("dumped log content should match", function() + -- Dump log and check values + local lua_code = [[ " local cartesi = require 'cartesi' test_util = require 'tests.util' cartesi_util = require 'cartesi.util' @@ -711,7 +666,7 @@ do_test("dumped log content should match", processor = initial_csr_values, ram = {length = 1 << 20}, rom = {image_filename = test_util.images_path .. 'rom.bin'}, - uarch = { + uarch = { ram = { length = 1 << 16, image_filename = uarch_ram_path } } } @@ -720,45 +675,41 @@ do_test("dumped log content should match", local log = machine:step_uarch(log_type) cartesi_util.dump_log(log, io.stdout) " 2>&1]] - local p = io.popen(lua_cmd .. lua_code) - local output = p:read(2000) - p:close() - local expected_output = - "begin step\n" .. - " 1: read uarch.cycle@0x320(800): 0x0(0)\n" .. - " 2: read uarch.halt_flag@0x328(808): 0x0(0)\n" .. - " 3: read uarch.pc@0x330(816): 0x70000000(1879048192)\n" .. - " 4: read memory@0x70000000(1879048192): 0x3280029307b00513(3638911329427784979)\n" .. - " begin addi\n" .. - " 5: read uarch.x@0x340(832): 0x0(0)\n" .. - " 6: write uarch.x@0x390(912): 0x0(0) -> 0x7b(123)\n" .. - " 7: write uarch.pc@0x330(816): 0x70000000(1879048192) -> 0x70000004(1879048196)\n" .. - " end addi\n" .. - " 8: write uarch.cycle@0x320(800): 0x0(0) -> 0x1(1)\n" .. - "end step\n" - - print("Output of dump log:") - print("--------------------------") - print(output) - print("--------------------------") - assert(output == expected_output, "Output does not match expected output:\n"..expected_output) - end -) + local p = io.popen(lua_cmd .. lua_code) + local output = p:read(2000) + p:close() + local expected_output = "begin step\n" + .. " 1: read uarch.cycle@0x320(800): 0x0(0)\n" + .. " 2: read uarch.halt_flag@0x328(808): 0x0(0)\n" + .. " 3: read uarch.pc@0x330(816): 0x70000000(1879048192)\n" + .. " 4: read memory@0x70000000(1879048192): 0x3280029307b00513(3638911329427784979)\n" + .. " begin addi\n" + .. " 5: read uarch.x@0x340(832): 0x0(0)\n" + .. " 6: write uarch.x@0x390(912): 0x0(0) -> 0x7b(123)\n" + .. " 7: write uarch.pc@0x330(816): 0x70000000(1879048192) -> 0x70000004(1879048196)\n" + .. " end addi\n" + .. " 8: write uarch.cycle@0x320(800): 0x0(0) -> 0x1(1)\n" + .. "end step\n" + + print("Output of dump log:") + print("--------------------------") + print(output) + print("--------------------------") + assert(output == expected_output, "Output does not match expected output:\n" .. expected_output) +end) print("\n\ntesting step and verification") -do_test("machine step should pass verifications", - function(machine) - local module = cartesi - if (machine_type ~= "local") then - if not remote then remote = connect() end - module = remote - end - local initial_hash = machine:get_root_hash() - local log = machine:step_uarch({proofs = true, annotations = true}) - local final_hash = machine:get_root_hash() - module.machine.verify_state_transition(initial_hash, log, final_hash, {}) - module.machine.verify_access_log(log, {}) +do_test("machine step should pass verifications", function(machine) + local module = cartesi + if machine_type ~= "local" then + if not remote then remote = connect() end + module = remote end -) + local initial_hash = machine:get_root_hash() + local log = machine:step_uarch({ proofs = true, annotations = true }) + local final_hash = machine:get_root_hash() + module.machine.verify_state_transition(initial_hash, log, final_hash, {}) + module.machine.verify_access_log(log, {}) +end) print("\n\nAll machine binding tests for type " .. machine_type .. " passed") diff --git a/src/tests/machine-test.lua b/src/tests/machine-test.lua index 9089817c9..cb8c13f19 100755 --- a/src/tests/machine-test.lua +++ b/src/tests/machine-test.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -15,9 +15,9 @@ -- You should have received a copy of the GNU Lesser General Public License -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -local cartesi = require "cartesi" -local test_util = require "tests.util" -local md5 = require "md5" +local cartesi = require("cartesi") +local test_util = require("tests.util") +local md5 = require("md5") -- Note: for grpc machine test to work, remote-cartesi-machine must run on -- same computer and remote-cartesi-machine execution path must be provided @@ -27,27 +27,19 @@ local md5 = require "md5" -- There is no UINT64_MAX in Lua, so we have to use the signed representation local MAX_MCYCLE = -1 -local remote_address = nil -local checkin_address = nil +local remote_address +local checkin_address local test_path = "./" local cleanup = {} -local function get_file_length(filename) - local file = io.open(filename, "rb") - if not file then return nil end - local size = file:seek("end") -- get file size - file:close() - return size -end - local linux_image = test_util.images_path .. "linux.bin" local rom_image = test_util.images_path .. "rom.bin" local rootfs_image = test_util.images_path .. "rootfs.ext2" -local rootfs_length = get_file_length(rootfs_image) -- Print help and exit local function help() - io.stderr:write(string.format([=[ + io.stderr:write(string.format( + [=[ Usage: %s [options] @@ -71,65 +63,82 @@ where options are: unix: can be a host name, IPv4 or IPv6 address. -]=], arg[0])) +]=], + arg[0] + )) os.exit() end - local options = { - { "^%-%-h$", function(all) - if not all then return false end - help() - end }, - { "^%-%-help$", function(all) - if not all then return false end - help() - end }, - { "^%-%-remote%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - remote_address = o - return true - end }, - { "^%-%-checkin%-address%=(.*)$", function(o) - if not o or #o < 1 then return false end - checkin_address = o - return true - end }, - { "^%-%-test%-path%=(.*)$", function(o) - if not o or #o < 1 then return false end - test_path = o - return true - end }, - { ".*", function(all) - error("unrecognized option " .. all) - end } + { + "^%-%-h$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-help$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-remote%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + remote_address = o + return true + end, + }, + { + "^%-%-checkin%-address%=(.*)$", + function(o) + if not o or #o < 1 then return false end + checkin_address = o + return true + end, + }, + { + "^%-%-test%-path%=(.*)$", + function(o) + if not o or #o < 1 then return false end + test_path = o + return true + end, + }, + { + ".*", + function(all) error("unrecognized option " .. all) end, + }, } -- Process command line options local arguments = {} -for i, argument in ipairs({...}) do - if argument:sub(1,1) == "-" then - for j, option in ipairs(options) do - if option[2](argument:match(option[1])) then - break - end +for _, argument in ipairs({ ... }) do + if argument:sub(1, 1) == "-" then + for _, option in ipairs(options) do + if option[2](argument:match(option[1])) then break end end else - arguments[#arguments+1] = argument + arguments[#arguments + 1] = argument end end local machine_type = assert(arguments[1], "missing machine type") -assert(machine_type == "local" or machine_type == "grpc" or machine_type == "jsonrpc", - "unknown machine type, should be 'local', 'grpc', or 'jsonrpc'") +assert( + machine_type == "local" or machine_type == "grpc" or machine_type == "jsonrpc", + "unknown machine type, should be 'local', 'grpc', or 'jsonrpc'" +) local protocol -if (machine_type == "grpc") then +if machine_type == "grpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") assert(checkin_address, "missing checkin address") assert(test_path ~= nil, "test path must be provided and must be working directory of remote cartesi machine") protocol = require("cartesi.grpc") end -if (machine_type == "jsonrpc") then +if machine_type == "jsonrpc" then assert(remote_address ~= nil, "remote cartesi machine address is missing") assert(test_path ~= nil, "test path must be provided and must be working directory of remote cartesi machine") protocol = require("cartesi.jsonrpc") @@ -137,10 +146,11 @@ end local function connect() local remote = protocol.stub(remote_address, checkin_address) - local version = assert(remote.get_version(), - "could not connect to remote cartesi machine at " .. remote_address) + local version = assert(remote.get_version(), "could not connect to remote cartesi machine at " .. remote_address) local shutdown = function() remote.shutdown() end - local mt = { __gc = function() pcall(shutdown) end} + local mt = { + __gc = function() pcall(shutdown) end, + } setmetatable(cleanup, mt) return remote, version end @@ -152,7 +162,7 @@ local pmas_file_names = { "0000000000020000--0000000000006000.bin", -- shadow tlb "0000000002000000--00000000000c0000.bin", -- clint "0000000040008000--0000000000001000.bin", -- htif - "0000000080000000--0000000000100000.bin", -- ram + "0000000080000000--0000000000100000.bin", -- ram } local pmas_file_names_with_uarch = { @@ -162,34 +172,28 @@ local pmas_file_names_with_uarch = { "0000000000020000--0000000000006000.bin", -- shadow tlb "0000000002000000--00000000000c0000.bin", -- clint "0000000040008000--0000000000001000.bin", -- htif - "0000000080000000--0000000000100000.bin", -- ram - "0000000070000000--0000000000100000.bin" -- uarch ram + "0000000080000000--0000000000100000.bin", -- ram + "0000000070000000--0000000000100000.bin", -- uarch ram } - -local function run_loop(machine, mcycle_end) - while machine:read_mcycle() < mcycle_end do - machine:run(mcycle_end) - if machine:read_iflags_H() then break end - end -end - +local remote local function build_machine(type, config) -- Create new machine -- Use default config to be max reproducible local concurrency_update_merkle_tree = 0 - config = config or { - processor = {}, - ram = {length = 1 << 20}, - rom = {image_filename = rom_image} - } + config = config + or { + processor = {}, + ram = { length = 1 << 20 }, + rom = { image_filename = rom_image }, + } local runtime = { concurrency = { - update_merkle_tree = concurrency_update_merkle_tree - } + update_merkle_tree = concurrency_update_merkle_tree, + }, } - local new_machine = nil - if (type ~= "local") then + local new_machine + if type ~= "local" then if not remote then remote = connect() end new_machine = assert(remote.machine(config, runtime)) else @@ -201,14 +205,14 @@ end local function build_uarch_machine(type) local config = { processor = {}, - ram = {length = 1 << 20}, - rom = {image_filename = rom_image}, - uarch = { - ram = { - length = 1 << 20, - image_filename = test_util.create_test_uarch_program() - } - } + ram = { length = 1 << 20 }, + rom = { image_filename = rom_image }, + uarch = { + ram = { + length = 1 << 20, + image_filename = test_util.create_test_uarch_program(), + }, + }, } local machine = build_machine(type, config) os.remove(config.uarch.ram.image_filename) @@ -218,86 +222,72 @@ end local do_test = test_util.make_do_test(build_machine, machine_type) local function remove_files(file_names) - for _, file_name in pairs(file_names) do os.remove(test_path .. file_name) end + for _, file_name in pairs(file_names) do + os.remove(test_path .. file_name) + end end - print("Testing machine for type " .. machine_type) print("\n\ntesting getting machine intial config and iflags") -do_test("machine halt and yield flags and config matches", - function(machine) - -- Get machine default config and test for known fields - local initial_config = machine:get_initial_config() - -- test_util.print_table(initial_config) - assert(initial_config["processor"]["marchid"] == cartesi.MARCHID, - "marchid value does not match") - assert(initial_config["processor"]["pc"] == 0x1000, - "pc value does not match") - assert(initial_config["ram"]["length"] == 1048576, - "ram length value does not match") - assert(initial_config["rom"]["image_filename"] ~= "", - "rom image filename not set") - -- Check machine is not halted - assert(not machine:read_iflags_H(), "machine shouldn't be halted") - -- Check machine is not yielded - assert(not machine:read_iflags_Y(), "machine shouldn't be yielded") - end -) +do_test("machine halt and yield flags and config matches", function(machine) + -- Get machine default config and test for known fields + local initial_config = machine:get_initial_config() + -- test_util.print_table(initial_config) + assert(initial_config["processor"]["marchid"] == cartesi.MARCHID, "marchid value does not match") + assert(initial_config["processor"]["pc"] == 0x1000, "pc value does not match") + assert(initial_config["ram"]["length"] == 1048576, "ram length value does not match") + assert(initial_config["rom"]["image_filename"] ~= "", "rom image filename not set") + -- Check machine is not halted + assert(not machine:read_iflags_H(), "machine shouldn't be halted") + -- Check machine is not yielded + assert(not machine:read_iflags_Y(), "machine shouldn't be yielded") +end) print("\n\ntesting memory pmas dump to files") -do_test("dumped file hashes should match memory data hashes", - function(machine) - -- Dump memory regions to files - -- Calculate hash for memory regions - -- Check if match memory data hash - machine:dump_pmas() - - for _, file_name in pairs(pmas_file_names) do +do_test("dumped file hashes should match memory data hashes", function(machine) + -- Dump memory regions to files + -- Calculate hash for memory regions + -- Check if match memory data hash + machine:dump_pmas() - print("Checking dump file " .. file_name) + for _, file_name in pairs(pmas_file_names) do + print("Checking dump file " .. file_name) - local temp = test_util.split_string(file_name, "--.") - local data_region_start = tonumber(temp[1], 16) - local data_region_size = tonumber(temp[2], 16) + local temp = test_util.split_string(file_name, "--.") + local data_region_start = tonumber(temp[1], 16) + local data_region_size = tonumber(temp[2], 16) - local dump = assert(io.open(test_path .. file_name, 'rb')) - local dump_hash = md5.sumhexa(dump:read("*all")) - dump:close() + local dump = assert(io.open(test_path .. file_name, "rb")) + local dump_hash = md5.sumhexa(dump:read("*all")) + dump:close() - local memory_read = machine:read_memory(data_region_start, data_region_size) - local memory_hash = md5.sumhexa(memory_read) + local memory_read = machine:read_memory(data_region_start, data_region_size) + local memory_hash = md5.sumhexa(memory_read) - assert(dump_hash == memory_hash, - "hash does not match for dump file " .. file_name) - end - remove_files(pmas_file_names) + assert(dump_hash == memory_hash, "hash does not match for dump file " .. file_name) end -) + remove_files(pmas_file_names) +end) print("\n\ntesting if machine initial hash is correct") -do_test("machine initial hash should match", - function(machine) - -- Get starting root hash - local root_hash = machine:get_root_hash() +do_test("machine initial hash should match", function(machine) + -- Get starting root hash + local root_hash = machine:get_root_hash() - machine:dump_pmas() - local calculated_root_hash = test_util.calculate_emulator_hash(test_path, - pmas_file_names, machine) + machine:dump_pmas() + local calculated_root_hash = test_util.calculate_emulator_hash(test_path, pmas_file_names, machine) - print("Root hash:", test_util.tohex(root_hash), " calculated root hash:", - test_util.tohex(calculated_root_hash)) + print("Root hash:", test_util.tohex(root_hash), " calculated root hash:", test_util.tohex(calculated_root_hash)) - assert( - test_util.tohex(root_hash) == test_util.tohex(calculated_root_hash), - "Initial root hash does not match") + assert(test_util.tohex(root_hash) == test_util.tohex(calculated_root_hash), "Initial root hash does not match") - remove_files(pmas_file_names) - end -) + remove_files(pmas_file_names) +end) print("\n\ntesting root hash after step one") -test_util.make_do_test(build_uarch_machine, machine_type)("machine root hash after step one should match", +test_util.make_do_test(build_uarch_machine, machine_type)( + "machine root hash after step one should match", function(machine) -- Get starting root hash local root_hash = machine:get_root_hash() @@ -307,9 +297,7 @@ test_util.make_do_test(build_uarch_machine, machine_type)("machine root hash aft local calculated_root_hash = test_util.calculate_emulator_hash(test_path, pmas_file_names_with_uarch, machine) remove_files(pmas_file_names) - assert(test_util.tohex(root_hash) == - test_util.tohex(calculated_root_hash), - "Initial root hash does not match") + assert(test_util.tohex(root_hash) == test_util.tohex(calculated_root_hash), "Initial root hash does not match") -- Perform step, dump address space to file, calculate emulator root hash -- and check if maches @@ -318,199 +306,203 @@ test_util.make_do_test(build_uarch_machine, machine_type)("machine root hash aft local root_hash_step1 = machine:get_root_hash() machine:dump_pmas() - local calculated_root_hash_step1 = test_util.calculate_emulator_hash(test_path, - pmas_file_names_with_uarch, machine) + local calculated_root_hash_step1 = + test_util.calculate_emulator_hash(test_path, pmas_file_names_with_uarch, machine) -- Remove dumped pmas files remove_files(pmas_file_names) - assert(test_util.tohex(root_hash_step1) == - test_util.tohex(calculated_root_hash_step1), - "hash after first step does not match") + assert( + test_util.tohex(root_hash_step1) == test_util.tohex(calculated_root_hash_step1), + "hash after first step does not match" + ) end ) print("\n\ntesting proof after step one") -test_util.make_do_test(build_uarch_machine, machine_type)("proof check should pass", - function(machine) - local log_type = {} - machine:step_uarch(log_type) - - -- Dump RAM memory to file, calculate hash of file - -- get proof of ram using get_proof and check if - -- hashes match - machine:dump_pmas() - local ram_file_name = pmas_file_names[5] - local ram = test_util.parse_pma_file(test_path .. ram_file_name) - - remove_files(pmas_file_names) - - local ram_address_start = tonumber(test_util.split_string(ram_file_name, - "--.")[1], 16) - local ram_data_number_of_pages = math.ceil(#ram / (1 << 12)) - local ram_log2_data_size = math.ceil(math.log(#ram, 2)) - local calculated_ram_hash = test_util.calculate_region_hash(ram, - ram_data_number_of_pages, - 12, - ram_log2_data_size) - local ram_proof = machine:get_proof(ram_address_start, ram_log2_data_size) - local root_hash = machine:get_root_hash() - - assert(test_util.tohex(root_hash) == test_util.tohex(ram_proof.root_hash), - "root hash in proof does not match") - print("target hash:", test_util.tohex(ram_proof.target_hash), - " calculated target hash:", test_util.tohex(calculated_ram_hash)) - assert(test_util.tohex(calculated_ram_hash) == - test_util.tohex(ram_proof.target_hash), - "target hash in proof does not match") - end -) +test_util.make_do_test(build_uarch_machine, machine_type)("proof check should pass", function(machine) + local log_type = {} + machine:step_uarch(log_type) + + -- Dump RAM memory to file, calculate hash of file + -- get proof of ram using get_proof and check if + -- hashes match + machine:dump_pmas() + local ram_file_name = pmas_file_names[5] + local ram = test_util.parse_pma_file(test_path .. ram_file_name) + + remove_files(pmas_file_names) + + local ram_address_start = tonumber(test_util.split_string(ram_file_name, "--.")[1], 16) + local ram_data_number_of_pages = math.ceil(#ram / (1 << 12)) + local ram_log2_data_size = math.ceil(math.log(#ram, 2)) + local calculated_ram_hash = test_util.calculate_region_hash(ram, ram_data_number_of_pages, 12, ram_log2_data_size) + local ram_proof = machine:get_proof(ram_address_start, ram_log2_data_size) + local root_hash = machine:get_root_hash() + + assert(test_util.tohex(root_hash) == test_util.tohex(ram_proof.root_hash), "root hash in proof does not match") + print( + "target hash:", + test_util.tohex(ram_proof.target_hash), + " calculated target hash:", + test_util.tohex(calculated_ram_hash) + ) + assert( + test_util.tohex(calculated_ram_hash) == test_util.tohex(ram_proof.target_hash), + "target hash in proof does not match" + ) +end) print("\n\nrun machine to 1000 mcycle and check for mcycle and root hash") -do_test("mcycle and root hash should match", - function(machine) - -- Run to 1000 cycle tics - local current_mcycle = machine:read_mcycle() - while current_mcycle < 1000 do - machine:run(1000) - current_mcycle = machine:read_mcycle() - end +do_test("mcycle and root hash should match", function(machine) + -- Run to 1000 cycle tics + local current_mcycle = machine:read_mcycle() + while current_mcycle < 1000 do + machine:run(1000) + current_mcycle = machine:read_mcycle() + end - assert(machine:read_mcycle() == 1000, "machine mcycle should be 1000") + assert(machine:read_mcycle() == 1000, "machine mcycle should be 1000") - local root_hash = machine:get_root_hash() + local root_hash = machine:get_root_hash() - machine:dump_pmas() - local calculated_root_hash_1000 = test_util.calculate_emulator_hash(test_path, - pmas_file_names, machine) - -- Remove dumped pmas files - remove_files(pmas_file_names) + machine:dump_pmas() + local calculated_root_hash_1000 = test_util.calculate_emulator_hash(test_path, pmas_file_names, machine) + -- Remove dumped pmas files + remove_files(pmas_file_names) - print("1000 cycle hash: ", test_util.tohex(root_hash)) - assert(test_util.tohex(root_hash) == - test_util.tohex(calculated_root_hash_1000), - "machine hash does not match after 1000 cycles") - end -) + print("1000 cycle hash: ", test_util.tohex(root_hash)) + assert( + test_util.tohex(root_hash) == test_util.tohex(calculated_root_hash_1000), + "machine hash does not match after 1000 cycles" + ) +end) print("\n\nrun machine to end mcycle and check for mcycle, hash and halt flag") -do_test("mcycle and root hash should match", - function(machine) - machine:run(MAX_MCYCLE) - -- Check machine is halted - assert(machine:read_iflags_H(), "machine should be halted") - -- Check for end mcycle - local end_mcycle = machine:read_mcycle() - assert(end_mcycle > 400000, "machine mcycle should be above 400000") - - local root_hash = machine:get_root_hash() - print("End hash: ", test_util.tohex(root_hash)) - - machine:dump_pmas() - local calculated_end_hash = test_util.calculate_emulator_hash(test_path, - pmas_file_names, machine) - -- Remove dumped pmas files - remove_files(pmas_file_names) - - assert(test_util.tohex(root_hash) == test_util.tohex(calculated_end_hash), - "machine hash does not match after on end cycle") - end -) +do_test("mcycle and root hash should match", function(machine) + machine:run(MAX_MCYCLE) + -- Check machine is halted + assert(machine:read_iflags_H(), "machine should be halted") + -- Check for end mcycle + local end_mcycle = machine:read_mcycle() + assert(end_mcycle > 400000, "machine mcycle should be above 400000") + + local root_hash = machine:get_root_hash() + print("End hash: ", test_util.tohex(root_hash)) + + machine:dump_pmas() + local calculated_end_hash = test_util.calculate_emulator_hash(test_path, pmas_file_names, machine) + -- Remove dumped pmas files + remove_files(pmas_file_names) + + assert( + test_util.tohex(root_hash) == test_util.tohex(calculated_end_hash), + "machine hash does not match after on end cycle" + ) +end) print("\n\nwrite something to ram memory and check if hash and proof matches") -do_test("proof and root hash should match", - function(machine) - local root_hash = machine:get_root_hash() - local ram_address_start = 0x80000000 - - -- Find proof for first KB of ram - local initial_ram_proof = machine:get_proof(ram_address_start, 10) - -- Calculate hash - local initial_memory_read = machine:read_memory(ram_address_start, 2 ^ 10) - local initial_calculated_hash = test_util.calculate_root_hash( - initial_memory_read, 10) - assert(test_util.tohex(initial_ram_proof.target_hash) == - test_util.tohex(initial_calculated_hash), - "initial hash does not match") +do_test("proof and root hash should match", function(machine) + local ram_address_start = 0x80000000 + + -- Find proof for first KB of ram + local initial_ram_proof = machine:get_proof(ram_address_start, 10) + -- Calculate hash + local initial_memory_read = machine:read_memory(ram_address_start, 2 ^ 10) + local initial_calculated_hash = test_util.calculate_root_hash(initial_memory_read, 10) + assert( + test_util.tohex(initial_ram_proof.target_hash) == test_util.tohex(initial_calculated_hash), + "initial hash does not match" + ) - print("initial target hash:", - test_util.tohex(initial_ram_proof.target_hash), - " calculated initial target hash:", - test_util.tohex(initial_calculated_hash)) + print( + "initial target hash:", + test_util.tohex(initial_ram_proof.target_hash), + " calculated initial target hash:", + test_util.tohex(initial_calculated_hash) + ) - machine:write_memory(0x8000000F, "mydataol12345678", 0x10) + machine:write_memory(0x8000000F, "mydataol12345678", 0x10) - local root_hash_step1 = machine:get_root_hash() - local verify = machine:verify_merkle_tree() + local verify = machine:verify_merkle_tree() + assert(verify, "verify merkle tree failed") - -- Find proof for first KB of ram - local ram_proof = machine:get_proof(ram_address_start, 10) - -- Calculate hash - local memory_read = machine:read_memory(ram_address_start, 2 ^ 10) - local calculated_hash = test_util.calculate_root_hash(memory_read, 10) + -- Find proof for first KB of ram + local ram_proof = machine:get_proof(ram_address_start, 10) + -- Calculate hash + local memory_read = machine:read_memory(ram_address_start, 2 ^ 10) + local calculated_hash = test_util.calculate_root_hash(memory_read, 10) - print("end target hash:", test_util.tohex(ram_proof.target_hash), - " calculated end target hash:", test_util.tohex(calculated_hash)) + print( + "end target hash:", + test_util.tohex(ram_proof.target_hash), + " calculated end target hash:", + test_util.tohex(calculated_hash) + ) - assert(test_util.tohex(initial_ram_proof.target_hash) ~= - test_util.tohex(ram_proof.target_hash), - "hash is same after memory is written") + assert( + test_util.tohex(initial_ram_proof.target_hash) ~= test_util.tohex(ram_proof.target_hash), + "hash is same after memory is written" + ) - assert(test_util.tohex(initial_calculated_hash) ~= - test_util.tohex(calculated_hash), - "calculated hash is same after memory is written") + assert( + test_util.tohex(initial_calculated_hash) ~= test_util.tohex(calculated_hash), + "calculated hash is same after memory is written" + ) - assert(test_util.tohex(ram_proof.target_hash) == - test_util.tohex(calculated_hash), - "hash does not match after memory is written") - end -) + assert( + test_util.tohex(ram_proof.target_hash) == test_util.tohex(calculated_hash), + "hash does not match after memory is written" + ) +end) print("\n\n check dirty page maps") -do_test("dirty page maps should be consistent", - function(machine) - -- Verify dirty page maps - assert(machine:verify_dirty_page_maps(), "error verifying dirty page maps") - end -) +do_test("dirty page maps should be consistent", function(machine) + -- Verify dirty page maps + assert(machine:verify_dirty_page_maps(), "error verifying dirty page maps") +end) print("\n\n check replace flash drives") test_util.make_do_test(build_machine, machine_type, { processor = {}, - ram = {length = 1 << 20}, - rom = {image_filename = rom_image}, - flash_drive = {{ - start = 0x80000000000000, + ram = { length = 1 << 20 }, + rom = { image_filename = rom_image }, + flash_drive = { + { + start = 0x80000000000000, + shared = false, + image_filename = rootfs_image, + }, + }, +})("should replace flash drive and read something", function(machine) + local rootfs_length = machine:get_initial_config().flash_drive[1].length + -- Create temp flash file + local input_path = test_path .. "input.raw" + local command = "echo 'test data 1234567890' > " + .. input_path + .. " && truncate -s " + .. tostring(rootfs_length) + .. " " + .. input_path + local p = io.popen(command) + p:close() + + local flash_address_start = 0x80000000000000 + local flash_drive_config = { + start = flash_address_start, length = rootfs_length, - shared = false, - image_filename = rootfs_image - }} -})("should replace flash drive and read something", - function(machine) - -- Create temp flash file - local input_path = test_path .. "input.raw" - local command = "echo 'test data 1234567890' > " .. input_path .. - " && truncate -s " .. tostring(rootfs_length) .. " " .. input_path - local p = io.popen(command) - p:close() - - local flash_address_start = 0x80000000000000 - local flash_drive_config = { - start = flash_address_start, - length = rootfs_length, - image_filename = input_path, - shared = true - } + image_filename = input_path, + shared = true, + } - machine:read_memory(flash_address_start, 20) + machine:read_memory(flash_address_start, 20) - machine:replace_memory_range(flash_drive_config) + machine:replace_memory_range(flash_drive_config) - local flash_data = machine:read_memory(flash_address_start, 20) - assert(flash_data == "test data 1234567890", "data read from replaced flash failed") - os.remove(input_path) - end -) + local flash_data = machine:read_memory(flash_address_start, 20) + assert(flash_data == "test data 1234567890", "data read from replaced flash failed") + os.remove(input_path) +end) print("\n\n check reading from an input and writing to an output flash drive") test_util.make_do_test(build_machine, machine_type, { @@ -521,63 +513,58 @@ test_util.make_do_test(build_machine, machine_type, { }, rom = { image_filename = rom_image, - bootargs = - "console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw quiet swiotlb=noforce single=yes splash=no ".. - "mtdparts=flash.0:-(root);flash.1:-(input);flash.2:-(output) -- ".. - "cat /mnt/input/etc/issue | dd status=none of=/dev/mtdblock2", + bootargs = "console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw quiet swiotlb=noforce single=yes splash=no " + .. "mtdparts=flash.0:-(root);flash.1:-(input);flash.2:-(output) -- " + .. "cat /mnt/input/etc/issue | dd status=none of=/dev/mtdblock2", }, - flash_drive = {{ - start = 0x80000000000000, - length = rootfs_length, - image_filename = rootfs_image - }, { - start = 0x90000000000000, - length = rootfs_length, - image_filename = rootfs_image - }, { - start = 0xa0000000000000, - length = 4096, - }} -})("should boot mount input flash drive and output to another flash drive", - function(machine) - machine:run(MAX_MCYCLE) - assert(machine:read_iflags_H(), "machine should be halted") + flash_drive = { + { + start = 0x80000000000000, + image_filename = rootfs_image, + }, + { + start = 0x90000000000000, + image_filename = rootfs_image, + }, + { + start = 0xa0000000000000, + length = 4096, + }, + }, +})("should boot mount input flash drive and output to another flash drive", function(machine) + machine:run(MAX_MCYCLE) + assert(machine:read_iflags_H(), "machine should be halted") - local expected_issue = 'Welcome to Cartesi' - local flash_data = machine:read_memory(0xa0000000000000, #expected_issue) - assert(flash_data == expected_issue, 'unexpected flash drive output') - end -) + local expected_issue = "Welcome to Cartesi" + local flash_data = machine:read_memory(0xa0000000000000, #expected_issue) + assert(flash_data == expected_issue, "unexpected flash drive output") +end) print("\n\n check for relevant register values after step 1") -test_util.make_do_test(build_uarch_machine, machine_type)("register values should match", - function(machine) - local uarch_pc_before = machine:read_uarch_pc() - local uarch_cycle_before = machine:read_uarch_cycle() +test_util.make_do_test(build_uarch_machine, machine_type)("register values should match", function(machine) + local uarch_pc_before = machine:read_uarch_pc() + local uarch_cycle_before = machine:read_uarch_cycle() - local log_type = {} - machine:step_uarch(log_type) + local log_type = {} + machine:step_uarch(log_type) - local uarch_pc_after = machine:read_uarch_pc() - local uarch_cycle_after = machine:read_uarch_cycle() + local uarch_pc_after = machine:read_uarch_pc() + local uarch_cycle_after = machine:read_uarch_cycle() - assert(uarch_pc_before + 4 == uarch_pc_after, "wrong uarch_pc value") - assert(uarch_cycle_before + 1 == uarch_cycle_after, "wrong uarch_cycle value") - end -) + assert(uarch_pc_before + 4 == uarch_pc_after, "wrong uarch_pc value") + assert(uarch_cycle_before + 1 == uarch_cycle_after, "wrong uarch_cycle value") +end) if machine_type ~= "local" then print("\n\n check remote get_machine") - do_test("get_machine should get reference to working machine", - function(machine) - local machine_2 = remote.get_machine() - assert(machine:get_root_hash() == machine_2:get_root_hash()) - machine_2:destroy() - local ret, err = pcall(function() machine:get_root_hash() end) - assert(ret == false) - assert(err:match("no machine")) - end - ) + do_test("get_machine should get reference to working machine", function(machine) + local machine_2 = remote.get_machine() + assert(machine:get_root_hash() == machine_2:get_root_hash()) + machine_2:destroy() + local ret, err = pcall(function() machine:get_root_hash() end) + assert(ret == false) + assert(err:match("no machine")) + end) end print("\n\nAll tests of machine lua API for type " .. machine_type .. " passed") diff --git a/src/tests/mcycle-overflow.lua b/src/tests/mcycle-overflow.lua index b1f4cccbb..dae585051 100755 --- a/src/tests/mcycle-overflow.lua +++ b/src/tests/mcycle-overflow.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -16,34 +16,34 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require"cartesi" -local test_util = require "tests.util" +local cartesi = require("cartesi") +local test_util = require("tests.util") -- There is no UINT64_MAX in Lua, so we have to use the signed representation local MAX_MCYCLE = -1 local MAX_UARCH_CYCLE = -1 local function build_machine() - local config = { + local config = { processor = { -- Request automatic default values for versioning CSRs mimpid = -1, marchid = -1, - mvendorid = -1 + mvendorid = -1, }, rom = { - image_filename = test_util.tests_path .. "bootstrap.bin" + image_filename = test_util.tests_path .. "bootstrap.bin", }, ram = { image_filename = test_util.tests_path .. "mcycle_overflow.bin", length = 32 << 20, }, - uarch = { - ram = { + uarch = { + ram = { length = 1 << 20, - image_filename = test_util.create_test_uarch_program() - } - } + image_filename = test_util.create_test_uarch_program(), + }, + }, } local machine = cartesi.machine(config) os.remove(config.uarch.ram.image_filename) @@ -56,9 +56,8 @@ end local function do_test(description, f) io.write(" " .. description .. "...") - local machine = build_machine() + local machine = build_machine() f(machine) - machine:destroy() print(" passed") end @@ -81,16 +80,15 @@ do_test("machine run shouldn't change state in max mcycle", function(machine) assert(hash_before == hash_after) end) -for _, proofs in ipairs{true, false} do - do_test("machine step should do nothing on max mcycle [proofs=" .. - tostring(proofs) .. "]", function(machine) +for _, proofs in ipairs({ true, false }) do + do_test("machine step should do nothing on max mcycle [proofs=" .. tostring(proofs) .. "]", function(machine) machine:write_uarch_cycle(MAX_UARCH_CYCLE) - local log = machine:step_uarch{proofs=proofs} + local log = machine:step_uarch({ proofs = proofs }) assert(machine:read_uarch_cycle() == MAX_UARCH_CYCLE) assert(#log.accesses == 1) assert(log.accesses[1].type == "read") assert(log.accesses[1].address == 0x320) -- address of uarch_cycle in shadow - assert(log.accesses[1].read == string.pack('J', MAX_UARCH_CYCLE)) + assert(log.accesses[1].read == string.pack("J", MAX_UARCH_CYCLE)) assert((log.accesses[1].proof ~= nil) == proofs) end) end diff --git a/src/tests/mtime-interrupt.lua b/src/tests/mtime-interrupt.lua index bed06041d..f78aaa78c 100755 --- a/src/tests/mtime-interrupt.lua +++ b/src/tests/mtime-interrupt.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2021 Cartesi Pte. Ltd. -- @@ -16,13 +16,13 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require"cartesi" -local test_util = require "tests.util" +local cartesi = require("cartesi") +local test_util = require("tests.util") local function build_machine() local machine_config = { rom = { - image_filename = test_util.tests_path .. "bootstrap.bin" + image_filename = test_util.tests_path .. "bootstrap.bin", }, ram = { image_filename = test_util.tests_path .. "mtime_interrupt.bin", @@ -34,25 +34,24 @@ end local function do_test(description, f) io.write(" " .. description .. "...") - local machine = build_machine() + local machine = build_machine() f(machine) - machine:destroy() print(" passed") end local RTC_FREQ_DIV = 8192 -local EXPECTED_MCYCLE = RTC_FREQ_DIV*2 + 20 +local EXPECTED_MCYCLE = RTC_FREQ_DIV * 2 + 20 local function check_state(machine) assert(machine:read_iflags_H(), "machine did not halt") - assert(machine:read_htif_tohost_data()>>1 == 0, "invalid return code") + assert(machine:read_htif_tohost_data() >> 1 == 0, "invalid return code") assert(machine:read_mcycle() == EXPECTED_MCYCLE, "invalid mcycle") end print("testing mtime interrupt") do_test("machine:run should interrupt for mtime", function(machine) - for i = 1, EXPECTED_MCYCLE do + for _ = 1, EXPECTED_MCYCLE do machine:run(-1) if machine:read_iflags_H() then break end end @@ -60,8 +59,8 @@ do_test("machine:run should interrupt for mtime", function(machine) end) test_util.disabled_test("machine:step_uarch should interrupt for mtime", function(machine) - for i = 1, EXPECTED_MCYCLE do - machine:step_uarch{} + for _ = 1, EXPECTED_MCYCLE do + machine:step_uarch({}) if machine:read_iflags_H() then break end end check_state(machine) diff --git a/src/tests/test-grpc-server.sh b/src/tests/test-grpc-server.sh index d68b4886a..9ad12d42b 100755 --- a/src/tests/test-grpc-server.sh +++ b/src/tests/test-grpc-server.sh @@ -20,7 +20,7 @@ remote_cartesi_machine=$1 cartesi_machine=$2 cartesi_machine_tests=$3 test_path=$4 -lua=${5:-lua5.3} +lua=${5:-lua5.4} server_address=127.0.0.1:5001 checkin_address=127.0.0.1:5002 diff --git a/src/tests/test-jsonrpc-server.sh b/src/tests/test-jsonrpc-server.sh index 6f9436bf9..cfcb13a4d 100755 --- a/src/tests/test-jsonrpc-server.sh +++ b/src/tests/test-jsonrpc-server.sh @@ -20,7 +20,7 @@ remote_cartesi_machine=$1 cartesi_machine=$2 cartesi_machine_tests=$3 test_path=$4 -lua=${5:-lua5.3} +lua=${5:-lua5.4} server_address=127.0.0.1:5001 diff --git a/src/tests/util.lua b/src/tests/util.lua index c4173b051..ad05c5e92 100644 --- a/src/tests/util.lua +++ b/src/tests/util.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2021 Cartesi Pte. Ltd. -- @@ -16,7 +16,7 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require "cartesi" +local cartesi = require("cartesi") local zero_keccak_hash_table = { "", @@ -81,7 +81,7 @@ local zero_keccak_hash_table = { "6d1ab973982c7ccbe6c1fae02788e4422ae22282fa49cbdb04ba54a7a238c6fc", -- 60 "41187451383460762c06d1c8a72b9cd718866ad4b689e10c9a8c38fe5ef045bd", -- 61 "785b01e980fc82c7e3532ce81876b778dd9f1ceeba4478e86411fb6fdd790683", -- 62 - "916ca832592485093644e8760cd7b4c01dba1ccc82b661bf13f0e3f34acd6b88" -- 63 + "916ca832592485093644e8760cd7b4c01dba1ccc82b661bf13f0e3f34acd6b88", -- 63 } local function adjust_images_path(path) @@ -95,16 +95,16 @@ local test_util = { m_page_log2_size = 0, m_tree_log2_size = 0, m_page_count = 0, - m_max_pages = 0 + m_max_pages = 0, }, - hash = {LOG2_WORD_SIZE = 3}, - images_path = adjust_images_path(os.getenv('CARTESI_IMAGES_PATH')), - tests_path = adjust_images_path(os.getenv("CARTESI_TESTS_PATH")) + hash = { LOG2_WORD_SIZE = 3 }, + images_path = adjust_images_path(os.getenv("CARTESI_IMAGES_PATH")), + tests_path = adjust_images_path(os.getenv("CARTESI_TESTS_PATH")), } -function test_util.create_test_uarch_program() +function test_util.create_test_uarch_program() local file_path = os.tmpname() - local f = io.open(file_path, 'wb') + local f = io.open(file_path, "wb") f:write(string.pack("I4", 0x07b00513)) -- li a0,123 f:write(string.pack("I4", 0x32800293)) -- li t0, UARCH_HALT_FLAG_SHADDOW_ADDR_DEF (0x328) f:write(string.pack("I4", 0x00100313)) -- li t1,1 UARCH_MMIO_HALT_VALUE_DEF @@ -116,19 +116,15 @@ end function test_util.make_do_test(build_machine, type, config) return function(description, f) io.write(" " .. description .. "...\n") - local machine = build_machine(type, config) + local machine = build_machine(type, config) f(machine) - machine:destroy() print("<<<<<<<<<<<<<<<< passed >>>>>>>>>>>>>>>") end end -function test_util.disabled_test(description, f) - print("Disabled test - "..description) -end +function test_util.disabled_test(description) print("Disabled test - " .. description) end -function test_util.incremental_merkle_tree_of_pages:new(o, page_log2_size, - tree_log2_size) +function test_util.incremental_merkle_tree_of_pages:new(o, page_log2_size, tree_log2_size) o = o or {} setmetatable(o, self) self.__index = self @@ -142,11 +138,10 @@ end function test_util.incremental_merkle_tree_of_pages:add_page(new_page_hash) local right = new_page_hash - assert(self.m_page_count < self.m_max_pages, - "Page count must be smaller than max pages") + assert(self.m_page_count < self.m_max_pages, "Page count must be smaller than max pages") local depth = self.m_tree_log2_size - self.m_page_log2_size for i = 0, depth do - if (self.m_page_count & (0x01 << i) ~= 0x0) then + if self.m_page_count & (0x01 << i) ~= 0x0 then local left = self.m_context[i] right = cartesi.keccak(left, right) else @@ -158,12 +153,10 @@ function test_util.incremental_merkle_tree_of_pages:add_page(new_page_hash) end function test_util.incremental_merkle_tree_of_pages:get_root_hash() - assert(self.m_page_count <= self.m_max_pages, - "Page count must be smaller or equal than max pages") + assert(self.m_page_count <= self.m_max_pages, "Page count must be smaller or equal than max pages") local depth = self.m_tree_log2_size - self.m_page_log2_size if self.m_page_count < self.m_max_pages then - local root = test_util.fromhex( - zero_keccak_hash_table[self.m_page_log2_size]) + local root = test_util.fromhex(zero_keccak_hash_table[self.m_page_log2_size]) for i = 0, depth - 1 do if (self.m_page_count & (0x01 << i)) ~= 0 then local left = self.m_context[i] @@ -177,7 +170,6 @@ function test_util.incremental_merkle_tree_of_pages:get_root_hash() else return self.m_context[depth] end - end function test_util.file_exists(name) @@ -191,14 +183,11 @@ function test_util.file_exists(name) end function test_util.fromhex(str) - return - (str:gsub('..', function(cc) return string.char(tonumber(cc, 16)) end)) + return (str:gsub("..", function(cc) return string.char(tonumber(cc, 16)) end)) end function test_util.tohex(str) - return (str:gsub('.', function(c) - return string.format('%02X', string.byte(c)) - end)) + return (str:gsub(".", function(c) return string.format("%02X", string.byte(c)) end)) end function test_util.split_string(inputstr, sep) @@ -210,15 +199,15 @@ function test_util.split_string(inputstr, sep) return t end -function test_util.check_proof(proof) +function test_util.check_proof(proof) local hash = proof.target_hash - for log2_size = proof.log2_target_size, proof.log2_root_size-1 do + for log2_size = proof.log2_target_size, proof.log2_root_size - 1 do local bit = (proof.target_address & (1 << log2_size)) ~= 0 local first, second if bit then - first, second = proof.sibling_hashes[proof.log2_root_size-log2_size], hash + first, second = proof.sibling_hashes[proof.log2_root_size - log2_size], hash else - first, second = hash, proof.sibling_hashes[proof.log2_root_size-log2_size] + first, second = hash, proof.sibling_hashes[proof.log2_root_size - log2_size] end hash = cartesi.keccak(first, second) end @@ -227,7 +216,6 @@ end function test_util.align(v, el) return (v >> el << el) end - -- Calculate root hash for data buffer of log2_size function test_util.calculate_root_hash(data, log2_size) if log2_size < test_util.hash.LOG2_WORD_SIZE then @@ -235,11 +223,8 @@ function test_util.calculate_root_hash(data, log2_size) elseif log2_size > test_util.hash.LOG2_WORD_SIZE then log2_size = log2_size - 1 local sz = math.ceil(data:len() / 2) - local child1 = - test_util.calculate_root_hash(data:sub(1, sz), log2_size) - local child2 = test_util.calculate_root_hash(data:sub(sz + 1, - data:len()), - log2_size) + local child1 = test_util.calculate_root_hash(data:sub(1, sz), log2_size) + local child2 = test_util.calculate_root_hash(data:sub(sz + 1, data:len()), log2_size) local hash = cartesi.keccak(child1, child2) return hash else @@ -252,20 +237,14 @@ end -- of page size page_log2_size -- calculate merke hash for region of up to tree_log2_size, -- using zero sibling hashes where needed -function test_util.calculate_region_hash(data_buffer, data_number_of_pages, - page_log2_size, tree_log2_size) - +function test_util.calculate_region_hash(data_buffer, data_number_of_pages, page_log2_size, tree_log2_size) local page_size = 1 << page_log2_size - local incremental_tree = test_util.incremental_merkle_tree_of_pages:new({}, - page_log2_size, - tree_log2_size) + local incremental_tree = test_util.incremental_merkle_tree_of_pages:new({}, page_log2_size, tree_log2_size) for i = 0, data_number_of_pages - 1 do - local current_page_data = data_buffer:sub(i * page_size + 1, - (i + 1) * page_size) - local current_page_hash = test_util.calculate_root_hash( - current_page_data, page_log2_size) + local current_page_data = data_buffer:sub(i * page_size + 1, (i + 1) * page_size) + local current_page_hash = test_util.calculate_root_hash(current_page_data, page_log2_size) incremental_tree:add_page(current_page_hash) end @@ -276,13 +255,11 @@ end -- Take data hash of some region and extend it with pristine space -- up to tree_log2_size, calculating target hash -function test_util.extend_region_hash(data_hash, data_address, data_log2_size, - tree_log2_size) - +function test_util.extend_region_hash(data_hash, data_address, data_log2_size, tree_log2_size) local result_hash = data_hash local result_address = data_address for n = data_log2_size + 1, tree_log2_size do - if result_address & ((1 << n)-1) == 0 then + if result_address & ((1 << n) - 1) == 0 then local child1 = result_hash local child2 = test_util.fromhex(zero_keccak_hash_table[n - 1]) result_hash = cartesi.keccak(child1, child2) @@ -302,19 +279,15 @@ end -- calculate merke hash for region of up to log2_result_address_space, -- using zero sibling hashes where needed. Data_address may not be aligned -- to the beginning of the log2_result_address_space -function test_util.calculate_region_hash_2(data_address, data_buffer, - log2_data_size, - log2_result_address_space) - +function test_util.calculate_region_hash_2(data_address, data_buffer, log2_data_size, log2_result_address_space) data_address = data_address & (~0x01 << (log2_data_size - 1)) - local data_hash = - test_util.calculate_root_hash(data_buffer, log2_data_size) + local data_hash = test_util.calculate_root_hash(data_buffer, log2_data_size) local result_hash = data_hash local result_address = data_address for n = log2_data_size + 1, log2_result_address_space do - if result_address & ((1 << n)-1) == 0 then + if result_address & ((1 << n) - 1) == 0 then local child1 = result_hash local child2 = test_util.fromhex(zero_keccak_hash_table[n - 1]) result_hash = cartesi.keccak(child1, child2) @@ -327,14 +300,11 @@ function test_util.calculate_region_hash_2(data_address, data_buffer, end return result_hash - end function test_util.parse_pma_file(filename) local fd = io.open(filename, "rb") - if not fd then - return "" - end + if not fd then return "" end local data_size = fd:seek("end") fd:seek("set") local data = fd:read(data_size) @@ -356,9 +326,7 @@ local PMA_RAM_START = 0x80000000 local PMA_PAGE_SIZE_LOG2 = 12 local PMA_PAGE_SIZE = 1 << PMA_PAGE_SIZE_LOG2 -local function ceil_log2(x) - return math.ceil(math.log(x, 2)) // 1 -end +local function ceil_log2(x) return math.ceil(math.log(x, 2)) // 1 end local extend_region_hash = test_util.extend_region_hash local calculate_region_hash = test_util.calculate_region_hash @@ -375,25 +343,30 @@ function test_util.calculate_emulator_hash(test_path, pmas_files) local htif = test_util.parse_pma_file(test_path .. pmas_files[6]) local ram = test_util.parse_pma_file(test_path .. pmas_files[7]) local uarch_ram = "" - if pmas_files[8] then - uarch_ram = test_util.parse_pma_file(test_path .. pmas_files[8]) - end + if pmas_files[8] then uarch_ram = test_util.parse_pma_file(test_path .. pmas_files[8]) end local shadow_rom = shadow_state .. rom .. shadow_pmas local shadow_rom_hash_size_log2 = ceil_log2(PMA_SHADOW_STATE_LENGTH + PMA_ROM_LENGTH + PMA_SHADOW_PMAS_LENGTH) - local shadow_rom_space_hash = calculate_region_hash(shadow_rom, - (#shadow_rom + PMA_PAGE_SIZE - 1) // PMA_PAGE_SIZE, PMA_PAGE_SIZE_LOG2, shadow_rom_hash_size_log2) + local shadow_rom_space_hash = calculate_region_hash( + shadow_rom, + (#shadow_rom + PMA_PAGE_SIZE - 1) // PMA_PAGE_SIZE, + PMA_PAGE_SIZE_LOG2, + shadow_rom_hash_size_log2 + ) shadow_rom_space_hash = extend_region_hash(shadow_rom_space_hash, 0, shadow_rom_hash_size_log2, 17) local tlb_size_log2 = ceil_log2(PMA_SHADOW_TLB_LENGTH) - local tlb_space_hash = - calculate_region_hash(shadow_tlb, (#shadow_tlb + PMA_PAGE_SIZE - 1) // PMA_PAGE_SIZE, PMA_PAGE_SIZE_LOG2, tlb_size_log2) + local tlb_space_hash = calculate_region_hash( + shadow_tlb, + (#shadow_tlb + PMA_PAGE_SIZE - 1) // PMA_PAGE_SIZE, + PMA_PAGE_SIZE_LOG2, + tlb_size_log2 + ) tlb_space_hash = extend_region_hash(tlb_space_hash, PMA_SHADOW_TLB_START, tlb_size_log2, 17) local shadow_rom_tlb_space_hash = cartesi.keccak(shadow_rom_space_hash, tlb_space_hash) -- 18 - shadow_rom_tlb_space_hash = - extend_region_hash(shadow_rom_tlb_space_hash, 0, 18, 25) + shadow_rom_tlb_space_hash = extend_region_hash(shadow_rom_tlb_space_hash, 0, 18, 25) local clint_size_log2 = ceil_log2(PMA_CLINT_LENGTH) local clint_space_hash = @@ -409,7 +382,12 @@ function test_util.calculate_emulator_hash(test_path, pmas_files) local uarch_ram_space_hash = test_util.fromhex(zero_keccak_hash_table[30]) if #uarch_ram > 0 then local uarch_ram_size_log2 = ceil_log2(#uarch_ram) - uarch_ram_space_hash = calculate_region_hash(uarch_ram, (#uarch_ram + PMA_PAGE_SIZE - 1) // PMA_PAGE_SIZE, PMA_PAGE_SIZE_LOG2, uarch_ram_size_log2) + uarch_ram_space_hash = calculate_region_hash( + uarch_ram, + (#uarch_ram + PMA_PAGE_SIZE - 1) // PMA_PAGE_SIZE, + PMA_PAGE_SIZE_LOG2, + uarch_ram_size_log2 + ) uarch_ram_space_hash = extend_region_hash(uarch_ram_space_hash, PMA_UARCH_RAM_START, uarch_ram_size_log2, 30) end left = cartesi.keccak(left, uarch_ram_space_hash) -- 31 diff --git a/src/uarch-riscv-tests.lua b/src/uarch-riscv-tests.lua index ebe83e50a..f19883c04 100755 --- a/src/uarch-riscv-tests.lua +++ b/src/uarch-riscv-tests.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -16,69 +16,69 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require"cartesi" -local util = require"cartesi.util" +local cartesi = require("cartesi") +local util = require("cartesi.util") -- Tests Cases -- format {"ram_image_file", number_of_uarch_cycles} -local tests = { - {"rv64ui-uarch-simple.bin", 12}, - {"rv64ui-uarch-add.bin", 441}, - {"rv64ui-uarch-addi.bin", 216}, - {"rv64ui-uarch-addiw.bin", 213}, - {"rv64ui-uarch-addw.bin", 436}, - {"rv64ui-uarch-and.bin", 516}, - {"rv64ui-uarch-andi.bin", 187}, - {"rv64ui-uarch-auipc.bin", 30}, - {"rv64ui-uarch-beq.bin", 262}, - {"rv64ui-uarch-bge.bin", 280}, - {"rv64ui-uarch-bgeu.bin", 370}, - {"rv64ui-uarch-blt.bin", 262}, - {"rv64ui-uarch-bltu.bin", 348}, - {"rv64ui-uarch-bne.bin", 262}, - {"rv64ui-uarch-jal.bin", 26}, - {"rv64ui-uarch-jalr.bin", 86}, - {"rv64ui-uarch-lb.bin", 224}, - {"rv64ui-uarch-lbu.bin", 224}, - {"rv64ui-uarch-lh.bin", 240}, - {"rv64ui-uarch-lhu.bin", 249}, - {"rv64ui-uarch-lw.bin", 254}, - {"rv64ui-uarch-lwu.bin", 288}, - {"rv64ui-uarch-ld.bin", 406}, - {"rv64ui-uarch-lui.bin", 36}, - {"rv64ui-uarch-or.bin", 549}, - {"rv64ui-uarch-ori.bin", 180}, - {"rv64ui-uarch-sb.bin", 425}, - {"rv64ui-uarch-sh.bin", 478}, - {"rv64ui-uarch-sw.bin", 485}, - {"rv64ui-uarch-sd.bin", 597}, - {"rv64ui-uarch-sll.bin", 511}, - {"rv64ui-uarch-slli.bin", 241}, - {"rv64ui-uarch-slliw.bin", 248}, - {"rv64ui-uarch-sllw.bin", 511}, - {"rv64ui-uarch-slt.bin", 430}, - {"rv64ui-uarch-slti.bin", 208}, - {"rv64ui-uarch-sltiu.bin", 208}, - {"rv64ui-uarch-sltu.bin", 447}, - {"rv64ui-uarch-sra.bin", 483}, - {"rv64ui-uarch-srai.bin", 229}, - {"rv64ui-uarch-sraiw.bin", 275}, - {"rv64ui-uarch-sraw.bin", 523}, - {"rv64ui-uarch-srl.bin", 525}, - {"rv64ui-uarch-srli.bin", 250}, - {"rv64ui-uarch-srliw.bin", 257}, - {"rv64ui-uarch-srlw.bin", 517}, - {"rv64ui-uarch-sub.bin", 432}, - {"rv64ui-uarch-subw.bin", 428}, - {"rv64ui-uarch-xor.bin", 544}, - {"rv64ui-uarch-xori.bin", 178}, - {"rv64ui-uarch-fence.bin", 13}, +local riscv_tests = { + { "rv64ui-uarch-simple.bin", 12 }, + { "rv64ui-uarch-add.bin", 441 }, + { "rv64ui-uarch-addi.bin", 216 }, + { "rv64ui-uarch-addiw.bin", 213 }, + { "rv64ui-uarch-addw.bin", 436 }, + { "rv64ui-uarch-and.bin", 516 }, + { "rv64ui-uarch-andi.bin", 187 }, + { "rv64ui-uarch-auipc.bin", 30 }, + { "rv64ui-uarch-beq.bin", 262 }, + { "rv64ui-uarch-bge.bin", 280 }, + { "rv64ui-uarch-bgeu.bin", 370 }, + { "rv64ui-uarch-blt.bin", 262 }, + { "rv64ui-uarch-bltu.bin", 348 }, + { "rv64ui-uarch-bne.bin", 262 }, + { "rv64ui-uarch-jal.bin", 26 }, + { "rv64ui-uarch-jalr.bin", 86 }, + { "rv64ui-uarch-lb.bin", 224 }, + { "rv64ui-uarch-lbu.bin", 224 }, + { "rv64ui-uarch-lh.bin", 240 }, + { "rv64ui-uarch-lhu.bin", 249 }, + { "rv64ui-uarch-lw.bin", 254 }, + { "rv64ui-uarch-lwu.bin", 288 }, + { "rv64ui-uarch-ld.bin", 406 }, + { "rv64ui-uarch-lui.bin", 36 }, + { "rv64ui-uarch-or.bin", 549 }, + { "rv64ui-uarch-ori.bin", 180 }, + { "rv64ui-uarch-sb.bin", 425 }, + { "rv64ui-uarch-sh.bin", 478 }, + { "rv64ui-uarch-sw.bin", 485 }, + { "rv64ui-uarch-sd.bin", 597 }, + { "rv64ui-uarch-sll.bin", 511 }, + { "rv64ui-uarch-slli.bin", 241 }, + { "rv64ui-uarch-slliw.bin", 248 }, + { "rv64ui-uarch-sllw.bin", 511 }, + { "rv64ui-uarch-slt.bin", 430 }, + { "rv64ui-uarch-slti.bin", 208 }, + { "rv64ui-uarch-sltiu.bin", 208 }, + { "rv64ui-uarch-sltu.bin", 447 }, + { "rv64ui-uarch-sra.bin", 483 }, + { "rv64ui-uarch-srai.bin", 229 }, + { "rv64ui-uarch-sraiw.bin", 275 }, + { "rv64ui-uarch-sraw.bin", 523 }, + { "rv64ui-uarch-srl.bin", 525 }, + { "rv64ui-uarch-srli.bin", 250 }, + { "rv64ui-uarch-srliw.bin", 257 }, + { "rv64ui-uarch-srlw.bin", 517 }, + { "rv64ui-uarch-sub.bin", 432 }, + { "rv64ui-uarch-subw.bin", 428 }, + { "rv64ui-uarch-xor.bin", 544 }, + { "rv64ui-uarch-xori.bin", 178 }, + { "rv64ui-uarch-fence.bin", 13 }, } - -- Print help and exit local function help() - io.stderr:write(string.format([=[ + io.stderr:write(string.format( + [=[ Usage: %s [options] where options are: @@ -91,96 +91,107 @@ where options are: --output-dir= write json logs to this directory required by the command json-logs - + and command can be: run run test and report errors - + list list tests selected by the test json-logs generate json log files, used by by Solidity unit tests, to the directory specified by --output-dir -]=], arg[0])) +]=], + arg[0] + )) os.exit() end local test_path = "./" local test_pattern = ".*" -local output_dir = nil -local cleanup = {} +local output_dir local options = { - { "^%-%-h$", function(all) - if not all then return false end - help() - end }, - { "^%-%-help$", function(all) - if not all then return false end - help() - end }, - { "^%-%-output%-dir%=(.*)$", function(o) - if not o or #o < 1 then return false end - output_dir = o - return true - end }, - { "^%-%-test%-path%=(.*)$", function(o) - if not o or #o < 1 then return false end - test_path = o - return true - end }, - { "^%-%-test%=(.*)$", function(o, a) - if not o or #o < 1 then return false end - test_pattern = o - return true - end }, - { ".*", function(all) - error("unrecognized option " .. all) - end } + { + "^%-%-h$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-help$", + function(all) + if not all then return false end + help() + end, + }, + { + "^%-%-output%-dir%=(.*)$", + function(o) + if not o or #o < 1 then return false end + output_dir = o + return true + end, + }, + { + "^%-%-test%-path%=(.*)$", + function(o) + if not o or #o < 1 then return false end + test_path = o + return true + end, + }, + { + "^%-%-test%=(.*)$", + function(o) + if not o or #o < 1 then return false end + test_pattern = o + return true + end, + }, + { + ".*", + function(all) error("unrecognized option " .. all) end, + }, } local values = {} -- Process command line options -for i, argument in ipairs({...}) do - if argument:sub(1,1) == "-" then - for j, option in ipairs(options) do - if option[2](argument:match(option[1])) then - break - end +for _, argument in ipairs({ ... }) do + if argument:sub(1, 1) == "-" then + for _, option in ipairs(options) do + if option[2](argument:match(option[1])) then break end end else - values[#values+1] = argument + values[#values + 1] = argument end end local command = assert(values[1], "missing command") assert(test_path, "missing test path") -local function nothing() -end - local function build_machine(test_name) local config = { rom = { - image_filename = test_path .. "/bootstrap.bin" + image_filename = test_path .. "/bootstrap.bin", }, ram = { - length = 0x20000 + length = 0x20000, }, uarch = { ram = { image_filename = test_path .. "/" .. test_name, - length = 0x20000 - } - } + length = 0x20000, + }, + }, } - local runtime = { } + local runtime = {} return assert(cartesi.machine(config, runtime)) end - local function add_error(errors, ram_image, msg, ...) local e = string.format(msg, ...) if not errors[ram_image] then errors[ram_image] = {} end @@ -188,10 +199,10 @@ local function add_error(errors, ram_image, msg, ...) ram_image_errors[#ram_image_errors + 1] = e end -local TEST_STATUS_X = 1 -- When test finishes executing, the value of this register contains the test result code -local FAILED_TEST_CASE_X = 3 -- If test fails, the value of this register contains the failed test case -local TEST_SUCEEDED = 0xbe1e7aaa -- Value indicating that test has passed -local TEST_FAILED = 0xdeadbeef -- Value indicating that test has failed +local TEST_STATUS_X = 1 -- When test finishes executing,the value of this register contains the test result code +local FAILED_TEST_CASE_X = 3 -- If test fails, the value of this register contains the failed test case +local TEST_SUCEEDED = 0xbe1e7aaa -- Value indicating that test has passed +local TEST_FAILED = 0xdeadbeef -- Value indicating that test has failed local function check_test_result(machine, ctx, errors) local test_status = machine:read_uarch_x(TEST_STATUS_X) @@ -218,9 +229,9 @@ local function run(tests) ram_image = test[1], expected_cycles = test[2], failed = false, - cycles = 0 + cycles = 0, } - local machine = build_machine(ctx.ram_image) + local machine = build_machine(ctx.ram_image) io.write(ctx.ram_image, ": ") machine:run_uarch(2 * ctx.expected_cycles) check_test_result(machine, ctx, errors) @@ -230,14 +241,13 @@ local function run(tests) else print("passed") end - machine:destroy() end if error_count > 0 then io.write(string.format("\nFAILED %d of %d tests:\n\n", error_count, #tests)) for k, v in pairs(errors) do - for _, e in ipairs(v) do - io.write(string.format("\t%s: %s\n", k, e)) - end + for _, e in ipairs(v) do + io.write(string.format("\t%s: %s\n", k, e)) + end end os.exit(1, true) else @@ -252,86 +262,83 @@ local function list(tests) end end -local function select(test_name, test_pattern) - local i, j = test_name:find(test_pattern) +local function select_test(test_name, patt) + local i, j = test_name:find(patt) if i == 1 and j == #test_name then return true end - i, j = test_name:find(test_pattern, 1, true) + i, j = test_name:find(patt, 1, true) return i == 1 and j == #test_name end -local function make_json_log_file_name(test_name, suffix) - return test_name .. (suffix or "") .. ".json" -end +local function make_json_log_file_name(test_name, suffix) return test_name .. (suffix or "") .. ".json" end local function create_json_log_file(test_name, suffix) - local file_path = output_dir .. "/" .. make_json_log_file_name(test_name, suffix) + local file_path = output_dir .. "/" .. make_json_log_file_name(test_name, suffix) return assert(io.open(file_path, "w"), "error opening file " .. file_path) end -local function open_steps_json_log(test_name, indent) - local f = create_json_log_file(test_name, "-steps") - return f -end +local function open_steps_json_log(test_name) return create_json_log_file(test_name, "-steps") end local function write_access_to_log(access, out, indent, last) - util.indentout(out, indent, '{\n') - util.indentout(out, indent+1, '"type": "%s",\n', access.type) - util.indentout(out, indent+1, '"address": %u,\n', access.address) + util.indentout(out, indent, "{\n") + util.indentout(out, indent + 1, '"type": "%s",\n', access.type) + util.indentout(out, indent + 1, '"address": %u,\n', access.address) if access.type == "write" then - util.indentout(out, indent+1, '"value": "%s"\n', util.hexstring(access.written)) + util.indentout(out, indent + 1, '"value": "%s"\n', util.hexstring(access.written)) else - util.indentout(out, indent+1, '"value": "%s"\n', util.hexstring(access.read)) + util.indentout(out, indent + 1, '"value": "%s"\n', util.hexstring(access.read)) end - util.indentout(out, indent, '}') - if not last then out:write(',') end - out:write('\n') + util.indentout(out, indent, "}") + if not last then out:write(",") end + out:write("\n") end local function write_step_to_log(log, out, indent, last) local n = #log.accesses - util.indentout(out, indent, '{\n') - util.indentout(out, indent+1, '"accesses": [\n') + util.indentout(out, indent, "{\n") + util.indentout(out, indent + 1, '"accesses": [\n') for i, access in ipairs(log.accesses) do - write_access_to_log(access, out, indent+2, i==n) + write_access_to_log(access, out, indent + 2, i == n) end - util.indentout(out, indent+1, ']\n') - util.indentout(out, indent, '}') - if not last then out:write(',') end - out:write('\n') + util.indentout(out, indent + 1, "]\n") + util.indentout(out, indent, "}") + if not last then out:write(",") end + out:write("\n") end local function create_catalog_json_log(contexts) local out = create_json_log_file("catalog") - util.indentout(out, 0, '[\n') + util.indentout(out, 0, "[\n") local n = #contexts for i, ctx in ipairs(contexts) do - local path = make_json_log_file_name(ctx.test_name, "-steps") + local path = make_json_log_file_name(ctx.test_name, "-steps") util.indentout(out, 1, '{"path": "%s", "steps": %d}', path, ctx.step_count) - if i < n then out:write(',\n') else out:write('\n') end + if i < n then + out:write(",\n") + else + out:write("\n") + end end - util.indentout(out, 0, ']\n') + util.indentout(out, 0, "]\n") out:close() end -local function run_machine_writing_json_logs(machine, ctx) +local function run_machine_writing_json_logs(machine, ctx) local test_name = ctx.test_name local max_cycle = ctx.expected_cycles * 2 local out = open_steps_json_log(test_name) local indent = 0 util.indentout(out, indent, '{ "steps":[\n') local step_count = 0 - while math.ult(machine:read_uarch_cycle(), max_cycle) do + while math.ult(machine:read_uarch_cycle(), max_cycle) do local log_type = {} -- no proofs, no annotations local log = machine:step_uarch(log_type) step_count = step_count + 1 local halted = machine:read_uarch_halt_flag() - write_step_to_log(log, out, indent+1, halted) - if halted then - break - end + write_step_to_log(log, out, indent + 1, halted) + if halted then break end end ctx.step_count = step_count - util.indentout(out, indent, '] }\n') + util.indentout(out, indent, "] }\n") out:close() end @@ -342,14 +349,14 @@ local function json_logs(tests) for _, test in ipairs(tests) do local ctx = { ram_image = test[1], - test_name = test[1]:gsub(".bin$",""), + test_name = test[1]:gsub(".bin$", ""), expected_cycles = test[2], failed = false, step_count = 0, - accesses_count = 0 + accesses_count = 0, } - contexts[#contexts+1] = ctx - local machine = build_machine(ctx.ram_image) + contexts[#contexts + 1] = ctx + local machine = build_machine(ctx.ram_image) io.write(ctx.ram_image, ": ") run_machine_writing_json_logs(machine, ctx) check_test_result(machine, ctx, errors) @@ -359,14 +366,13 @@ local function json_logs(tests) else print("passed") end - machine:destroy() end if error_count > 0 then io.write(string.format("\nFAILED %d of %d tests:\n\n", error_count, #tests)) for k, v in pairs(errors) do - for _, e in ipairs(v) do - io.write(string.format("\t%s: %s\n", k, e)) - end + for _, e in ipairs(v) do + io.write(string.format("\t%s: %s\n", k, e)) + end end os.exit(1, true) else @@ -374,18 +380,21 @@ local function json_logs(tests) create_catalog_json_log(contexts) os.exit(0, true) end - end local selected_tests = {} -for _, test in ipairs(tests) do - if select(test[1], test_pattern) then - selected_tests[#selected_tests+1] = test - end +for _, test in ipairs(riscv_tests) do + if select_test(test[1], test_pattern) then selected_tests[#selected_tests + 1] = test end end -if #selected_tests < 1 then error("no test selected") -elseif command == "run" then run(selected_tests) -elseif command == "list" then list(selected_tests) -elseif command == "json-logs" then json_logs(selected_tests) -else error("command not found") end +if #selected_tests < 1 then + error("no test selected") +elseif command == "run" then + run(selected_tests) +elseif command == "list" then + list(selected_tests) +elseif command == "json-logs" then + json_logs(selected_tests) +else + error("command not found") +end diff --git a/third-party/riscv-arch-tests/Makefile b/third-party/riscv-arch-tests/Makefile index 3c6eee953..df9548b69 100644 --- a/third-party/riscv-arch-tests/Makefile +++ b/third-party/riscv-arch-tests/Makefile @@ -9,7 +9,7 @@ SRCDIR = $(abspath src) DEPDIR = third-party RISCV_ARCH_TEST = $(DEPDIR)/$(RISCV_ARCH_TEST_PROJ)-$(RISCV_ARCHTEST_RELEASE) TEST_BOOTSTRAP = $(abspath src/bootstrap.bin) -LUA = lua5.3 +LUA = lua5.4 RUN_ARCH_TEST = $(LUA) $(abspath src/run-rv64i-arch-test.lua) # Arguments required by the riscv arch test suite diff --git a/third-party/riscv-arch-tests/src/run-rv64i-arch-test.lua b/third-party/riscv-arch-tests/src/run-rv64i-arch-test.lua index da2361333..7dba7de94 100755 --- a/third-party/riscv-arch-tests/src/run-rv64i-arch-test.lua +++ b/third-party/riscv-arch-tests/src/run-rv64i-arch-test.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019-2021 Cartesi Pte. Ltd. -- @@ -16,10 +16,11 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local cartesi = require "cartesi" +local cartesi = require("cartesi") -if #arg ~= 2 then - io.stderr:write(string.format([=[ +if #arg ~= 2 then + io.stderr:write(string.format( + [=[ Usage: %s @@ -30,57 +31,58 @@ where: name of file to write test signature results - ]=], arg[0])) - os.exit(1) + ]=], + arg[0] + )) + os.exit(1) end local uarch_ram_image_filename = arg[1] local output_signature_file = arg[2] -local uarch_ram_start = 0x70000000 +local uarch_ram_start = 0x70000000 local uarch_ram_length = 0x1000000 local dummy_rom_filename = os.tmpname() -io.open(dummy_rom_filename, 'w'):close() -local deleter = setmetatable({}, { __gc = function() os.remove(dummy_rom_filename) end } ) +io.open(dummy_rom_filename, "w"):close() +local deleter = {} +setmetatable(deleter, { + __gc = function() os.remove(dummy_rom_filename) end, +}) local config = { - uarch ={ - ram = { - image_filename = uarch_ram_image_filename, - length = uarch_ram_length + uarch = { + ram = { + image_filename = uarch_ram_image_filename, + length = uarch_ram_length, + }, }, - }, - processor = {}, - rom = { image_filename = dummy_rom_filename }, - ram = { length = 0x1000 } + processor = {}, + rom = { image_filename = dummy_rom_filename }, + ram = { length = 0x1000 }, } -local runtime_config = {} - -local machine = assert(cartesi.machine(config, runtime)) +local machine = assert(cartesi.machine(config)) -- run microarchitecture machine:run_uarch() --- extract test result signature from microarchitecture RAM +-- extract test result signature from microarchitecture RAM local mem = machine:read_memory(uarch_ram_start, uarch_ram_length) local s1, e1 = string.find(mem, "BEGIN_CTSI_SIGNATURE____") local s2, e2 = string.find(mem, "END_CTSI_SIGNATURE______") -local sig = string.sub(mem, e1+1, s2-1) +local sig = string.sub(mem, e1 + 1, s2 - 1) -- write signature to file, in the format expected by the arch test script local fd = io.open(output_signature_file, "w") -for i=1, #sig, 4 do - local w = string.reverse(string.sub(sig, i, i+3)) - for j=1,4,1 do - local b = string.byte(string.sub(w, j, h)) - fd:write(string.format("%02x", b)) - end - fd:write("\n") +for i = 1, #sig, 4 do + local w = string.reverse(string.sub(sig, i, i + 3)) + for j = 1, 4, 1 do + local b = string.byte(string.sub(w, j)) + fd:write(string.format("%02x", b)) + end + fd:write("\n") end -- pad fill output file -if (#sig % 16) ~= 0 then - fd:write("00000000\n00000000\n") -end +if (#sig % 16) ~= 0 then fd:write("00000000\n00000000\n") end -fd:close(sig_file) +fd:close() diff --git a/tools/benchmarks/run-benchmarks.lua b/tools/benchmarks/run-benchmarks.lua index c2328fa6f..2b8af9bc7 100755 --- a/tools/benchmarks/run-benchmarks.lua +++ b/tools/benchmarks/run-benchmarks.lua @@ -1,4 +1,4 @@ -#!/usr/bin/env lua5.3 +#!/usr/bin/env lua5.4 -- Copyright 2019 Cartesi Pte. Ltd. -- @@ -16,8 +16,8 @@ -- along with the machine-emulator. If not, see http://www.gnu.org/licenses/. -- -local socket = require"socket" -local cartesi = require"cartesi" +local socket = require("socket") +local cartesi = require("cartesi") -- Number of times each benchmark is measured local N_RUNS = 5 @@ -38,42 +38,42 @@ local benchmarks = { { name = "boot", exec_format = "exit %s", - params = {0} + params = { 0 }, }, { name = "dhrystone", exec_format = "echo %d | /usr/bin/dhrystone", - params = {100000, 500000, 1000000} + params = { 100000, 500000, 1000000 }, }, { name = "whetstone", exec_format = "/usr/bin/whetstone %d", - params = {100, 250, 500} + params = { 100, 250, 500 }, }, { name = "tinymembench", exec_format = "/usr/bin/tinymembench", - params = {} + params = {}, }, { name = "ramspeed", exec_format = "/usr/bin/ramspeed -b %d -g 1", - params = {1, 2, 3, 4, 5, 6} + params = { 1, 2, 3, 4, 5, 6 }, }, { name = "iozone", exec_format = "/usr/bin/iozone -a -s 65536 -r %d", - params = {64, 512, 2048} + params = { 64, 512, 2048 }, }, { name = "dieharder", exec_format = "/usr/bin/dieharder -d %d", - params = {0, 1, 2} + params = { 0, 1, 2 }, }, { name = "bonnie++", exec_format = "/usr/sbin/bonnie++ -d $(mktemp -d) -u root", - params = {0, 1, 2} + params = { 0, 1, 2 }, }, } @@ -87,25 +87,26 @@ local function build_machine(exec_args) -- Request automatic default values for versioning CSRs mimpid = -1, marchid = -1, - mvendorid = -1 + mvendorid = -1, }, rom = { image_filename = rom_image_filename, bootargs = ( - "console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw " .. - "quiet mtdparts=flash.0:-(root) -- " .. exec_args - ) + "console=hvc0 rootfstype=ext2 root=/dev/mtdblock0 rw " + .. "quiet mtdparts=flash.0:-(root) -- " + .. exec_args + ), }, ram = { image_filename = ram_image_filename, - length = 64 << 20 + length = 64 << 20, }, flash_drive = { { start = 0x80000000000000, length = 0x40000000, image_filename = flash_image_filename, - } + }, }, } @@ -114,14 +115,13 @@ end local function measure(exec_args) local results = {} - for i = 1, N_RUNS do - local machine = build_machine(exec_args) + for _ = 1, N_RUNS do + local machine = build_machine(exec_args) local start = socket.gettime() repeat machine:run(MAX_MCYCLE) until machine:read_iflags_H() or machine:read_mcycle() < MAX_MCYCLE local elapsed = socket.gettime() - start - machine:destroy() table.insert(results, elapsed) end return results @@ -136,7 +136,7 @@ local function measure_all() table.insert(results, { name = benchmark.name, param = param, - times = times + times = times, }) end end @@ -155,9 +155,9 @@ local function stddev(arr) local std2 = 0.0 local avg = average(arr) for _, value in ipairs(arr) do - std2 = std2 + (value - avg)^2 + std2 = std2 + (value - avg) ^ 2 end - return math.sqrt(std2/#arr) + return math.sqrt(std2 / #arr) end local function print_results(results) @@ -167,9 +167,7 @@ local function print_results(results) io.write(string.format("%-24s", label)) io.write("|") io.write(string.format("%7.3f", average(result.times))) - if PRINT_STDDEV then - io.write(string.format(" +-%.3f", stddev(result.times))) - end + if PRINT_STDDEV then io.write(string.format(" +-%.3f", stddev(result.times))) end io.write("|\n") end end diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index 7bde64127..d793fc9f9 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -15,17 +15,17 @@ RUN \ build-essential vim wget git libreadline-dev libboost-coroutine-dev \ libboost-context-dev libboost-filesystem-dev libboost-log-dev \ libssl-dev openssl libc-ares-dev zlib1g-dev clang-tidy-14 clang-format-14 \ - ca-certificates automake libtool patchelf cmake pkg-config lua5.3 liblua5.3-dev \ + ca-certificates automake libtool patchelf cmake pkg-config lua5.4 liblua5.4-dev \ libb64-dev libcrypto++-dev nlohmann-json3-dev luarocks && \ rm -rf /var/lib/apt/lists/* RUN \ - luarocks install luasocket && \ - luarocks install luasec && \ - luarocks install luaposix && \ - luarocks install lpeg && \ - luarocks install md5 && \ - luarocks install dkjson + luarocks install --lua-version=5.4 luasocket && \ + luarocks install --lua-version=5.4 luasec && \ + luarocks install --lua-version=5.4 luaposix && \ + luarocks install --lua-version=5.4 lpeg && \ + luarocks install --lua-version=5.4 md5 && \ + luarocks install --lua-version=5.4 dkjson ENV PATH="${PATH}:${BASE}/build/Linux_x86_64/bin" diff --git a/tools/template/cartesi-machine-stored-hash.template b/tools/template/cartesi-machine-stored-hash.template index d02a91356..a6bc9c8e2 100755 --- a/tools/template/cartesi-machine-stored-hash.template +++ b/tools/template/cartesi-machine-stored-hash.template @@ -1,2 +1,4 @@ #!/bin/sh -LUA_PATH="${LUA_PATH};ARG_LUA_PATH" LUA_CPATH="${LUA_CPATH};ARG_LUA_CPATH" lua5.3 ARG_BIN_INSTALL_PATH//cartesi-machine-stored-hash.lua "$@" +export LUA_PATH_5_4="${LUA_PATH_5_4:-;};ARG_LUA_PATH" +export LUA_CPATH_5_4="${LUA_CPATH_5_4:-;};ARG_LUA_CPATH" +lua5.4 "ARG_BIN_INSTALL_PATH/cartesi-machine-stored-hash.lua" "$@" diff --git a/tools/template/cartesi-machine-tests.template b/tools/template/cartesi-machine-tests.template index ee664f2fa..50f293768 100755 --- a/tools/template/cartesi-machine-tests.template +++ b/tools/template/cartesi-machine-tests.template @@ -1,5 +1,5 @@ #!/bin/sh -export LUA_PATH="$(lua5.3 -e 'print(package.path)');ARG_LUA_PATH" -export LUA_CPATH="$(lua5.3 -e 'print(package.cpath)');ARG_LUA_CPATH" +export LUA_PATH_5_4="${LUA_PATH_5_4:-;};ARG_LUA_PATH" +export LUA_CPATH_5_4="${LUA_CPATH_5_4:-;};ARG_LUA_CPATH" export CARTESI_IMAGES_PATH="ARG_INSTALL_PATH" -lua5.3 "ARG_BIN_INSTALL_PATH/cartesi-machine-tests.lua" "$@" +lua5.4 "ARG_BIN_INSTALL_PATH/cartesi-machine-tests.lua" "$@" diff --git a/tools/template/cartesi-machine.template b/tools/template/cartesi-machine.template index 77e987237..b35d9ff5f 100755 --- a/tools/template/cartesi-machine.template +++ b/tools/template/cartesi-machine.template @@ -1,5 +1,5 @@ #!/bin/sh -export LUA_PATH="$(lua5.3 -e 'print(package.path)');ARG_LUA_PATH" -export LUA_CPATH="$(lua5.3 -e 'print(package.cpath)');ARG_LUA_CPATH" +export LUA_PATH_5_4="${LUA_PATH_5_4:-;};ARG_LUA_PATH" +export LUA_CPATH_5_4="${LUA_CPATH_5_4:-;};ARG_LUA_CPATH" export CARTESI_IMAGES_PATH="ARG_INSTALL_PATH" -lua5.3 "ARG_BIN_INSTALL_PATH/cartesi-machine.lua" "$@" +lua5.4 "ARG_BIN_INSTALL_PATH/cartesi-machine.lua" "$@" diff --git a/tools/template/rollup-memory-range.template b/tools/template/rollup-memory-range.template index 9a8b0425a..3ed30b1fa 100755 --- a/tools/template/rollup-memory-range.template +++ b/tools/template/rollup-memory-range.template @@ -1,4 +1,4 @@ #!/bin/sh -export LUA_PATH="$(lua5.3 -e 'print(package.path)');ARG_LUA_PATH" -export LUA_CPATH="$(lua5.3 -e 'print(package.cpath)');ARG_LUA_CPATH" -lua5.3 "ARG_BIN_INSTALL_PATH/rollup-memory-range.lua" "$@" +export LUA_PATH_5_4="${LUA_PATH_5_4:-;};ARG_LUA_PATH" +export LUA_CPATH_5_4="${LUA_CPATH_5_4:-;};ARG_LUA_CPATH" +lua5.4 "ARG_BIN_INSTALL_PATH/rollup-memory-range.lua" "$@" diff --git a/tools/template/uarch-riscv-tests.template b/tools/template/uarch-riscv-tests.template index 2e32c174c..01de6adf9 100644 --- a/tools/template/uarch-riscv-tests.template +++ b/tools/template/uarch-riscv-tests.template @@ -1,5 +1,5 @@ #!/bin/sh -export LUA_PATH="$(lua5.3 -e 'print(package.path)');ARG_LUA_PATH" -export LUA_CPATH="$(lua5.3 -e 'print(package.cpath)');ARG_LUA_CPATH" +export LUA_PATH_5_4="${LUA_PATH_5_4:-;};ARG_LUA_PATH" +export LUA_CPATH_5_4="${LUA_CPATH_5_4:-;};ARG_LUA_CPATH" export CARTESI_IMAGES_PATH="ARG_INSTALL_PATH" -lua5.3 "ARG_BIN_INSTALL_PATH/uarch-riscv-tests.lua" "$@" +lua5.4 "ARG_BIN_INSTALL_PATH/uarch-riscv-tests.lua" "$@"