Skip to content

fix(linalg): register missing Int16/Uint16/Bool rows in CPU Outer dispatch (fixes segfault)#1099

Closed
yingjerkao wants to merge 1 commit into
masterfrom
fix/outer-16bit-bool-dispatch
Closed

fix(linalg): register missing Int16/Uint16/Bool rows in CPU Outer dispatch (fixes segfault)#1099
yingjerkao wants to merge 1 commit into
masterfrom
fix/outer-16bit-bool-dispatch

Conversation

@yingjerkao

Copy link
Copy Markdown
Collaborator

Problem

linalg::Outer segfaults for Int16, Uint16, or Bool inputs on the CPU.

Outer.cpp promotes both operands to a common dtype and dispatches through
Outer_ii[out_dtype][out_dtype]. The CPU Outer_ii dispatch table in
linalg_internal_interface.cpp was never populated with the Int16, Uint16,
or Bool source rows
— every other source dtype has a full 11-entry row, but
these three had none. So when the promoted dtype is Int16/Uint16/Bool,
Outer_ii[out_dtype][out_dtype] is a null function pointer, and the call jumps
to address 0:

AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x000000000000 ...)
    #1 ... TestBody tests/linalg_test/Outer_test.cpp:32   // the linalg::Outer(a, b) call

Every sibling dispatch table in the same constructor — MM_ii, Sum_ii,
Diag_ii, Matmul_ii, Matvec_ii, and even the GPU cuOuter_ii — already
registers these dtypes, so the omission is an oversight, not intent. (The GPU
path works; this is CPU-only.)

Reproduces on master (86cb96c). Minimal repro:

import cytnx
a = cytnx.zeros([5], cytnx.Type.Int16); b = cytnx.zeros([7], cytnx.Type.Int16)
cytnx.linalg.Outer(a, b)   # SIGSEGV

Fix

Register the 33 missing rows (Int16, Uint16, Bool sources × 11 target
dtypes). The Outer_internal_{i16,u16,b}t* implementations already exist and
are declared — this is registration only, no kernel or algorithm change.

Testing

  • New tests/linalg_test/Outer_test.cpp (OuterDtypeCoverage: Int16,
    Uint16, Bool, Int16xBool) checks independently hand-computed values
    (dtype and value, with negative and boolean cases).
  • Regression discipline: on the pre-fix binary these crash with the ASan
    SEGV above at the linalg::Outer call site; post-fix all four pass.
  • Existing DtypePromotion.OuterComplexfloatDouble and LinalgKronTest tests
    still pass. clang-format (v14) clean.

…patch

linalg::Outer segfaulted for Int16, Uint16, or Bool CPU inputs. The
CPU Outer_ii dispatch table in linalg_internal_interface.cpp was never
populated with the Int16/Uint16/Bool *source* rows (every other source
dtype has a full 11-entry row; these three had none). Outer.cpp casts
both operands to the promoted dtype and dispatches through
Outer_ii[out_dtype][out_dtype]; when out_dtype is Int16/Uint16/Bool that
entry was a null function pointer, so the call jumped to address 0 and
crashed. Every sibling table (MM_ii, Sum_ii, Diag_ii, Matmul_ii,
Matvec_ii, and GPU cuOuter_ii) already registers these dtypes, so the
omission was an oversight, not intent.

Fix: register the 33 missing rows. The Outer_internal_{i16,u16,b}t*
implementations already exist and are declared -- this is registration
only, no kernel or algorithm change.

Testing: new tests/linalg_test/Outer_test.cpp (OuterDtypeCoverage:
Int16, Uint16, Bool, Int16xBool) checks independently hand-computed
values. On the pre-fix binary these crash with an ASan SEGV at address
0x0 (null function-pointer call) at the linalg::Outer call site;
post-fix all four pass. Existing DtypePromotion.OuterComplexfloatDouble
and LinalgKronTest tests still pass. clang-format (v14) clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request registers the missing Int16, Uint16, and Bool source rows in the CPU Outer_ii dispatch table to prevent null function pointer dereferences and crashes when using these types as the left operand in linalg::Outer. It also adds comprehensive regression tests in tests/linalg_test/Outer_test.cpp to verify correctness across these data types. The review feedback points out several instances in the new test file where legacy cytnx_uint64 typedefs are used as loop counters, which violates the repository style guide's preference for standard C++ types like size_t or uint64_t in new code.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +37 to +38
for (cytnx_uint64 i = 0; i < 3; i++)
for (cytnx_uint64 j = 0; j < 2; j++)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository style guide, legacy cytnx_XXXX typedefs (such as cytnx_uint64) should not be propagated as ordinary loop counters in new or touched code. Standard C++ types like size_t or uint64_t should be preferred instead.

Suggested change
for (cytnx_uint64 i = 0; i < 3; i++)
for (cytnx_uint64 j = 0; j < 2; j++)
for (size_t i = 0; i < 3; i++)
for (size_t j = 0; j < 2; j++)
References
  1. For new or touched code, avoid propagating legacy cytnx_XXXX typedefs as ordinary loop counters, sizes, flags, or local arithmetic types. Prefer standard C++ types unless the value is specifically a dtype-backed tensor/storage scalar. (link)

Comment on lines +58 to +59
for (cytnx_uint64 i = 0; i < 2; i++)
for (cytnx_uint64 j = 0; j < 3; j++)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository style guide, legacy cytnx_XXXX typedefs (such as cytnx_uint64) should not be propagated as ordinary loop counters in new or touched code. Standard C++ types like size_t or uint64_t should be preferred instead.

Suggested change
for (cytnx_uint64 i = 0; i < 2; i++)
for (cytnx_uint64 j = 0; j < 3; j++)
for (size_t i = 0; i < 2; i++)
for (size_t j = 0; j < 3; j++)
References
  1. For new or touched code, avoid propagating legacy cytnx_XXXX typedefs as ordinary loop counters, sizes, flags, or local arithmetic types. Prefer standard C++ types unless the value is specifically a dtype-backed tensor/storage scalar. (link)

Comment on lines +80 to +81
for (cytnx_uint64 i = 0; i < 2; i++)
for (cytnx_uint64 j = 0; j < 3; j++)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository style guide, legacy cytnx_XXXX typedefs (such as cytnx_uint64) should not be propagated as ordinary loop counters in new or touched code. Standard C++ types like size_t or uint64_t should be preferred instead.

Suggested change
for (cytnx_uint64 i = 0; i < 2; i++)
for (cytnx_uint64 j = 0; j < 3; j++)
for (size_t i = 0; i < 2; i++)
for (size_t j = 0; j < 3; j++)
References
  1. For new or touched code, avoid propagating legacy cytnx_XXXX typedefs as ordinary loop counters, sizes, flags, or local arithmetic types. Prefer standard C++ types unless the value is specifically a dtype-backed tensor/storage scalar. (link)

Comment on lines +100 to +101
for (cytnx_uint64 i = 0; i < 2; i++)
for (cytnx_uint64 j = 0; j < 2; j++)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the repository style guide, legacy cytnx_XXXX typedefs (such as cytnx_uint64) should not be propagated as ordinary loop counters in new or touched code. Standard C++ types like size_t or uint64_t should be preferred instead.

Suggested change
for (cytnx_uint64 i = 0; i < 2; i++)
for (cytnx_uint64 j = 0; j < 2; j++)
for (size_t i = 0; i < 2; i++)
for (size_t j = 0; j < 2; j++)
References
  1. For new or touched code, avoid propagating legacy cytnx_XXXX typedefs as ordinary loop counters, sizes, flags, or local arithmetic types. Prefer standard C++ types unless the value is specifically a dtype-backed tensor/storage scalar. (link)

@codecov

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.77%. Comparing base (86cb96c) to head (3a71c72).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1099      +/-   ##
==========================================
+ Coverage   72.71%   72.77%   +0.05%     
==========================================
  Files         226      226              
  Lines       28151    28184      +33     
  Branches       71       71              
==========================================
+ Hits        20471    20510      +39     
+ Misses       7659     7653       -6     
  Partials       21       21              
Flag Coverage Δ
cpp 72.89% <100.00%> (+0.05%) ⬆️
python 64.13% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
C++ backend 72.00% <100.00%> (+0.06%) ⬆️
Python bindings 77.70% <ø> (ø)
Python package 64.13% <ø> (ø)
Files with missing lines Coverage Δ
src/backend/linalg_internal_interface.cpp 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 86cb96c...3a71c72. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@IvanaGyro IvanaGyro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The comments from bots are worth to be resolved. Except that LGTM.

@ianmccul

ianmccul commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Outer.cpp casts both operands to out_dtype, then calls Outer_ii[out_dtype][out_dtype]. Only the three missing diagonal entries are reachable; the other 30 registrations are harmless but dead. Likewise, Int16TimesBool does not exercise Outer_ii[Int16][Bool].

I'm wondering why this PR doesn't replace the whole mechanism by a much simpler implementation. src/linalg/Outer.cpp:24 rejects either operand unless shape().size() == 1. There is only a Tensor overload. So:

Tensor Outer(const Tensor &lhs, const Tensor &rhs) {
  cytnx_error_msg(lhs.is_void(), "[ERROR] lhs must be initialized.%s", "\n");
  cytnx_error_msg(rhs.is_void(), "[ERROR] rhs must be initialized.%s", "\n");
  cytnx_error_msg(lhs.device() != rhs.device(),
                  "[ERROR] the two tensors must be on the same device.%s", "\n");
  cytnx_error_msg(lhs.rank() != 1, "[ERROR] tensor #1 should have rank-1.%s", "\n");
  cytnx_error_msg(rhs.rank() != 1, "[ERROR] tensor #2 should have rank-1.%s", "\n");

  return Kron(lhs, rhs).reshape(std::vector<cytnx_uint64>{lhs.shape()[0], rhs.shape()[0]});
}

@ianmccul ianmccul left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

see top-level comment above

@yingjerkao

Copy link
Copy Markdown
Collaborator Author

Addressing @ianmccul's review comment: rather than register the missing Int16/Uint16/Bool rows, #1105 reimplements Outer as Kron(a, b).reshape({m, n}) and retires the per-dtype Outer_ii/cuOuter_ii dispatch entirely. Kron's variant-based dispatch already covers those dtype pairs on CPU and GPU, so the missing-row segfault this PR fixed can no longer occur. This PR is superseded by #1105.

@ianmccul ianmccul left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hmm, not sure how to 'unsubmit' approval but it doesn't matter, I assume this PR is no longer relevant

@ianmccul
ianmccul self-requested a review July 21, 2026 09:50
@yingjerkao

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #1105.

This PR registered the missing Outer_ii[Int16/Uint16/Bool][…] rows in the CPU Outer dispatch table to fix a segfault on those source dtypes. Since it was approved, #1105 landed and reworked Outer as Kron(a, b).reshape(...), deleting the Outer_ii/Outerfunc_oii/cuOuter_ii dispatch tables entirely.

As a result, on current master:

  • The segfault is already fixedOuter now routes through Kron, whose variant-based type_promote_from_pointer_t dispatch covers every dtype pair, so there's no null dispatch entry to crash on. Verified: Outer(Int16/Uint16/Bool, Double) and Outer(Bool, Bool) all return correct results.
  • The dispatch registration this PR adds has nowhere to go — the table it patches no longer exists.
  • The dtype coverage is already testedtests/linalg_test/Outer_test.cpp on master (added by refactor(linalg): implement Outer as Kron+reshape, retire Outer dispatch (#1003) #1105) exercises the Int16/Uint16/Bool cases.

Nothing here is left to salvage, so closing rather than rebasing. Thanks!

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