Skip to content

[zkvm] Precompiles refactor#2209

Draft
dhil wants to merge 21 commits intomainfrom
dhil/zkvm-precompiles
Draft

[zkvm] Precompiles refactor#2209
dhil wants to merge 21 commits intomainfrom
dhil/zkvm-precompiles

Conversation

@dhil
Copy link
Copy Markdown
Contributor

@dhil dhil commented Apr 16, 2026

No description provided.

@dhil dhil requested review from Baltoli and andreaslyn as code owners April 16, 2026 16:42
@dhil dhil requested a review from goodlyrottenapple April 16, 2026 16:43
@dhil
Copy link
Copy Markdown
Contributor Author

dhil commented Apr 16, 2026

The diff is kind of pointless here as the target has diverted from main. I'll retarget to main instead, though, I am not seeking to get this patch included in main at the moment.

@dhil dhil changed the base branch from sam/zkvm-precompiles to main April 16, 2026 16:45
@dhil
Copy link
Copy Markdown
Contributor Author

dhil commented Apr 16, 2026

@goodlyrottenapple The actual patch starts on commit fa1e9a5. Each precompile refactor has its own commit, so it is probably best reviewed on a commit-by-commit basis.

(the base of this patch is #2090)

@dhil dhil marked this pull request as draft April 16, 2026 16:47
@dhil dhil changed the title [draft] [zkvm] Precompiles refactor [zkvm] Precompiles refactor Apr 16, 2026
@goodlyrottenapple
Copy link
Copy Markdown
Contributor

goodlyrottenapple commented Apr 17, 2026

Might it be worth rolling most of the new *_execute functions into a templated generic one?

enum class PrecompileImplOpts
{
    FreeOnNull,
    SuccessOnNull,
};

template <typename>
struct impl_out_size;

template <size_t N>
struct impl_out_size<
    PrecompileImplResult (*)(byte_string_view, std::span<uint8_t, N>)>
{
    static constexpr size_t value = N;
};

template <auto Impl, PrecompileImplOpts... Opts>
static PrecompileResult alloc_and_execute(byte_string_view const input)
{
    constexpr bool free_on_null =
        ((Opts == PrecompileImplOpts::FreeOnNull) || ...);
    constexpr bool success_on_null =
        ((Opts == PrecompileImplOpts::SuccessOnNull) || ...);

    constexpr size_t N = impl_out_size<decltype(Impl)>::value;
    auto *const out = static_cast<uint8_t *>(std::malloc(N));
    MONAD_ASSERT(out != nullptr);
    auto const result = Impl(input, std::span<uint8_t, N>{out, N});

    if (result.data == nullptr) {
        MONAD_DEBUG_ASSERT(result.size == 0);
        if constexpr (free_on_null) {
            std::free(out);
        }
        if constexpr (success_on_null) {
            return {EVMC_SUCCESS, nullptr, 0};
        }
        return PrecompileResult::failure();
    }
    return {EVMC_SUCCESS, result.data, result.size};
}

then in cases, have e.g.:

CASE(0x02, sha256_gas_cost, alloc_and_execute<sha256_impl>);
...
CASE(
    0x09, blake2bf_gas_cost<traits>,
    alloc_and_execute<blake2bf_impl, PrecompileImplOpts::FreeOnNull>);
...
CASE(
    0x0100, p256_verify_gas_cost,
    alloc_and_execute<
        p256_verify_impl, PrecompileImplOpts::FreeOnNull,
        PrecompileImplOpts::SuccessOnNull>
...

@dhil
Copy link
Copy Markdown
Contributor Author

dhil commented Apr 21, 2026

Might it be worth rolling most of the new *_execute functions into a templated generic one?

enum class PrecompileImplOpts
{
    FreeOnNull,
    SuccessOnNull,
};

template <typename>
struct impl_out_size;

template <size_t N>
struct impl_out_size<
    PrecompileImplResult (*)(byte_string_view, std::span<uint8_t, N>)>
{
    static constexpr size_t value = N;
};

template <auto Impl, PrecompileImplOpts... Opts>
static PrecompileResult alloc_and_execute(byte_string_view const input)
{
    constexpr bool free_on_null =
        ((Opts == PrecompileImplOpts::FreeOnNull) || ...);
    constexpr bool success_on_null =
        ((Opts == PrecompileImplOpts::SuccessOnNull) || ...);

    constexpr size_t N = impl_out_size<decltype(Impl)>::value;
    auto *const out = static_cast<uint8_t *>(std::malloc(N));
    MONAD_ASSERT(out != nullptr);
    auto const result = Impl(input, std::span<uint8_t, N>{out, N});

    if (result.data == nullptr) {
        MONAD_DEBUG_ASSERT(result.size == 0);
        if constexpr (free_on_null) {
            std::free(out);
        }
        if constexpr (success_on_null) {
            return {EVMC_SUCCESS, nullptr, 0};
        }
        return PrecompileResult::failure();
    }
    return {EVMC_SUCCESS, result.data, result.size};
}

then in cases, have e.g.:

CASE(0x02, sha256_gas_cost, alloc_and_execute<sha256_impl>);
...
CASE(
    0x09, blake2bf_gas_cost<traits>,
    alloc_and_execute<blake2bf_impl, PrecompileImplOpts::FreeOnNull>);
...
CASE(
    0x0100, p256_verify_gas_cost,
    alloc_and_execute<
        p256_verify_impl, PrecompileImplOpts::FreeOnNull,
        PrecompileImplOpts::SuccessOnNull>
...

Yes. I think this might be a good way to cut down the boilerplate. I will look into this.

@dhil dhil force-pushed the dhil/zkvm-precompiles branch 5 times, most recently from 1f1a96f to 3b6ec81 Compare April 29, 2026 13:48
@mkolosick
Copy link
Copy Markdown
Contributor

I dug more into the precompiles I hadn't looked at yet, and I think that template generic'ing is actually premature here. I think there are more opportunities to follow the pattern in ecrecover_execute of pulling the common error handling/parsing up into the _execute layer once we've removed silkpre. I started doing this for the other precompiles but too many of them are tied up to internal silkpre implementation at the moment, so I pulled back on that for now.

In light of that I'm just cleaning up the commits and rebasing my other changes and then I would be good with the commit.

@mkolosick mkolosick force-pushed the dhil/zkvm-precompiles branch 2 times, most recently from 1165e25 to d6bd1b8 Compare May 5, 2026 20:30
mkolosick and others added 12 commits May 5, 2026 16:47
Add a standalone CMake project (category/zkvm/) that builds
libmonad-zkvm.a for bare-metal RISC-V targets (ZisK and SP1 zkVM
backends). This allows the Monad EVM interpreter to run inside
zkVMs.

Key changes:
- RISC-V toolchain file (rv64ima, lp64, medany, -nostdlib++)
- intx-backed uint256 wrapper replacing AVX2 implementation
- zkVM-abstracted allocator (zkvm_aligned_alloc) and
  exit (zkvm_exit) interfaces with ZisK/SP1 backends
- Conditional compilation guards (#ifdef MONAD_ZKVM) for bare-metal
  compatibility across interpreter, runtime, and core components
- Shared compile_options.cmake extracted from root CMakeLists.txt
- Bare-metal stubs: libc (malloc/calloc/free), libstdc++ (operator
  new/delete, std::terminate), and assert handlers

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@mkolosick mkolosick force-pushed the dhil/zkvm-precompiles branch from d6bd1b8 to c7228b5 Compare May 5, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants