Docs/Quick Start

Run the first verified Flow.

Install the sealed Darwin SDK, build the checked consumer example, and verify its exact result. Every command below uses the prefix created by the verifier.

1.0.4 AlphaDarwin ARM64C++20Exact output check

1. Prepare the host

NeedCurrent release boundary
Host and toolchainEvery field in the exact Darwin 1.0.4 host tuple.
Backend dependenciesThe exact Homebrew versions in that same tuple; the SDK does not bundle MoltenVK.
Install locationAn existing destination directory with no rund-sdk-1.0.4-darwin-arm64 child.
Do not unpack the archive by hand The verifier checks checksum, archive safety, source and artifact identity, public CMake target, host tuple, and native dependencies before publishing the SDK prefix.
Run the host preflight before downloading Copy the inspection block on Platforms. If any field differs, stop there and follow that page's supported resolution; do not unpack or patch the binary.

2. Verify and install

Create an empty working directory. Download these three adjacent files from the 1.0.4 release into it:

rund-sdk-1.0.4-darwin-arm64.tar.gz
rund-sdk-1.0.4-darwin-arm64.sha256
rund-verify
shell
chmod +x ./rund-verify
./rund-verify \
  ./rund-sdk-1.0.4-darwin-arm64.tar.gz \
  ./rund-sdk-1.0.4-darwin-arm64.sha256 \
  "$PWD"

Success creates $PWD/rund-sdk-1.0.4-darwin-arm64. Failure creates no usable prefix; keep the typed diagnostic and use Troubleshooting instead of bypassing it.

3. Create the project

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(rund_first_flow LANGUAGES CXX)

find_package(runD 1.0.4 EXACT CONFIG REQUIRED)

add_executable(rund_first_flow main.cpp)
target_link_libraries(rund_first_flow PRIVATE runD::sdk)

Link only runD::sdk. Do not add the source tree, copy include paths, or link Kernel, Accel, Node, Metal, or Vulkan targets yourself.

main.cpp · checked installed example
#include <rund/compute.hpp>

#include <array>
#include <cstdint>
#include <cstdio>
#include <string_view>
#include <vector>

int main() {
  std::array<std::int32_t, 4> input{1, 2, 3, 4};
  auto output = rund::compute::on(rund::compute::Target::cpu(), input)
                    .map("twice", [](auto value) { return value * 2 + 5; })
                    .collect();
  if (!output) {
    const std::string_view message = output.error();
    std::fprintf(stderr, "cpu failed (code=%u): %.*s\n",
                 static_cast<unsigned>(output.code()),
                 static_cast<int>(message.size()), message.data());
    return output.exit_code();
  }
  if (*output != std::vector<std::int32_t>{7, 9, 11, 13}) {
    std::fputs("unexpected output; expected [7, 9, 11, 13]\n", stderr);
    return 2;
  }
  std::printf("cpu: [%d, %d, %d, %d]\n", (*output)[0], (*output)[1],
              (*output)[2], (*output)[3]);
  return 0;
}

4. Build and run

shell
cmake -S . -B build \
  -DCMAKE_PREFIX_PATH="$PWD/rund-sdk-1.0.4-darwin-arm64"
cmake --build build
./build/rund_first_flow
Expected output: cpu: [7, 9, 11, 13] It then exits 0. A product failure prints its code and diagnostic and returns its own exit_code(). Exit 2 means runD succeeded but the application assertion did not match.

5. Verify the same bytes on every target

Replace main.cpp with this complete checked program and rebuild. The Flow is declared once; only the explicit target changes. Device opening is the capability boundary; every backend that opens must execute and match the CPU bytes.

parity.cpp · checked installed example
#include <rund/compute.hpp>

#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <span>
#include <string_view>
#include <utility>
#include <vector>

int main() {
  const std::array<std::int32_t, 4> input{1, 2, 3, 4};

  const auto execute = [&input](const rund::compute::Device &device) {
    auto flow = rund::compute::on(device).map<std::int32_t>(
        "step", input.size(), [](auto value) { return value * 2 + 1; });
    auto program = std::move(flow).compile();
    if (!program) {
      return rund::compute::Result<std::vector<std::int32_t>>::fail(
          program.reason());
    }
    return program->run(std::span<const std::int32_t>{input});
  };

  const auto report = [](const std::string_view target, const auto &result) {
    const std::string_view message = result.error();
    std::fprintf(stderr, "%.*s failed (code=%u): %.*s\n",
                 static_cast<int>(target.size()), target.data(),
                 static_cast<unsigned>(result.code()),
                 static_cast<int>(message.size()), message.data());
    return result.exit_code();
  };
  auto cpu_device = rund::compute::open(rund::compute::Target::cpu());
  if (!cpu_device) {
    return report("cpu", cpu_device);
  }
  const auto cpu = execute(*cpu_device);
  if (!cpu) {
    return report("cpu", cpu);
  }

  const auto same_bits = [](const auto &left, const auto &right) {
    return left.size() == right.size() &&
           std::memcmp(left.data(), right.data(),
                       left.size() * sizeof(std::int32_t)) == 0;
  };
  const std::vector<std::int32_t> expected{3, 5, 7, 9};
  if (*cpu != expected) {
    std::fputs("cpu output mismatch\n", stderr);
    return 2;
  }

  std::size_t available_backend_count = 1u;
#if defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__))
  constexpr bool require_accelerators = true;
#else
  constexpr bool require_accelerators = false;
#endif
  const auto compare = [&](const std::string_view name,
                           const rund::compute::Target target) {
    auto device = rund::compute::open(target);
    if (!device) {
      if (!require_accelerators &&
          device.reason() == rund::compute::Reason::AdapterUnavailable) {
        const std::string_view message = device.error();
        std::fprintf(stderr, "%.*s unavailable (code=%u): %.*s\n",
                     static_cast<int>(name.size()), name.data(),
                     static_cast<unsigned>(device.code()),
                     static_cast<int>(message.size()), message.data());
        return 0;
      }
      return report(name, device);
    }
    const auto result = execute(*device);
    if (!result) {
      return report(name, result);
    }
    if (!same_bits(*cpu, *result)) {
      std::fprintf(stderr, "%.*s output mismatch\n",
                   static_cast<int>(name.size()), name.data());
      return 2;
    }
    ++available_backend_count;
    return 0;
  };
  if (const int metal = compare("metal", rund::compute::Target::metal());
      metal != 0) {
    return metal;
  }
  if (const int vulkan = compare("vulkan", rund::compute::Target::vulkan());
      vulkan != 0) {
    return vulkan;
  }

  if (available_backend_count == 3u) {
    std::printf("same bytes: cpu = metal = vulkan [%d, %d, %d, %d]\n",
                (*cpu)[0], (*cpu)[1], (*cpu)[2], (*cpu)[3]);
  } else {
    std::printf("verified [%d, %d, %d, %d] on %zu native backend(s)\n",
                (*cpu)[0], (*cpu)[1], (*cpu)[2], (*cpu)[3],
                available_backend_count);
  }
  return 0;
}

Expected output is same bytes: cpu = metal = vulkan [3, 5, 7, 9]. Darwin ARM64 requires all three backends. A non-Apple candidate may report the exact AdapterUnavailable result while opening a device; it never reruns that Flow on CPU. Any failure after a device opens remains fatal. This checked source is the complete public parity example for the release.

Stuck? Match the diagnostic to checksum, prefix, CMake, host tuple, or backend remedies in Troubleshooting.
Next conceptWhy the meaning survives another backend