Skip to content

Windowz#13

Closed
PhilipDeegan wants to merge 6 commits into
masterfrom
windowz
Closed

Windowz#13
PhilipDeegan wants to merge 6 commits into
masterfrom
windowz

Conversation

@PhilipDeegan

@PhilipDeegan PhilipDeegan commented Jan 8, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Windows CI pipeline added
    • Lightweight function-call debugging counter
    • New AVX-enabled benchmark programs
  • Documentation

    • Alignment configuration doc updated to a conditional, CPU-aware default
  • Refactor

    • Vector/Span APIs reworked to span-based usage and simpler Vector_t alias
    • Array and Span construction/assignment semantics improved
    • AVX numeric/type system reorganized and simplified
  • Tests

    • Updated and added benchmarks and unit tests to cover changes

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 8, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR overhauls the AVX subsystem (types, span, array, vector), introduces a function-call Counter, replaces the old Type system with a specialization-based implementation, updates public headers/tests to new aliases (Vector_t, make_spans), adds Windows CI, and introduces new benchmark tests and profiles.

Changes

Cohort / File(s) Summary
CI / Workflow
\.github/workflows/build_nix.yml, \.github/workflows/build_win.yml, mkn.yaml
Nix workflow targets changed (bench → test,b0,b1), KLOG level adjusted, -DKUL_FORCE_TRACE added; new Windows workflow (MSVC toolchain, mkn invocation flags) added; mkn.yaml adds b0/b1 profiles and run flags. Attention: CI targets/flags changed.
Core AVX numeric types
inc/mkn/avx/types.hpp, inc/mkn/avx/url.hpp
Removed legacy Type machinery from types.hpp; added specialization-driven Type_<T,SIZE> + Type<T,SIZE> wrapper with per-type intrinsic pointers, arithmetic operators, store and fma in url.hpp. Attention: public numeric AVX types/operators moved and reimplemented.
Vector API & public headers
inc/mkn/avx/vector.hpp, inc/mkn/avx.hpp, inc/mkn/avx/span.hpp, inc/mkn/avx/span.hpp
Replaced Vector class with Vector_t alias (std::vector + AlignedAllocator using Options::ALIGN()); avx.hpp sample uses Vector_t; span.hpp refactored (union storage, copy/move ctors, many inline APIs, make_span(s)). Attention: ABI/API surface and allocation/alignment behavior changed.
Array & Span semantics
inc/mkn/avx/array.hpp, inc/mkn/avx/span.hpp
Array default alignment switched to Options::ALIGN(), added std::nullopt ctor, adjusted copy/move/assign semantics and many inline operator changes; Span adds union storage, explicit copy/move ctors, richer assignment/comparison APIs and make_span(s) helpers. Attention: construction/assignment and static constexpr size() semantics changed.
Options / alignment config
inc/mkn/avx/def.hpp, README.noformat
Removed ALIGN_AS constant; added Options::ALIGN() constexpr computing alignment by AVX level (with MKN_AVX_ALIGN_AS override); N() can be forced with MKN_AVX_FORCE_N; README updated to document conditional alignment mapping. Attention: alignment now computed via function.
Debug utilities
inc/mkn/avx/dbg.hpp
New Counter singleton and CountPoint, plus MKN_AVX_FN_COUNTER macro (enabled when MKN_AVX_COUNT_FNS defined) to record per-call-site counters. Attention: runtime map introduced when enabled.
Lazy eval minor changes
inc/mkn/avx/lazy.hpp
AVXVec allocator now uses Options::ALIGN() and eval overloads annotate unused in_place parameter. Attention: alignment dependency change.
Benchmarks & tests
test/bench.cpp, test/betterbench.cpp, test/betterbench.hpp, test/betterbench_avx.cpp, test/test_avx.cpp, test/test_lazy.cpp
Tests updated to use Vector_t and span helpers; new SoA/AoS benchmark sources added; many assertions switched to assert; test_avx logs Counter contents. Attention: new tests and significant logic additions — verify correctness and linkage to the new Type implementation.
Public API removals / renames
inc/mkn/avx/... (multiple)
Several public types and helpers removed/renamed (old Type system, Vector class); new aliases/functions added (Vector_t, make_span(s), Type_<...>, Counter). Attention: downstream consumers must update uses and constructors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • lazy vectors ops #7 — touches same AVX core headers (lazy.hpp, span.hpp, types.hpp, vector.hpp) and lazy-eval APIs; likely overlapping changes.
  • arrays #11 — overlaps array/span and alignment/options changes and public AVX header refactors; likely related.

Poem

I nibble bytes and hop through code,
From Vector to Vector_t I rode,
Spans tuck in a union bed,
Counters tally where I tread,
Benchmarks hum — hooray for AVX mode! 🐇✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Windowz' is vague and does not clearly convey the main change. While the PR objectives indicate the work involves fixing Windows CI/build issues, the title itself is non-descriptive and provides no meaningful information about the changeset. Revise the title to be more descriptive of the main objective, e.g., 'Add Windows CI/build support and fix MSVC compatibility issues' or 'Fix Windows build and add MSVC-compatible AVX intrinsic wrappers'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
inc/mkn/avx/array.hpp (1)

35-37: Duplicate include of span.hpp.

Line 37 includes "span.hpp" which is redundant with line 35's "mkn/avx/span.hpp". Remove the duplicate.

🔧 Suggested fix
 #include "mkn/avx/def.hpp"
 #include "mkn/avx/span.hpp"

-#include "span.hpp"
-
 #include <array>
🤖 Fix all issues with AI agents
In @inc/mkn/avx/dbg.hpp:
- Around line 72-74: The macro MKN_AVX_FN_COUNTER uses token pasting with
__LINE__ (as in __mkn_avx_count_point##__LINE__) which prevents __LINE__ from
expanding; replace it with a two-step helper macro (e.g., a JOIN/EXPAND pair) so
the line number is expanded before concatenation, then use that helper to
produce the unique identifier for CountPoint and to call Counter::I() with the
correct file/func/line parameters; update the macro definition that creates
__mkn_avx_count_point##__LINE__ to use the helper so CountPoint and Counter::I()
get the actual line number.

In @inc/mkn/avx/url.hpp:
- Around line 1-4: The header currently forces MKN_AVX_FN_COUNTER to be defined
empty, which masks MKN_AVX_COUNT_FNS logic in dbg.hpp; remove the conditional
macro definition for MKN_AVX_FN_COUNTER from this header so dbg.hpp can control
it (or alternatively ensure this header is included only after dbg.hpp); verify
that dbg.hpp still sets MKN_AVX_FN_COUNTER when MKN_AVX_COUNT_FNS is defined and
that no other headers reintroduce the empty definition.
- Around line 48-49: The two operator[] overloads have inconsistent semantics:
the non-const operator[] uses reinterpret_cast<T*>(&array) while the const
operator[] indexes array directly; make the const overload mirror the non-const
behavior by indexing through a pointer-to-const T obtained from
reinterpret_cast<const T*>(&array) so both versions return the same element view
and maintain noexcept and const correctness (update the const operator[]
implementation in the class to use reinterpret_cast to a const T pointer and
index it).

In @test/betterbench_avx.cpp:
- Around line 58-60: Replace the throw with a non-zero process exit and remove
the stray semicolon: where the code currently does `if (d3[arrs - 1][3] != b3)
throw std::runtime_error("FAIL!");` change it to return a non-zero value (e.g.
`return EXIT_FAILURE;` or `return 1;`) so the program exits cleanly with failure
semantics (add `#include <cstdlib>` if using `EXIT_FAILURE`), and delete the
trailing semicolon after the closing brace to remove the extraneous token.
🧹 Nitpick comments (4)
inc/mkn/avx/array.hpp (1)

102-106: Consider removing commented-out code.

The commented-out assignment operator adds noise. If it's not planned for future use, consider removing it entirely.

.github/workflows/build_win.yml (1)

30-30: Consider aligning KLOG level with Linux CI and verify -fPIC relevance on Windows.

Two minor observations:

  1. KLOG level inconsistency: The Windows workflow uses KLOG=3 while the Linux workflow uses KLOG=5 (line 21 in build_nix.yml). Consider using KLOG=5 here as well for consistent verbose logging across platforms, unless lower verbosity on Windows is intentional.

  2. -fPIC flag on Windows: The -fPIC flag is typically a Unix/GCC concept for position-independent code. MSVC may ignore it or produce warnings. While likely harmless, you might want to verify it's necessary or consider platform-specific flag handling.

test/betterbench_avx.cpp (1)

25-26: Unused prefetcher lambda.

The prefetcher lambda is defined but never invoked in this benchmark. Consider removing it or adding the prefetch calls if they were intended to be part of the benchmark loop.

inc/mkn/avx/types.hpp (1)

34-37: Misleading comment.

The comment "one file url include no deps" on line 37 is inaccurate—url.hpp includes <immintrin.h>, <cstdint>, and <utility>. Consider clarifying or removing this comment.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ac9325 and 146efae.

📒 Files selected for processing (19)
  • .github/workflows/build_nix.yml
  • .github/workflows/build_win.yml
  • README.noformat
  • inc/mkn/avx.hpp
  • inc/mkn/avx/array.hpp
  • inc/mkn/avx/dbg.hpp
  • inc/mkn/avx/def.hpp
  • inc/mkn/avx/lazy.hpp
  • inc/mkn/avx/span.hpp
  • inc/mkn/avx/types.hpp
  • inc/mkn/avx/url.hpp
  • inc/mkn/avx/vector.hpp
  • mkn.yaml
  • test/bench.cpp
  • test/betterbench.cpp
  • test/betterbench.hpp
  • test/betterbench_avx.cpp
  • test/test_avx.cpp
  • test/test_lazy.cpp
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: PhilipDeegan
Repo: mkn/mkn.avx PR: 11
File: inc/mkn/avx/array.hpp:185-190
Timestamp: 2025-03-16T19:40:42.661Z
Learning: In the mkn.avx library, separate type parameters (T0, T1) are used in template functions to handle const/non-const type variations rather than for actual different types. If the types are incompatible, the code will fail to compile.
📚 Learning: 2025-03-16T19:40:42.661Z
Learnt from: PhilipDeegan
Repo: mkn/mkn.avx PR: 11
File: inc/mkn/avx/array.hpp:185-190
Timestamp: 2025-03-16T19:40:42.661Z
Learning: In the mkn.avx library, separate type parameters (T0, T1) are used in template functions to handle const/non-const type variations rather than for actual different types. If the types are incompatible, the code will fail to compile.

Applied to files:

  • inc/mkn/avx/lazy.hpp
  • inc/mkn/avx/types.hpp
  • inc/mkn/avx/vector.hpp
  • test/bench.cpp
  • inc/mkn/avx/def.hpp
  • inc/mkn/avx.hpp
  • test/betterbench.cpp
  • test/betterbench.hpp
  • test/betterbench_avx.cpp
  • test/test_avx.cpp
  • inc/mkn/avx/url.hpp
  • inc/mkn/avx/span.hpp
  • inc/mkn/avx/array.hpp
📚 Learning: 2025-03-16T14:42:12.355Z
Learnt from: PhilipDeegan
Repo: mkn/mkn.avx PR: 11
File: inc/mkn/avx/array.hpp:93-96
Timestamp: 2025-03-16T14:42:12.355Z
Learning: In the mkn::avx Array class, the move constructor is implemented as a call to the copy constructor because the base class _A_ has a deleted move constructor. This design choice was made to ensure the class can be returned from functions while maintaining the constraint that the underlying array cannot be moved.

Applied to files:

  • inc/mkn/avx/types.hpp
  • inc/mkn/avx/vector.hpp
  • inc/mkn/avx.hpp
  • test/betterbench.hpp
  • test/test_avx.cpp
  • inc/mkn/avx/url.hpp
  • inc/mkn/avx/span.hpp
  • inc/mkn/avx/array.hpp
🧬 Code graph analysis (5)
test/bench.cpp (3)
test/betterbench.hpp (1)
  • v0 (39-39)
test/test_avx.cpp (1)
  • v0 (70-70)
inc/mkn/avx/span.hpp (6)
  • make_spans (561-564)
  • make_spans (561-561)
  • make_spans (568-571)
  • make_spans (568-568)
  • make_spans (575-578)
  • make_spans (575-575)
inc/mkn/avx.hpp (1)
inc/mkn/avx/span.hpp (6)
  • make_spans (561-564)
  • make_spans (561-561)
  • make_spans (568-571)
  • make_spans (568-568)
  • make_spans (575-578)
  • make_spans (575-575)
test/betterbench.cpp (1)
test/betterbench.hpp (2)
  • op (68-87)
  • op (68-68)
test/betterbench_avx.cpp (1)
test/betterbench.hpp (4)
  • op (68-87)
  • op (68-68)
  • KUL_DBG_FUNC_ENTER (90-112)
  • KUL_DBG_FUNC_ENTER (115-134)
inc/mkn/avx/array.hpp (2)
inc/mkn/avx/def.hpp (2)
  • N (79-94)
  • N (79-79)
inc/mkn/avx/span.hpp (41)
  • N (74-74)
  • N (250-250)
  • arr (72-72)
  • arr (73-73)
  • arr (157-162)
  • arr (157-157)
  • arr (174-179)
  • arr (174-174)
  • arr (190-195)
  • arr (190-190)
  • arr (396-402)
  • arr (396-396)
  • arr (420-426)
  • arr (420-420)
  • arr (439-445)
  • arr (439-439)
  • arr (462-468)
  • arr (462-462)
  • val (403-407)
  • val (403-403)
  • val (446-450)
  • val (446-446)
  • val (469-473)
  • val (469-469)
  • make_span (522-528)
  • make_span (522-522)
  • make_span (530-536)
  • make_span (530-530)
  • make_span (539-542)
  • make_span (539-539)
  • make_span (544-547)
  • make_span (544-544)
  • make_span (550-553)
  • make_span (550-550)
  • make_span (555-558)
  • make_span (555-555)
  • span (248-248)
  • span (249-249)
  • span (255-255)
  • span (256-256)
  • span (504-504)
🪛 Cppcheck (2.19.0)
test/betterbench.cpp

[error] 32-32: Unhandled exception thrown in function that is an entry point.

(throwInEntryPoint)

test/betterbench_avx.cpp

[warning] 48-48: The address of variable 'array' might be accessed at non-zero index.

(objectIndex)


[error] 59-59: Unhandled exception thrown in function that is an entry point.

(throwInEntryPoint)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (35)
test/test_lazy.cpp (2)

16-16: LGTM on the assert migration.

The switch from abort_if_not to standard assert is appropriate for test code. Note that assert is disabled when NDEBUG is defined, so ensure test builds don't define NDEBUG.

Also applies to: 27-27, 39-39, 48-48, 60-60, 70-70, 79-79, 94-94, 105-105


108-119: Diagnostic output and test structure look good.

The __FILE__ output aids debugging test failures. The test suite covers add, mul, and FMA operations comprehensively.

inc/mkn/avx/lazy.hpp (1)

354-366: Unused parameter documentation is appropriate.

Commenting out the parameter name /*in_place*/ correctly suppresses unused parameter warnings while preserving the API signature for future implementation.

inc/mkn/avx/def.hpp (2)

81-83: Early return pattern with preprocessor is correct but may confuse static analyzers.

The preprocessor-based early return works correctly. Consider adding a comment noting that the remaining code is the fallback path when MKN_AVX_FORCE_N is not defined.


96-110: New ALIGN() function looks good.

The alignment computation correctly maps AVX levels to appropriate alignment values (64/32/16/8). The MKN_AVX_ALIGN_AS override provides useful flexibility for non-standard configurations.

inc/mkn/avx/array.hpp (3)

71-82: std::nullopt_t constructor for uninitialized arrays is a nice addition.

Using std::nullopt_t as a tag type to signal no-initialization is an elegant pattern. The default constructor now initializes to 0 via the Span assignment operator, which is clear and safe.


84-95: Move constructor copies data as expected.

Based on learnings, the move constructor intentionally copies data because the underlying _A_ base cannot be moved. The **this = *that pattern cleanly delegates to Span-based assignment.


204-229: Free operator overloads for Span arithmetic look correct.

The operators properly create uninitialized arrays with std::nullopt, then perform the operations. Using __restrict hints to the compiler that the spans don't alias.

README.noformat (1)

23-31: Documentation accurately reflects the new alignment behavior.

The updated documentation correctly describes the conditional alignment defaults based on AVX level, matching the implementation in Options::ALIGN().

mkn.yaml (1)

17-32: Benchmark profiles restructured for AVX configuration testing.

Splitting into b0 and b1 profiles enables benchmarking with different AVX configurations (-DMKN_AVX_BENCH_AVX_N). The gbench profile is preserved for Google Benchmark integration.

inc/mkn/avx/vector.hpp (1)

42-43: Clean simplification to a type alias.

Replacing the complex Vector class with a simple alias to std::vector with an aligned allocator is a good design choice. This leverages standard library functionality while ensuring proper AVX alignment via Options::ALIGN(). All usages of the old Vector class have been successfully migrated to Vector_t, with no remaining legacy patterns in the codebase.

.github/workflows/build_nix.yml (1)

21-21: LGTM! Enhanced testing configuration.

The changes improve CI diagnostics and test coverage by increasing KLOG verbosity, adding KUL tracing, and expanding test targets to include b0 and b1 profiles.

.github/workflows/build_win.yml (2)

3-5: LGTM! Proper concurrency configuration.

The concurrency cancellation prevents redundant builds when new commits are pushed to the same PR or branch.


19-21: LGTM! Standard MSVC environment setup.

Using the ilammy/msvc-dev-cmd action with amd64 architecture is the correct approach for Windows C++ builds.

test/betterbench.cpp (4)

9-13: LGTM! Flexible type configuration.

The conditional compilation allows the benchmark to run with either AVX-optimized Array types or plain scalar types, enabling performance comparison.


23-25: LGTM! Proper baseline establishment.

Computing the baseline value once and logging it provides a reference for validating the SoA and AoS implementations.


27-33: LGTM! Proper SoA validation.

The test correctly validates the SoA implementation against the baseline. The exception thrown on failure is appropriate for test code, despite the static analysis warning. Test programs commonly throw or abort on failure to signal the test harness.


34-40: LGTM! Proper AoS validation.

The AoS test mirrors the SoA validation pattern, ensuring both data layout implementations produce correct results.

test/bench.cpp (3)

97-102: LGTM! Systematic API migration to Vector_t and spans.

The refactoring from Vector<T> to Vector_t<T> with span-based operations is clean and consistent. The structured binding pattern auto [a, b, c] = mkn::avx::make_spans(v0, v1, v2) improves ergonomics and aligns with the broader architectural changes in this PR.


5-5: LGTM! Include organization improvements.

The include reordering and addition of standard headers (<cmath>, <array>, <cassert>) improve clarity and ensure all dependencies are explicitly listed.

Also applies to: 10-12


196-200: Clarify the status of uint32_t support and either enable or remove the deferred benchmarks.

The commented uint32_t benchmarks (lines 196-200) correspond to a commented-out Type_<std::uint32_t, 8> specialization in inc/mkn/avx/url.hpp (lines 194-201). While Type_<std::int32_t, 8> is active and float/double benchmarks are enabled, uint32_t support appears intentionally deferred. Either complete the uint32_t implementation with a clear tracking issue or remove the commented code to avoid confusion.

inc/mkn/avx.hpp (2)

35-35: LGTM! Essential header addition.

Including mkn/avx/span.hpp exposes the span utilities to library users, enabling the new span-based API pattern demonstrated in the sample code.


51-52: LGTM! Clear demonstration of the new API.

The updated sample code effectively demonstrates the Vector_t and make_spans pattern, providing users with a clear usage example of the refactored API.

test/test_avx.cpp (4)

3-6: LGTM!

The include reordering and additions (mkn/kul/log.hpp, mkn/avx/vector.hpp, <cmath>) align with the new API surface and support the Counter logging added in main().


17-37: LGTM!

The transition from abort_if_not to standard assert is cleaner. The expanded test data with t2{1} and combined return t1 + t2 correctly validates the copy/move semantics of Array_t.


45-66: LGTM!

The Vec alias updates to mkn::avx::Vector_t<T> correctly follow the new API convention established in this refactor.


132-141: Verify KLOG exception safety in noexcept context.

main() is marked noexcept, but KLOG(INF) may potentially throw (e.g., during stream operations or formatting failures). If KLOG can throw, this would cause std::terminate. Verify that the mkn::kul logging implementation is exception-safe or marked noexcept before using it in this context.

test/betterbench_avx.cpp (1)

28-48: LGTM!

The op lambda correctly demonstrates the new AVX_t function-pointer-based API for vectorized arithmetic operations. The structured binding with std::forward_as_tuple is idiomatic.

The static analysis warning about array index access appears to be a false positive related to how structured bindings interact with tuple references.

inc/mkn/avx/dbg.hpp (1)

43-62: Counter is not thread-safe.

The singleton Counter modifies cnts without synchronization. If the library is used in multi-threaded code with MKN_AVX_COUNT_FNS enabled, this will result in data races.

Consider adding a mutex or documenting that function counting is not thread-safe, or using std::atomic<std::size_t> values if approximate counts are acceptable.

test/betterbench.hpp (1)

33-43: LGTM!

The SoA structure correctly initializes four Vec<V> members with the same size, and the operator bool() check validates the initialization. The default size of 10 is suitable for a test harness.

inc/mkn/avx/url.hpp (1)

149-190: Incomplete integer type specializations.

The Type_<std::int16_t, ...> and Type_<std::int32_t, ...> specializations are missing several function pointers (mul_func_ptr, div_func_ptr, set_func_ptr, set_v_func_ptr for int16_t; sub_func_ptr, div_func_ptr, set_func_ptr, set_v_func_ptr for int32_t).

Code using these types with missing operations will fail to compile. Consider either completing these specializations or adding static_assert with clear error messages.

inc/mkn/avx/span.hpp (4)

67-80: Union type punning between pointer types.

The union U holds both T* arr and mkn::avx::Type<R, N>* i, and avx() provides access to the i member. Reading from a different union member than was written is implementation-defined in C++ (unlike C where it's well-defined).

This pattern works in practice for SIMD aliasing but relies on Type<R, N> having compatible layout with T[N]. Ensure this invariant is documented and tested.


94-104: LGTM!

The copy and move constructors both perform shallow copies, which is correct for a non-owning span type. The move constructor intentionally behaves like a copy since there's no ownership to transfer.


560-578: LGTM!

The make_spans overloads provide convenient tuple construction for multiple spans. The use of containers.data() correctly extracts the underlying data pointers while the forwarding reference syntax enables both lvalue and rvalue container arguments.


211-228: LGTM!

The templated operator= correctly branches on mkn::kul::is_span_like_v<T0> to handle both span-like and scalar assignments. Deleting the rvalue assignment operator prevents dangling references from temporary spans.

Comment thread inc/mkn/avx/dbg.hpp
Comment on lines +72 to +74
#define MKN_AVX_FN_COUNTER \
static mkn::avx::CountPoint __mkn_avx_count_point##__LINE__{__FILE__, __func__, __LINE__}; \
mkn::avx::Counter::I()(__FILE__, __func__, __LINE__);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Macro __LINE__ token pasting issue.

The ##__LINE__ concatenation won't expand __LINE__ to the actual line number. The preprocessor requires indirection through a helper macro for proper expansion.

Proposed fix
+#define MKN_AVX_CONCAT_(a, b) a##b
+#define MKN_AVX_CONCAT(a, b) MKN_AVX_CONCAT_(a, b)
+
 #if defined(MKN_AVX_COUNT_FNS)
 #define MKN_AVX_FN_COUNTER                                                                         \
-    static mkn::avx::CountPoint __mkn_avx_count_point##__LINE__{__FILE__, __func__, __LINE__};     \
+    static mkn::avx::CountPoint MKN_AVX_CONCAT(__mkn_avx_count_point, __LINE__){__FILE__, __func__, __LINE__};     \
     mkn::avx::Counter::I()(__FILE__, __func__, __LINE__);

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @inc/mkn/avx/dbg.hpp around lines 72 - 74, The macro MKN_AVX_FN_COUNTER uses
token pasting with __LINE__ (as in __mkn_avx_count_point##__LINE__) which
prevents __LINE__ from expanding; replace it with a two-step helper macro (e.g.,
a JOIN/EXPAND pair) so the line number is expanded before concatenation, then
use that helper to produce the unique identifier for CountPoint and to call
Counter::I() with the correct file/func/line parameters; update the macro
definition that creates __mkn_avx_count_point##__LINE__ to use the helper so
CountPoint and Counter::I() get the actual line number.

Comment thread inc/mkn/avx/url.hpp
Comment on lines +1 to +4

#if !defined(MKN_AVX_FN_COUNTER)
#define MKN_AVX_FN_COUNTER // optionally defined in dbg.hpp
#endif

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Macro definition order issue.

Defining MKN_AVX_FN_COUNTER as empty before including dbg.hpp will prevent the counter from being enabled even when MKN_AVX_COUNT_FNS is defined, because the #if !defined check on line 2 will always succeed when this header is included first.

This header should be included after dbg.hpp, or the macro definition should be removed here.

Proposed fix
-
-#if !defined(MKN_AVX_FN_COUNTER)
-#define MKN_AVX_FN_COUNTER // optionally defined in dbg.hpp
-#endif
+#include "mkn/avx/dbg.hpp"

 #include <cstdint>
 #include <utility>
 #include <immintrin.h> // avx
🤖 Prompt for AI Agents
In @inc/mkn/avx/url.hpp around lines 1 - 4, The header currently forces
MKN_AVX_FN_COUNTER to be defined empty, which masks MKN_AVX_COUNT_FNS logic in
dbg.hpp; remove the conditional macro definition for MKN_AVX_FN_COUNTER from
this header so dbg.hpp can control it (or alternatively ensure this header is
included only after dbg.hpp); verify that dbg.hpp still sets MKN_AVX_FN_COUNTER
when MKN_AVX_COUNT_FNS is defined and that no other headers reintroduce the
empty definition.

Comment thread inc/mkn/avx/url.hpp
Comment on lines +300 to +305
template<typename T, std::size_t SIZE>
void inline store(T const* __restrict a, Type<T, SIZE> const& __restrict b) noexcept
{
MKN_AVX_FN_COUNTER;
Type<T, SIZE>::set_func_ptr(const_cast<T*>(a), b());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

const_cast to write through const pointer.

The store function takes T const* but immediately casts away const to write. This is misleading and potentially undefined behavior if the caller passes a pointer to an actual const object.

Proposed fix
 template<typename T, std::size_t SIZE>
-void inline store(T const* __restrict a, Type<T, SIZE> const& __restrict b) noexcept
+void inline store(T* __restrict a, Type<T, SIZE> const& __restrict b) noexcept
 {
     MKN_AVX_FN_COUNTER;
-    Type<T, SIZE>::set_func_ptr(const_cast<T*>(a), b());
+    Type<T, SIZE>::set_func_ptr(a, b());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
template<typename T, std::size_t SIZE>
void inline store(T const* __restrict a, Type<T, SIZE> const& __restrict b) noexcept
{
MKN_AVX_FN_COUNTER;
Type<T, SIZE>::set_func_ptr(const_cast<T*>(a), b());
}
template<typename T, std::size_t SIZE>
void inline store(T* __restrict a, Type<T, SIZE> const& __restrict b) noexcept
{
MKN_AVX_FN_COUNTER;
Type<T, SIZE>::set_func_ptr(a, b());
}

Comment thread test/betterbench_avx.cpp
Comment on lines +58 to +60
if (d3[arrs - 1][3] != b3)
throw std::runtime_error("FAIL!");
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider returning a non-zero exit code instead of throwing.

Static analysis correctly flags that throwing std::runtime_error in main() results in an unhandled exception, leading to std::terminate. For a benchmark/test executable, returning a non-zero exit code provides cleaner failure semantics.

Proposed fix
     if (d3[arrs - 1][3] != b3)
-        throw std::runtime_error("FAIL!");
-};
+    {
+        std::cerr << "FAIL!" << std::endl;
+        return 1;
+    }
+    return 0;
+}

Also note the trailing semicolon after the closing brace on line 60, which should be removed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (d3[arrs - 1][3] != b3)
throw std::runtime_error("FAIL!");
};
if (d3[arrs - 1][3] != b3)
{
std::cerr << "FAIL!" << std::endl;
return 1;
}
return 0;
}
🧰 Tools
🪛 Cppcheck (2.19.0)

[error] 59-59: Unhandled exception thrown in function that is an entry point.

(throwInEntryPoint)

🤖 Prompt for AI Agents
In @test/betterbench_avx.cpp around lines 58 - 60, Replace the throw with a
non-zero process exit and remove the stray semicolon: where the code currently
does `if (d3[arrs - 1][3] != b3) throw std::runtime_error("FAIL!");` change it
to return a non-zero value (e.g. `return EXIT_FAILURE;` or `return 1;`) so the
program exits cleanly with failure semantics (add `#include <cstdlib>` if using
`EXIT_FAILURE`), and delete the trailing semicolon after the closing brace to
remove the extraneous token.

Comment thread test/betterbench.hpp
Comment on lines +98 to +111
else
{
auto constexpr static N = mkn::avx::Options::N<T>();

// NO AVX FOR AOS
for (std::size_t i = 0; i < size(); i += N)
{
for (std::size_t j = 0; j < N; ++j)
{
std::size_t off = i + j;
op<T>(v.data()[off].v0, v.data()[off].v1, v.data()[off].v2, v.data()[off].v3);
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Potential out-of-bounds access when size() is not divisible by N.

In the else branch, the outer loop increments by N and the inner loop iterates j from 0 to N. If size() is not evenly divisible by N, the last iteration will compute off = i + j where off >= size(), causing out-of-bounds access.

Proposed fix
     else
     {
         auto constexpr static N = mkn::avx::Options::N<T>();

         // NO AVX FOR AOS
-        for (std::size_t i = 0; i < size(); i += N)
+        for (std::size_t i = 0; i < size(); ++i)
         {
-            for (std::size_t j = 0; j < N; ++j)
-            {
-                std::size_t off = i + j;
-                op<T>(v.data()[off].v0, v.data()[off].v1, v.data()[off].v2, v.data()[off].v3);
-            }
+            op<T>(v.data()[i].v0, v.data()[i].v1, v.data()[i].v2, v.data()[i].v3);
         }
     }

Alternatively, if the intent is to process in blocks of N, add a bounds check or ensure size() is always a multiple of N.

Comment thread test/betterbench.hpp
Comment on lines +123 to +133
else
{
auto constexpr static N = mkn::avx::Options::N<V>();

// AVX!!
for (std::size_t i = 0; i < size(); i += N)
{
auto&& [a0, a1, a2, a3] = mkn::avx::make_spans<N>(i, v0(), v1(), v2(), v3());
op<T>(a0, a1, a2, a3);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Same out-of-bounds risk when size() is not divisible by N.

The AVX path iterates with step N but doesn't handle the remainder when size() is not evenly divisible by N. This will cause make_spans to read past the buffer.

Proposed fix
     else
     {
         auto constexpr static N = mkn::avx::Options::N<V>();
+        auto const aligned_size = size() - (size() % N);

         // AVX!!
-        for (std::size_t i = 0; i < size(); i += N)
+        for (std::size_t i = 0; i < aligned_size; i += N)
         {
             auto&& [a0, a1, a2, a3] = mkn::avx::make_spans<N>(i, v0(), v1(), v2(), v3());
             op<T>(a0, a1, a2, a3);
         }
+        // Handle remainder with scalar operations
+        for (std::size_t i = aligned_size; i < size(); ++i)
+            op<T>(v0().data()[i], v1().data()[i], v2().data()[i], v3().data()[i]);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else
{
auto constexpr static N = mkn::avx::Options::N<V>();
// AVX!!
for (std::size_t i = 0; i < size(); i += N)
{
auto&& [a0, a1, a2, a3] = mkn::avx::make_spans<N>(i, v0(), v1(), v2(), v3());
op<T>(a0, a1, a2, a3);
}
}
else
{
auto constexpr static N = mkn::avx::Options::N<V>();
auto const aligned_size = size() - (size() % N);
// AVX!!
for (std::size_t i = 0; i < aligned_size; i += N)
{
auto&& [a0, a1, a2, a3] = mkn::avx::make_spans<N>(i, v0(), v1(), v2(), v3());
op<T>(a0, a1, a2, a3);
}
// Handle remainder with scalar operations
for (std::size_t i = aligned_size; i < size(); ++i)
op<T>(v0().data()[i], v1().data()[i], v2().data()[i], v3().data()[i]);
}

@PhilipDeegan
PhilipDeegan force-pushed the windowz branch 2 times, most recently from 4359fb8 to 1f90485 Compare January 8, 2026 15:28
@PhilipDeegan

PhilipDeegan commented Jan 9, 2026

Copy link
Copy Markdown
Member Author

focus on the compile errors in the actions please @CodeRabbit

@coderabbitai

coderabbitai Bot commented Jan 9, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Get workflow runs for this PR
echo "=== Workflow Runs for PR #13 ==="
gh pr checks 13 --json name,status,conclusion,detailsUrl

echo -e "\n=== Recent workflow runs ==="
gh run list --limit 10 --json databaseId,name,status,conclusion,headBranch,createdAt | jq '.[] | select(.headBranch == "windowz")'

echo -e "\n=== Getting failed run logs ==="
# Get the most recent failed runs
FAILED_RUNS=$(gh run list --branch windowz --limit 5 --json databaseId,conclusion,status,name | jq -r '.[] | select(.conclusion == "failure") | .databaseId' | head -3)

for run_id in $FAILED_RUNS; do
    echo -e "\n=== Logs for run ID: $run_id ==="
    gh run view "$run_id" --log-failed | head -200
done

Length of output: 39394


@PhilipDeegan I've identified the compile errors in the Windows build. Here's what's happening:

Windows Build Failure Analysis

The Windows build is failing with linker errors (LNK2019: unresolved external symbol), while the Ubuntu builds succeed. The errors are all related to AVX intrinsics:

Unresolved symbols:

  • _mm256_add_pd, _mm256_add_ps
  • _mm256_div_pd
  • _mm256_mul_pd, _mm256_mul_ps
  • _mm256_sub_pd
  • _mm256_store_pd, _mm256_store_ps
  • _mm256_set1_pd, _mm256_set1_ps

Root Cause

The issue stems from inc/mkn/avx/url.hpp which assigns AVX intrinsics to function pointers:

static constexpr auto add_func_ptr = _mm256_add_pd;
static constexpr auto mul_func_ptr = _mm256_mul_pd;
// etc.

On MSVC, AVX intrinsics are inline pseudo-functions recognized by the compiler and replaced directly with machine code. They are not actual callable functions with linkable symbols. When you take their address (assign to function pointer), MSVC expects a real symbol in a library, which doesn't exist.

This pattern works fine on GCC/Clang where intrinsics can be treated as regular functions, but fails on MSVC.

Solution Options

  1. Wrap intrinsics in inline functions instead of using function pointers
  2. Use macros or templates to dispatch to intrinsics directly
  3. Add MSVC-specific code paths that avoid function pointers
  4. Create actual wrapper functions (though this may impact performance)

Would you like me to investigate the codebase further to suggest a specific fix approach?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @inc/mkn/avx/url.hpp:
- Line 84: The static member assignment for fma (auto const static inline fma)
directly binds to the AVX intrinsic _mm256_fmadd_pd which on MSVC produces
linker errors; change the assignment to match the other intrinsic members by
wrapping the call in a lambda that forwards its arguments to _mm256_fmadd_pd
(same pattern used for the other ops around lines 77-82), so fma becomes an
inline lambda taking the intended parameters and returning the intrinsic result,
avoiding creating a non-linkable symbol.
- Around line 48-49: The const overload of operator[] is inconsistent and
invalid because it tries to subscript the AVX register member `array` directly;
change the const `auto& operator[](std::size_t i) const noexcept` to mirror the
non-const version by returning `reinterpret_cast<const T*>(&array)[i]` (i.e.,
cast `&array` to `const T*` and index it) so both overloads return the same
element type and remain noexcept.
🧹 Nitpick comments (2)
inc/mkn/avx/url.hpp (2)

243-269: Consider consistent access pattern for operations.

The arithmetic operators use inconsistent access patterns:

  • operator+ and operator* use Type<T, SIZE>::add/mul (lines 247, 261)
  • operator- and operator/ use Type<T, SIZE>::Super::impl_type::sub/div (lines 254, 268)

Both approaches work, but consistency would improve maintainability.

♻️ Proposed fix for consistency
 Type<T, SIZE> inline operator-(Type<T, SIZE> const& a, Type<T, SIZE> const& b) noexcept
 {
     MKN_AVX_FN_COUNTER;
-    return {Type<T, SIZE>::Super::impl_type::sub(a(), b())};
+    return {Type<T, SIZE>::sub(a(), b())};
 }

 template<typename T, std::size_t SIZE>
 Type<T, SIZE> inline operator/(Type<T, SIZE> const& a, Type<T, SIZE> const& b) noexcept
 {
     MKN_AVX_FN_COUNTER;
-    return {Type<T, SIZE>::Super::impl_type::div(a(), b())};
+    return {Type<T, SIZE>::div(a(), b())};
 }

315-320: Consider adding MKN_AVX_FN_COUNTER to fma function.

The fma function lacks the MKN_AVX_FN_COUNTER instrumentation present in all arithmetic operators (lines 246, 253, 260, 267, 274, 281, 288, 295, 303, 310). If call counting is desired for profiling, this should be consistent.

♻️ Proposed addition
 template<typename T, std::size_t SIZE>
 Type<T, SIZE> inline fma(Type<T, SIZE> const& a, Type<T, SIZE> const& b,
                          Type<T, SIZE> const& c) noexcept
 {
+    MKN_AVX_FN_COUNTER;
     return {Type<T, SIZE>::Super::impl_type::fma(a(), b(), c())};
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4359fb8 and 8ae4b14.

📒 Files selected for processing (6)
  • .github/workflows/build_nix.yml
  • .github/workflows/build_win.yml
  • inc/mkn/avx/lazy.hpp
  • inc/mkn/avx/url.hpp
  • mkn.yaml
  • test/test_lazy.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • inc/mkn/avx/lazy.hpp
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: PhilipDeegan
Repo: mkn/mkn.avx PR: 12
File: test/bench.cpp:97-99
Timestamp: 2025-03-23T21:51:48.782Z
Learning: Do not comment on pipeline failures or CI build issues for the mkn.avx repository. The user prefers to handle these issues separately from code reviews.
📚 Learning: 2025-03-16T19:40:42.661Z
Learnt from: PhilipDeegan
Repo: mkn/mkn.avx PR: 11
File: inc/mkn/avx/array.hpp:185-190
Timestamp: 2025-03-16T19:40:42.661Z
Learning: In the mkn.avx library, separate type parameters (T0, T1) are used in template functions to handle const/non-const type variations rather than for actual different types. If the types are incompatible, the code will fail to compile.

Applied to files:

  • inc/mkn/avx/url.hpp
📚 Learning: 2025-03-16T14:42:12.355Z
Learnt from: PhilipDeegan
Repo: mkn/mkn.avx PR: 11
File: inc/mkn/avx/array.hpp:93-96
Timestamp: 2025-03-16T14:42:12.355Z
Learning: In the mkn::avx Array class, the move constructor is implemented as a call to the copy constructor because the base class _A_ has a deleted move constructor. This design choice was made to ensure the class can be returned from functions while maintaining the constraint that the underlying array cannot be moved.

Applied to files:

  • inc/mkn/avx/url.hpp
🧬 Code graph analysis (1)
inc/mkn/avx/url.hpp (2)
inc/mkn/avx/array.hpp (16)
  • arr (118-123)
  • arr (118-118)
  • arr (124-129)
  • arr (124-124)
  • arr (132-137)
  • arr (132-132)
  • arr (138-143)
  • arr (138-138)
  • arr (146-151)
  • arr (146-146)
  • arr (152-157)
  • arr (152-152)
  • arr (160-165)
  • arr (160-160)
  • arr (166-171)
  • arr (166-166)
inc/mkn/avx/dbg.hpp (1)
  • i (45-49)
🪛 Clang (14.0.6)
inc/mkn/avx/url.hpp

[error] 6-6: 'cstdint' file not found

(clang-diagnostic-error)

🔇 Additional comments (11)
test/test_lazy.cpp (3)

11-11: LGTM! Good move to centralized alignment configuration.

Using Options::ALIGN() consistently across all vector typedefs improves maintainability and allows compile-time alignment overrides.


16-16: Verify that tests are not built with NDEBUG defined.

Switching from abort_if_not to assert is cleaner, but assert is a no-op when NDEBUG is defined. Ensure that test builds do not define NDEBUG, or test assertions will be silently skipped.


110-110: Useful diagnostic for identifying test output.

.github/workflows/build_win.yml (1)

1-30: Windows CI workflow looks well-structured.

The workflow includes:

  • Proper concurrency control to cancel in-progress runs on new commits.
  • MSVC toolchain setup with AMD64 architecture.
  • Documented workaround for the /bin/link conflict.
.github/workflows/build_nix.yml (1)

21-21: LGTM! Build targets updated to match new profiles.

The changes align the CI command with the new test profiles (test, b0, b1) defined in mkn.yaml, and the added -DKUL_FORCE_TRACE flag is consistent with the Windows workflow.

mkn.yaml (2)

12-37: Profile structure is clean and well-organized.

The new profiles (test_lazy, b0, b1) follow the existing pattern, and having b0/b1 share the same source file with different compile-time flags is a sensible approach for benchmark variants.


1-1: Comment on line 1 references undefined dbg profile in this file.

The comment includes dbg in the directive flags, but no profile with name: dbg is defined in mkn.yaml. The profiles test, test_lazy, and gbench are all present, but dbg is not. Since mkn.yaml lists mkn.kul as a parent dependency (external), the dbg profile may be inherited from that parent or be a built-in profile in the mkn build system. Clarify whether this is intentional or if the reference should be removed.

inc/mkn/avx/url.hpp (4)

1-29: LGTM: Primary template and fallback operations are well-structured.

The primary template correctly provides non-AVX fallback operations for SIZE==1, with appropriate static_assert to catch missing specializations.


104-144: LGTM: Float specializations consistently wrap intrinsics.

All float specializations correctly wrap AVX intrinsics in lambdas, avoiding MSVC linker issues.


149-190: LGTM: Integer specializations correctly wrap intrinsics.

All integer type specializations consistently wrap AVX intrinsics in lambdas.


220-241: Verify Type wrapper works on MSVC after fixing line 84.

The Type wrapper correctly aliases operations from Type_<T, SIZE> specializations. Once the direct intrinsic assignment on line 84 is fixed, verify that this aliasing approach works correctly on MSVC.

Comment thread inc/mkn/avx/url.hpp
Comment on lines +48 to +49
auto& operator[](std::size_t i) noexcept { return reinterpret_cast<T*>(&array)[i]; }
auto& operator[](std::size_t i) const noexcept { return array[i]; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix inconsistent const operator[] implementation.

The non-const operator[] uses reinterpret_cast<T*>(&array)[i] to access elements, but the const version returns array[i]. This inconsistency means the const version attempts to subscript the AVX register type directly, which is invalid.

🐛 Proposed fix
-    auto& operator[](std::size_t i) const noexcept { return array[i]; }
+    auto const& operator[](std::size_t i) const noexcept { return reinterpret_cast<T const*>(&array)[i]; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto& operator[](std::size_t i) noexcept { return reinterpret_cast<T*>(&array)[i]; }
auto& operator[](std::size_t i) const noexcept { return array[i]; }
auto& operator[](std::size_t i) noexcept { return reinterpret_cast<T*>(&array)[i]; }
auto const& operator[](std::size_t i) const noexcept { return reinterpret_cast<T const*>(&array)[i]; }
🤖 Prompt for AI Agents
In @inc/mkn/avx/url.hpp around lines 48 - 49, The const overload of operator[]
is inconsistent and invalid because it tries to subscript the AVX register
member `array` directly; change the const `auto& operator[](std::size_t i) const
noexcept` to mirror the non-const version by returning `reinterpret_cast<const
T*>(&array)[i]` (i.e., cast `&array` to `const T*` and index it) so both
overloads return the same element type and remain noexcept.

Comment thread inc/mkn/avx/url.hpp
auto const static inline set = [](auto&&... v) { return _mm256_store_pd(v...); };
auto const static inline set_v = [](auto&&... v) { return _mm256_set1_pd(v...); };

auto const static inline fma = _mm256_fmadd_pd;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix inconsistent intrinsic assignment causing MSVC linker errors.

Line 84 directly assigns _mm256_fmadd_pd without wrapping it in a lambda, unlike all other intrinsic operations (lines 77-82, and throughout other specializations). On MSVC, AVX intrinsics are inline pseudo-functions without linkable symbols; directly assigning them to static members causes the linker to expect a real symbol, resulting in LNK2019 errors.

This inconsistency is the root cause of the Windows CI failures mentioned in the PR objectives.

🐛 Proposed fix to wrap in lambda
-    auto const static inline fma = _mm256_fmadd_pd;
+    auto const static inline fma = [](auto&&... v) { return _mm256_fmadd_pd(v...); };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
auto const static inline fma = _mm256_fmadd_pd;
auto const static inline fma = [](auto&&... v) { return _mm256_fmadd_pd(v...); };
🤖 Prompt for AI Agents
In @inc/mkn/avx/url.hpp at line 84, The static member assignment for fma (auto
const static inline fma) directly binds to the AVX intrinsic _mm256_fmadd_pd
which on MSVC produces linker errors; change the assignment to match the other
intrinsic members by wrapping the call in a lambda that forwards its arguments
to _mm256_fmadd_pd (same pattern used for the other ops around lines 77-82), so
fma becomes an inline lambda taking the intended parameters and returning the
intrinsic result, avoiding creating a non-linkable symbol.

@PhilipDeegan
PhilipDeegan deleted the windowz branch July 25, 2026 14:04
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.

1 participant