PXL
launcher.hpp
Go to the documentation of this file.
1 // SPDX-License-Identifier: Apache-2.0
2 // Copyright 2024 XCENA Inc.
3 
4 #pragma once
5 
6 #include <atomic>
7 #include <cstdint>
8 #include <future>
9 #include <memory>
10 #include <string>
11 #include <type_traits>
12 #include <vector>
13 #include <xpti/xpti.hpp>
14 
15 #include "pxl/direction.hpp"
16 #include "pxl/launch_result.hpp"
17 #include "pxl/map.hpp"
18 #include "pxl/module.hpp"
19 #include "pxl/ndarray.hpp"
20 #include "pxl/stage_builder.hpp"
21 #include "pxl/stream.hpp"
22 #include "pxl/task_count.hpp"
23 #include "pxl/type.hpp"
24 
25 namespace pxl
26 {
27 
28 namespace impl
29 {
30 class LaunchBuilderImpl;
31 class LaunchBuilderTestAccess;
32 class DeviceHandle;
33 } // namespace impl
34 
52 // [[nodiscard]] catches the most common new-user mistake — building the chain
53 // and forgetting `.run()`. Without it, the temporary is silently destroyed,
54 // the kernel never executes, and there is no runtime signal.
55 class [[nodiscard]] LaunchBuilder
56 {
57 public:
66  LaunchBuilder& reserve(uint32_t n);
67 
78  LaunchBuilder& tasks(uint32_t taskCount);
79 
87  LaunchBuilder& batchSize(uint32_t n);
88 
101  LaunchBuilder& clusterBitmap(uint32_t bitmap);
102 
110 
125 
140  LaunchBuilder& onMessage(Map::MessageCallback cb, void* arg = nullptr);
141 
151  LaunchBuilder& onError(Map::ErrorCallback cb, void* arg = nullptr);
152 
169 
180  LaunchBuilder& enableProfile(const xpti::ProfileConfig& config);
181 
196 
224  std::future<LaunchResult> runAsync();
225 
227  LaunchBuilder(LaunchBuilder&& other) noexcept;
229 
230  LaunchBuilder(const LaunchBuilder&) = delete;
232 
233 private:
234  LaunchBuilder(std::unique_ptr<impl::LaunchBuilderImpl> impl);
235 
236  std::unique_ptr<impl::LaunchBuilderImpl> impl_;
237  // Atomic so concurrent run()/runAsync() races on the same builder are
238  // diagnosed (returned as the "already called" failure) rather than UB.
239  // The builder is not designed to be shared across threads, but the guard
240  // is cheap and protects users from accidental sharing patterns.
241  std::atomic<bool> started_{false};
242 
243  friend class Launcher;
244  friend class impl::LaunchBuilderTestAccess;
245 };
246 
266 class Launcher
267 {
268 public:
289 
307  explicit Launcher(uint32_t deviceId);
308 
310 
311  Launcher(const Launcher&) = delete;
312  Launcher& operator=(const Launcher&) = delete;
313  Launcher(Launcher&&) noexcept;
314  Launcher& operator=(Launcher&&) noexcept;
315 
334  template <auto Func, typename... Args,
335  detail::EnableCompatibleKernelArguments<Func, Args...> = 0>
336  LaunchBuilder execute(uint32_t taskCount, Args&&... args)
337  {
338  const char* kernelName = nullptr;
339 
340 #if defined(__PXCC_ANALYSIS__)
341  kernelName = "__pxcc_placeholder__";
342 #else
343  static_assert(sizeof(pxl::KernelTraits<Func>) > 0,
344  "Kernel function not found in KernelTraits — was it compiled with pxcc?");
345  kernelName = pxl::KernelTraits<Func>::name;
346 #endif
347 
348  std::vector<ArgInfo_t> argList;
349  std::vector<std::shared_ptr<void>> storage;
350  std::vector<std::pair<void*, size_t>> syncTargets;
351  auto directions = detail::MakeKernelDirections<Func>();
352  argList.reserve(sizeof...(Args));
353  if constexpr (sizeof...(Args) > 0)
354  {
355  buildArgList(argList, storage, syncTargets, std::forward<Args>(args)...);
356  }
357 
358  if (impl_ == nullptr)
359  {
360  initFromArgs(argList, syncTargets);
361  }
362  if (!isValid())
363  {
364  return makeEmptyBuilder();
365  }
366 
367  return execute(kernelName, taskCount,
368  std::move(argList), std::move(storage), std::move(syncTargets),
369  std::move(directions));
370  }
371 
382  template <auto Func, typename... Args,
383  detail::EnableCompatibleKernelArguments<Func, Args...> = 0>
385  {
387  execute<Func>(uint32_t{0}, std::forward<Args>(args)...));
388  }
389 
406  template <auto Func, typename... Args,
407  detail::EnableCompatibleKernelArguments<Func, Args...> = 0>
408  StageBuilder stage(uint32_t taskCount, Args&&... args)
409  {
410  if (impl_ == nullptr)
411  {
412  std::vector<ArgInfo_t> tempArgList;
413  std::vector<std::shared_ptr<void>> tempStorage;
414  std::vector<std::pair<void*, size_t>> tempSyncTargets;
415  tempArgList.reserve(sizeof...(Args));
416  if constexpr (sizeof...(Args) > 0)
417  {
418  buildArgList(tempArgList, tempStorage, tempSyncTargets, std::forward<Args>(args)...);
419  }
420  initFromArgs(tempArgList, tempSyncTargets);
421  }
422  StageBuilder builder(impl_);
423  // Call returns StageBuilder& to the local; discard ref and return the
424  // local by value to enable NRVO (StageBuilder is move-only).
425  builder.stage<Func>(taskCount, std::forward<Args>(args)...);
426  return builder;
427  }
428 
436  template <auto Func, typename... Args,
437  detail::EnableCompatibleKernelArguments<Func, Args...> = 0>
439  {
441  stage<Func>(uint32_t{0}, std::forward<Args>(args)...));
442  }
443 
460  {
461  // Allocate the parent StageBuilder on the heap and transfer ownership
462  // to the returned StageGroupBuilder. Without owning the parent here,
463  // the StageGroupBuilder would hold a reference into this stack frame
464  // and dangle as soon as the function returns.
465  return StageGroupBuilder(std::make_unique<StageBuilder>(impl_));
466  }
467 
471  uint32_t deviceId() const;
472 
476  bool isValid() const;
477 
478 private:
479  LaunchBuilder execute(const char* kernelName, uint32_t taskCount,
480  std::vector<ArgInfo_t> argList,
481  std::vector<std::shared_ptr<void>> storage,
482  std::vector<std::pair<void*, size_t>> syncTargets);
483 
484  LaunchBuilder execute(const char* kernelName, uint32_t taskCount,
485  std::vector<ArgInfo_t> argList,
486  std::vector<std::shared_ptr<void>> storage,
487  std::vector<std::pair<void*, size_t>> syncTargets,
488  ArgDirections directions);
489 
490  void initFromArgs(const std::vector<ArgInfo_t>& argList,
491  const std::vector<std::pair<void*, size_t>>& syncTargets);
492  LaunchBuilder makeEmptyBuilder();
493 
494  template <typename T, typename... Rest>
495  void buildArgList(std::vector<ArgInfo_t>& argList, std::vector<std::shared_ptr<void>>& storage,
496  std::vector<std::pair<void*, size_t>>& syncTargets, T&& arg, Rest&&... rest)
497  {
498  addArg(argList, storage, syncTargets, std::forward<T>(arg));
499  if constexpr (sizeof...(rest) > 0)
500  {
501  buildArgList(argList, storage, syncTargets, std::forward<Rest>(rest)...);
502  }
503  }
504 
505  template <typename T>
506  std::enable_if_t<std::is_pointer_v<std::decay_t<T>>>
507  addArg(std::vector<ArgInfo_t>& argList, std::vector<std::shared_ptr<void>>&,
508  std::vector<std::pair<void*, size_t>>&, T&& ptr)
509  {
510  argList.emplace_back(ArgInfo_t{ArgType::DeviceMemory, static_cast<const void*>(ptr), 0});
511  }
512 
513  template <typename T>
514  std::enable_if_t<std::is_arithmetic_v<std::decay_t<T>> && !std::is_pointer_v<std::decay_t<T>> &&
515  !std::is_same_v<std::decay_t<T>, bool>>
516  addArg(std::vector<ArgInfo_t>& argList, std::vector<std::shared_ptr<void>>& storage,
517  std::vector<std::pair<void*, size_t>>&, T&& value)
518  {
519  auto stored = std::make_shared<std::decay_t<T>>(std::forward<T>(value));
520  argList.emplace_back(ArgInfo_t{ArgType::Constant, stored.get(), sizeof(std::decay_t<T>)});
521  storage.push_back(stored);
522  }
523 
524  template <typename T>
525  void addArg(std::vector<ArgInfo_t>& argList, std::vector<std::shared_ptr<void>>& storage,
526  std::vector<std::pair<void*, size_t>>& syncTargets,
527  const pxl::NDArray<T>& ndarrayArg)
528  {
529  // Copy the NDArray descriptor into heap storage so runAsync's worker
530  // thread never reads a dead caller stack frame. The data buffer behind
531  // ndarrayArg.data() is owned by pxl::allocateMemory and outlives the
532  // builder; only the descriptor needs heap-stable storage.
533  //
534  // On a device without hardware Back-Invalidate the launch synchronizes
535  // this view's host cache lines, and that works at cache-line
536  // granularity: a view sharing a line with host-written data outside it
537  // can have those host bytes written back over the kernel's output.
538  // Views that start at the allocation base, or are cache-line aligned
539  // and sized, are unaffected. The same holds for a pointer argument
540  // that points into the middle of an allocation.
541  auto storedNdarray = std::make_shared<pxl::NDArray<T>>(ndarrayArg);
542  argList.emplace_back(ArgInfo_t{ArgType::NDArray, storedNdarray.get(), sizeof(pxl::NDArray<T>)});
543  storage.push_back(storedNdarray);
544  // NDArray::size() returns int64_t; guard against negative values before casting to size_t.
545  const auto byteSize = ndarrayArg.size();
546  syncTargets.emplace_back(const_cast<void*>(static_cast<const void*>(ndarrayArg.data())),
547  byteSize > 0 ? static_cast<size_t>(byteSize) : 0);
548  }
549 
550  std::shared_ptr<impl::DeviceHandle> impl_;
551 };
552 
553 } // namespace pxl
Configurable kernel execution request.
Definition: launcher.hpp:56
LaunchBuilder & enableProfile(const xpti::ProfileConfig &config)
Enables profiling for this kernel.
LaunchBuilder(LaunchBuilder &&other) noexcept
LaunchBuilder & clusterBitmap(uint32_t bitmap)
Sets the cluster bitmap (0 = use all clusters).
LaunchBuilder & tasks(uint32_t taskCount)
Sets how many tasks invoke the kernel.
LaunchBuilder & operator=(const LaunchBuilder &)=delete
LaunchBuilder & batchSize(uint32_t n)
Sets per-task batch size for kernel execution. If not called, the runtime uses an implementation-defi...
LaunchBuilder & onError(Map::ErrorCallback cb, void *arg=nullptr)
Registers an error callback for execution failure.
LaunchBuilder & operator=(LaunchBuilder &&other) noexcept
LaunchResult run()
Executes the kernel and waits for completion.
LaunchBuilder & stream(Stream *s)
Routes the kernel through a user-managed stream.
LaunchBuilder & onComplete(Map::CompletionCallback cb, void *arg=nullptr)
Registers a completion callback for successful execution.
LaunchBuilder(const LaunchBuilder &)=delete
LaunchBuilder & reserve(uint32_t n)
Sets the number of Sub resources to reserve (Mid-Level).
LaunchBuilder & locality(LocalityMode mode)
Sets the locality mode for this kernel. If not called, the runtime uses an implementation-defined def...
LaunchBuilder & onMessage(Map::MessageCallback cb, void *arg=nullptr)
Registers a message callback for device-originated messages.
std::future< LaunchResult > runAsync()
Executes the kernel asynchronously.
Execution context for kernel launches on XCENA devices.
Definition: launcher.hpp:267
uint32_t deviceId() const
Returns the device ID.
StageGroupBuilder stage()
Begins a parallel stage group.
Definition: launcher.hpp:459
Launcher(uint32_t deviceId)
Constructs a Launcher for an explicit device ID.
StageBuilder stage(uint32_t taskCount, Args &&... args)
Begins a stage chain with a sequential kernel stage.
Definition: launcher.hpp:408
Launcher(Launcher &&) noexcept
LaunchBuilder execute(uint32_t taskCount, Args &&... args)
Launches a kernel for deferred execution.
Definition: launcher.hpp:336
Launcher & operator=(const Launcher &)=delete
Launcher(const Launcher &)=delete
detail::TaskCountRequired< LaunchBuilder > execute(Args &&... args)
Launches a kernel with only the kernel's own arguments.
Definition: launcher.hpp:384
bool isValid() const
Returns whether the launcher was initialized successfully.
detail::TaskCountRequired< StageBuilder > stage(Args &&... args)
Begins a stage chain with only the kernel's own arguments.
Definition: launcher.hpp:438
Launcher()
Constructs a Launcher with deferred device detection.
std::function< void(void *message, void *arg)> MessageCallback
Callback function type for task message.
Definition: map.hpp:69
std::function< void(void *arg)> ErrorCallback
Callback function type for task error.
Definition: map.hpp:78
std::function< void(void *arg)> CompletionCallback
Callback function type for task completion.
Definition: map.hpp:59
NDArray is a class that can be used in both host and device code. NDArray is automatically divided in...
Definition: ndarray.hpp:25
T * data()
Definition: ndarray.hpp:84
std::int64_t size() const
Definition: ndarray.hpp:114
Builds a chain of kernel stages for sequential/parallel execution.
StageBuilder & stage(uint32_t taskCount, Args &&... args)
StageBuilder & reserve(uint32_t n)
Collects parallel kernels within a single stage group.
An asynchronous work queue for device operations.
Definition: stream.hpp:20
Holds a launch request that still needs its task count.
Definition: task_count.hpp:103
std::enable_if_t< KernelArgumentsCompatibleV< Func, Actual... >, int > EnableCompatibleKernelArguments
Definition: direction.hpp:184
Definition: config.hpp:11
LocalityMode
Definition: type.hpp:92
Traits class that maps a kernel function pointer to its name string.
Definition: module.hpp:30