Skip to content

Fix UB in float-to-unsigned scalar type conversions#1283

Open
shivansh023023 wants to merge 1 commit intoNVIDIA:mainfrom
shivansh023023:fix/uint8-cast-ub
Open

Fix UB in float-to-unsigned scalar type conversions#1283
shivansh023023 wants to merge 1 commit intoNVIDIA:mainfrom
shivansh023023:fix/uint8-cast-ub

Conversation

@shivansh023023
Copy link
Copy Markdown

@shivansh023023 shivansh023023 commented Mar 11, 2026

Description

This PR fixes a bug causing undefined behavior when casting negative float values to unsigned integer scalar types (like wp.uint8) inside Warp kernels.
When wp.uint8(float_val) is used, the codegen emitted a direct C-style functional cast wp::uint8(float_val). Since uint8 maps directly to uint8_t (unsigned char), casting a float with a negative truncated value to this unsigned type triggers undefined behavior per the C++ standard (§7.3.11).

  • Fix: For float* -> uint* pairs during codegen, the emitted C++ code now routes via custom safe-cast functional overloads in builtin.h (e.g., float32_to_uint8). These explicitly perform a two-step cast: float -> int64 -> unsigned, leveraging C++'s well-defined modular arithmetic rule for signed-to-unsigned conversion.
  • Test Enabled: Re-enabled the test_type_closure_scalar case for uint8 in test_codegen_instancing.py:1099.
    Closes FIXME in warp/tests/test_codegen_instancing.py:1099.

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

Test plan

  • Ensured that python3 -m pytest warp/tests/test_codegen_instancing.py::TestCodeGenInstancing::test_type_closure_scalar_cpu -v passes.
  • Confirmed that explicit wrap-around bounds are handled correctly with arbitrary bounds:
    • wp.uint8(-0.1) yields 0
    • wp.uint8(255.1) yields 255
    • wp.uint8(128.0) yields 128
    • wp.uint8(-100.0) yields 156 (expected modular wrap-around)

Bug fix

import warp as wp
wp.init()
@wp.kernel
def cast_negative_float():
    x = wp.float32(-1.5)
    
    # Without this PR, mapping a negative float directly to 
    # unsigned integer triggers undefined behavior in C++
    y = wp.uint8(x)
    wp.printf("y = %d\\n", int(y))
wp.launch(cast_negative_float, dim=1)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Added safe, well-defined floatunsigned integer conversion routines (including float16 paths) with corresponding no-op adjoint support.

* **Tests**
  * Expanded scalar conversion tests: re-enabled and broadened uint8 cases and added uint64/float64 boundary coverage.

* **Notes**
  * Public APIs and documented behavior remain unchanged.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot bot commented Mar 11, 2026

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds safe float→unsigned conversion helpers in C++ and updates Python builtin generation to emit and reference safe native names/namespaces for float→unsigned casts; expands tests to exercise uint8/uint64 float-cast edge cases.

Changes

Cohort / File(s) Summary
Python builtin generation
warp/_src/builtins.py
Adds unsigned_int_types and float_src_types; computes safe_native names for float→unsigned casts (e.g., float32_to_uint8) and adjusts namespace and native_func resolution for scalar math constructors when safe conversions or bool dest apply.
C++ native conversions & adjoints
warp/native/builtin.h
Adds safe_float_to_int64/safe_float_to_uint64, float16/32/64 → uint8/16/32/64 conversion helpers, and corresponding no-op adjoint stubs; exposes new inline conversion functions in wp namespace.
Tests – type-closure / instancing
warp/tests/test_codegen_instancing.py
Re-enables and expands uint8 test cases and adds uint64 float64 closure to exercise large-value truncation and boundary cases.

Sequence Diagram(s)

sequenceDiagram
    participant User as Python user code
    participant BuiltinGen as WarpBuiltinGen
    participant Native as C++ Native (`wp`)
    participant AD as Adjoint/Runtime

    User->>BuiltinGen: request scalar cast (float -> unsigned)
    BuiltinGen->>BuiltinGen: detect float source & unsigned dest\ncompute safe_native (e.g., float32_to_uint8)\nset namespace/native_func accordingly
    BuiltinGen->>Native: reference resolved native function
    Native->>Native: perform safe conversion (NaN/bounds handling)
    Native->>AD: provide no-op adjoint stub
    AD->>User: gradient/adjoint behavior (no-op for these casts)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: fixing undefined behavior in float-to-unsigned scalar type conversions by implementing safe conversion helpers.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR fixes undefined behavior (C++ §7.3.11) when casting negative float values to unsigned integer scalar types (uint8, uint16, uint32, uint64) inside Warp kernels. The fix introduces a two-step intermediate-cast strategy (float → int64 → unsigned) via new safe_float_to_int64 / safe_float_to_uint64 helpers in builtin.h, and updates the codegen in builtins.py to emit these safe-cast function calls instead of direct C-style wp::uint8(x) calls. The approach is sound and well-structured.

Key points from the review:

  • repro_uint8_bug.py — a temporary debug/reproduction script has been committed to the repository root and should be removed before merging; its logic is already covered by the re-enabled official tests.
  • Untested critical code path — the sole uint64 test value (9223372036854774784.0, just below 2^63) does not exercise the newly added x >= pow2_63 branch in safe_float_to_uint64, which was the root cause of the previously flagged UB for float32_to_uint64. Tests for values in [2^63, 2^64), values >= 2^64 (saturation path), and negative float-to-uint64 conversions should be added.
  • The float16_to_uint* wrappers correctly route through float32_to_uint* after converting via float32(x), avoiding any double-conversion issues.
  • The half struct's implicit conversion operators (operator uint8() etc.) still use direct float-to-unsigned casts, but these are pre-existing and not introduced by this PR.

Confidence Score: 3/5

  • The core fix is logically correct but the critical upper-range branch of safe_float_to_uint64 (values in [2^63, 2^64)) has no test coverage, and a debug script should be removed before merging.
  • The safe_float_to_uint64 implementation correctly handles all four cases (NaN, negative, [0, 2^63), [2^63, 2^64), overflow), and the codegen wiring in builtins.py is clean. However, the x >= pow2_63 branch — the exact range that prompted the uint64 fix — is exercised by zero tests, which reduces confidence in correctness for that specific path. Additionally, a temporary debug script (repro_uint8_bug.py) is included in the diff and should be removed.
  • warp/tests/test_codegen_instancing.py needs additional uint64 test cases covering values >= 2^63. repro_uint8_bug.py should be deleted from the repository root.

Important Files Changed

Filename Overview
warp/native/builtin.h Adds safe float-to-unsigned integer conversion helpers (safe_float_to_int64, safe_float_to_uint64, and per-type wrapper functions). The core logic for safe_float_to_uint64 correctly handles NaN, negative, mid-range [0, 2^63), upper-range [2^63, 2^64), and overflow >= 2^64 cases. No-op adjoint stubs are added for all new functions. Note: the half struct's implicit operator uint8/16/32/64() conversion operators are not updated and still use direct float-to-unsigned casts, but these are pre-existing and not introduced by this PR.
warp/_src/builtins.py Adds the unsigned_int_types and float_src_types maps and wires the code generation loop to emit safe-cast function names (e.g. float32_to_uint8) rather than wp::uint8(x) for float→unsigned pairs. Python's built-in float is correctly aliased to "float32" in the map. Namespace is correctly cleared for safe-cast paths.
warp/tests/test_codegen_instancing.py Re-enables and expands the uint8 test cases and adds a uint64 boundary test with float64 precision. However, the single uint64 test value (9.22e18, just below 2^63) does not exercise the newly added x >= pow2_63 branch in safe_float_to_uint64, leaving the most critical new code path untested.
repro_uint8_bug.py Temporary debug/reproduction script committed to the repository root. Its logic is already covered by the re-enabled official test. Should be removed before merging.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["wp.uint8 / uint16 / uint32 / uint64\n(float input in kernel)"] --> B{"builtins.py\ncodegen loop:\nt in unsigned_int_types\nu in float_src_types?"}
    B -- "Yes" --> C["emit: float32_to_uint8(x)\n(safe_native function name,\nno wp:: namespace)"]
    B -- "No" --> D["emit: wp::uint8(x)\n(direct C++ cast)"]
    C --> E{"builtin.h\nsafe_float_to_int64(x)"}
    E -- "NaN" --> F["return 0"]
    E -- "x < -2^63" --> G["return INT64_MIN"]
    E -- "x >= 2^63" --> H["return INT64_MAX"]
    E -- "in range" --> I["static_cast<int64>(x)"]
    F & G & H & I --> J["static_cast<uintN>(int64_val)\n✅ well-defined signed→unsigned\nmodular arithmetic"]
    C --> K{"uint64 path?\nsafe_float_to_uint64(x)"}
    K -- "NaN" --> L["return 0"]
    K -- "x <= 0" --> M["route through\nsafe_float_to_int64(x)"]
    K -- "x >= 2^64" --> N["return UINT64_MAX\n(clamp/saturate)"]
    K -- "x >= 2^63" --> O["safe_float_to_int64(x - 2^63)\n+ 2^63\n✅ upper-half mapping"]
    K -- "0 < x < 2^63" --> P["safe_float_to_int64(x)\ncast to uint64"]
Loading

Comments Outside Diff (2)

  1. repro_uint8_bug.py, line 1-40 (link)

    Debug reproduction script committed to repository root

    This file is a local development/debugging script and should not be merged into the repository. Its logic is now fully covered by the re-enabled and expanded test_type_closure_scalar tests in warp/tests/test_codegen_instancing.py. The repro_ prefix makes it clear this was only ever intended to be a temporary artifact.

    Please remove this file before merging. The previous review cycle had the same concern about a temp_test_uint8_edge_cases.py file — repro_uint8_bug.py has the same issue.

  2. warp/tests/test_codegen_instancing.py, line 1117-1118 (link)

    safe_float_to_uint64 upper-half branch is untested

    The sole uint64 test case uses the value 9223372036854774784.0, which is approximately 2^63 - 3072 — a value that falls below pow2_63 in safe_float_to_uint64. This means the newly added x >= pow2_63 branch (lines 133–134 of builtin.h), which is the critical new code path for values in the range [2^63, 2^64), has no test coverage. The previous review comment about float32_to_uint64 UB prompted this code, but the fix for that exact range goes entirely untested.

    Consider adding tests for:

    • A value in [2^63, 2^64), e.g. 1e19 (≈ 10^19, which is > 2^63 ≈ 9.22e18 and < 2^64 ≈ 1.84e19)
    • A value >= 2^64 to exercise the saturation/clamp path (return 18446744073709551615ULL)
    • A negative float-to-uint64 conversion to exercise the x <= 0.0 branch
    # Values in [2^63, 2^64): exercises the upper-half branch of safe_float_to_uint64
    wp.launch(type_closure_kernel_uint64_f64, dim=1, inputs=[1e19, ...])  # expected: int(1e19) % 2^64
    
    # Values >= 2^64: exercises the saturation/clamp path
    wp.launch(type_closure_kernel_uint64_f64, dim=1, inputs=[2e19, 18446744073709551615.0])
    
    # Negative float: exercises the x <= 0.0 branch  
    wp.launch(type_closure_kernel_uint64_f64, dim=1, inputs=[-1.0, 18446744073709551615.0])

Last reviewed commit: 019ebf3

Comment on lines +116 to +117
CUDA_CALLABLE inline uint64 float32_to_uint64(float32 x) { return static_cast<uint64>(static_cast<int64>(x)); }
CUDA_CALLABLE inline uint64 float64_to_uint64(float64 x) { return static_cast<uint64>(static_cast<int64>(x)); }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

float_to_uint64 still triggers UB for large positive floats

The intermediate static_cast<int64>(x) is itself UB when x >= 2^63 (~9.22e18). Both float32 and float64 can represent values in the range [2^63, 2^64), which are perfectly valid uint64 values — but routing them through int64 causes the same class of undefined behaviour that this PR is trying to eliminate.

As an example, wp.uint64(1e19) would invoke float32_to_uint64(1e19f), and static_cast<int64>(1e19f) is UB because 1e19 exceeds INT64_MAX (~9.22e18).

A two-branch approach is needed:

CUDA_CALLABLE inline uint64 float32_to_uint64(float32 x) {
    // Values >= 2^63 cannot pass through int64 (would be UB),
    // so handle the upper half of uint64 range separately.
    constexpr float32 pow2_63 = 9.223372036854776e18f;
    if (x >= pow2_63) {
        return static_cast<uint64>(static_cast<int64>(x - pow2_63))
               + static_cast<uint64>(9223372036854775808ULL);
    }
    return static_cast<uint64>(static_cast<int64>(x));
}

CUDA_CALLABLE inline uint64 float64_to_uint64(float64 x) {
    constexpr float64 pow2_63 = 9.223372036854775808e18;
    if (x >= pow2_63) {
        return static_cast<uint64>(static_cast<int64>(x - pow2_63))
               + static_cast<uint64>(9223372036854775808ULL);
    }
    return static_cast<uint64>(static_cast<int64>(x));
}

The uint8/uint16/uint32 conversions are safe because the maximum representable value of each (255, 65535, 4294967295) is well within INT64_MAX, so the intermediate cast to int64 is always well-defined there.

Comment on lines +1 to +37
import warp as wp
import numpy as np

wp.init()

@wp.kernel
def test_uint8_edge_cases(
inputs: wp.array(dtype=float),
expected: wp.array(dtype=float),
):
tid = wp.tid()
val = inputs[tid]

# Cast float to uint8
casted = wp.uint8(val)

# Cast back to float for assertion
out = float(casted)
wp.expect_eq(out, expected[tid])

def run_test():
# Test values: -0.1, 255.1, 128.0, -100.0
# Expected results after float -> int64 -> uint8 conversion:
# -0.1 -> 0 -> 0
# 255.1 -> 255 -> 255
# 128.0 -> 128 -> 128
# -100.0 -> -100 -> 156 (since uint8(-100) = 256 - 100 = 156)
inputs = wp.array([-0.1, 255.1, 128.0, -100.0], dtype=float)
expected = wp.array([0.0, 255.0, 128.0, 156.0], dtype=float)

print("Launching test_uint8_edge_cases...")
wp.launch(test_uint8_edge_cases, dim=len(inputs), inputs=[inputs, expected])
wp.synchronize()
print("Test passed successfully!")

if __name__ == "__main__":
run_test()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Temporary debug script committed to repository root

This file appears to be a local development/debugging script and should not be committed to the repository. It duplicates logic already covered by the re-enabled test in warp/tests/test_codegen_instancing.py, and the temp_ prefix makes it clear it was only ever intended to be temporary.

Please remove this file before merging. Any edge-case values worth preserving (e.g., -0.1, 255.1, -100.0) should be added as additional wp.launch calls inside test_type_closure_scalar in the official test file.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
warp/_src/builtins.py (1)

1019-1031: ⚠️ Potential issue | 🟠 Major

The new float→unsigned path still leaves UB for NaN, infinity, and large positive values.

The helpers called at line 1019 (such as float64_to_uint64 in warp/native/builtin.h lines 109–122) route through static_cast<int64>(x) first. This cast invokes undefined behavior if x is NaN, infinity, or outside int64_t range. For uint64 targets, values in [2^63, 2^64) are still unsafe—the intermediate int64 cast fails before the conversion to uint64 ever begins. Guard the helpers with std::isfinite() checks and range validation, or avoid the signed intermediate for the positive branch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@warp/_src/builtins.py` around lines 1019 - 1031, The float→unsigned
conversion path currently uses helpers like float64_to_uint64 (referenced by the
safe_native variable in add_builtin calls) that perform a static_cast to int64_t
first, which is UB for NaN/inf or values outside int64 range; fix the native
helpers (e.g., float32_to_uint32/float64_to_uint64 in warp/native/builtin.h) to
either (A) explicitly check std::isfinite(x) and that x is within the target
unsigned range before casting and handle out-of-range/NaN/inf deterministically,
or (B) avoid the signed intermediate entirely by using unsigned-range-aware
logic (e.g., separate paths for x >= 0 using unsigned arithmetic), and keep
builtins.py’s safe_native usage unchanged so it points to the corrected, safe
helper functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@temp_test_uint8_edge_cases.py`:
- Around line 4-37: Move the ad-hoc script into the Warp test suite by
converting the standalone file into a unittest-style test in warp/tests (e.g.,
add a new test function in test_codegen_instancing.py or a new test_*.py),
remove wp.init(), run_test(), print() and __main__ logic, and register the
kernel test under the unittest harness; replace direct wp.launch/wp.synchronize
usages with either add_function_test(get_test_devices()) if you need
multi-device coverage or wrap kernel invocation in with wp.ScopedDevice(device):
and call wp.synchronize_device() after wp.launch, and keep the kernel name
test_uint8_edge_cases and the assertion wp.expect_eq unchanged so the same cases
are exercised.

In `@warp/native/builtin.h`:
- Around line 120-123: The four functions float16_to_uint8, float16_to_uint16,
float16_to_uint32, and float16_to_uint64 reference the float16 typedef before it
is defined; move these function definitions so they appear after the typedef
float16 (typedef half float16) or change them to operate on half instead and
then provide thin float16 wrappers after the typedef. Specifically, either
relocate the existing CUDA_CALLABLE inline float16_to_uint* functions to below
the float16 typedef, or replace their parameter type with half and call the
existing float32 conversion (e.g., float32(x)), then add wrapper functions named
float16_to_uint* that take float16 and forward to the half-based implementations
once the typedef is available.

---

Outside diff comments:
In `@warp/_src/builtins.py`:
- Around line 1019-1031: The float→unsigned conversion path currently uses
helpers like float64_to_uint64 (referenced by the safe_native variable in
add_builtin calls) that perform a static_cast to int64_t first, which is UB for
NaN/inf or values outside int64 range; fix the native helpers (e.g.,
float32_to_uint32/float64_to_uint64 in warp/native/builtin.h) to either (A)
explicitly check std::isfinite(x) and that x is within the target unsigned range
before casting and handle out-of-range/NaN/inf deterministically, or (B) avoid
the signed intermediate entirely by using unsigned-range-aware logic (e.g.,
separate paths for x >= 0 using unsigned arithmetic), and keep builtins.py’s
safe_native usage unchanged so it points to the corrected, safe helper
functions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 23e8749b-b715-4228-a334-8b590b82fb7b

📥 Commits

Reviewing files that changed from the base of the PR and between e6391e9 and 41c61ec.

📒 Files selected for processing (4)
  • temp_test_uint8_edge_cases.py
  • warp/_src/builtins.py
  • warp/native/builtin.h
  • warp/tests/test_codegen_instancing.py

Comment on lines +4 to +37
wp.init()

@wp.kernel
def test_uint8_edge_cases(
inputs: wp.array(dtype=float),
expected: wp.array(dtype=float),
):
tid = wp.tid()
val = inputs[tid]

# Cast float to uint8
casted = wp.uint8(val)

# Cast back to float for assertion
out = float(casted)
wp.expect_eq(out, expected[tid])

def run_test():
# Test values: -0.1, 255.1, 128.0, -100.0
# Expected results after float -> int64 -> uint8 conversion:
# -0.1 -> 0 -> 0
# 255.1 -> 255 -> 255
# 128.0 -> 128 -> 128
# -100.0 -> -100 -> 156 (since uint8(-100) = 256 - 100 = 156)
inputs = wp.array([-0.1, 255.1, 128.0, -100.0], dtype=float)
expected = wp.array([0.0, 255.0, 128.0, 156.0], dtype=float)

print("Launching test_uint8_edge_cases...")
wp.launch(test_uint8_edge_cases, dim=len(inputs), inputs=[inputs, expected])
wp.synchronize()
print("Test passed successfully!")

if __name__ == "__main__":
run_test()
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

Please fold this into the real Warp test suite instead of a standalone temp script.

The edge cases are useful, but this file is structured as an ad-hoc runner (wp.init(), run_test(), print(), __main__) rather than a normal Warp test, so it only exercises the default device and is easy for CI to miss. I'd move these cases into warp/tests/test_codegen_instancing.py or another warp/tests/test_*.py module and, if you still need an explicit flush for wp.expect_eq, do it under with wp.ScopedDevice(device): via wp.synchronize_device().

♻️ Suggested direction
- wp.init()
-
- `@wp.kernel`
- def test_uint8_edge_cases(
+ `@wp.kernel`
+ def uint8_edge_cases_kernel(
      inputs: wp.array(dtype=float),
      expected: wp.array(dtype=float),
  ):
      tid = wp.tid()
      val = inputs[tid]
-     casted = wp.uint8(val)
-     out = float(casted)
-     wp.expect_eq(out, expected[tid])
+     wp.expect_eq(float(wp.uint8(val)), expected[tid])

- def run_test():
-     inputs = wp.array([-0.1, 255.1, 128.0, -100.0], dtype=float)
-     expected = wp.array([0.0, 255.0, 128.0, 156.0], dtype=float)
-     print("Launching test_uint8_edge_cases...")
-     wp.launch(test_uint8_edge_cases, dim=len(inputs), inputs=[inputs, expected])
-     wp.synchronize()
-     print("Test passed successfully!")
-
- if __name__ == "__main__":
-     run_test()
+ def test_uint8_edge_cases(test, device):
+     with wp.ScopedDevice(device):
+         inputs = wp.array([-0.1, 255.1, 128.0, -100.0], dtype=float, device=device)
+         expected = wp.array([0.0, 255.0, 128.0, 156.0], dtype=float, device=device)
+         wp.launch(uint8_edge_cases_kernel, dim=len(inputs), inputs=[inputs, expected])
+         wp.synchronize_device()

Then register it with the existing Warp unittest harness instead of keeping it as a temporary script.

As per coding guidelines: warp/tests/test_*.py: "Use unittest, not pytest, for all test files in Warp" and "Use add_function_test() only when tests need to run across multiple devices via get_test_devices()"; **/*.py: "Use wp.synchronize_device() inside wp.ScopedDevice() contexts, not wp.synchronize()."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@temp_test_uint8_edge_cases.py` around lines 4 - 37, Move the ad-hoc script
into the Warp test suite by converting the standalone file into a unittest-style
test in warp/tests (e.g., add a new test function in test_codegen_instancing.py
or a new test_*.py), remove wp.init(), run_test(), print() and __main__ logic,
and register the kernel test under the unittest harness; replace direct
wp.launch/wp.synchronize usages with either
add_function_test(get_test_devices()) if you need multi-device coverage or wrap
kernel invocation in with wp.ScopedDevice(device): and call
wp.synchronize_device() after wp.launch, and keep the kernel name
test_uint8_edge_cases and the assertion wp.expect_eq unchanged so the same cases
are exercised.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
warp/native/builtin.h (1)

135-138: ⚠️ Potential issue | 🔴 Critical

float16_to_uint*() is declared before float16 exists.

typedef half float16; is only introduced at Line 215, so these inline definitions use an undeclared type and won't compile as written. Move them below the alias, or take half here and add thin float16 wrappers after the typedef.

#!/bin/bash
# Expect the four float16_to_uint* definitions to appear before `typedef half float16;`.
rg -n '\bfloat16_to_uint(8|16|32|64)\b|\btypedef\s+half\s+float16\b|\bstruct\s+half\b' warp/native/builtin.h
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@warp/native/builtin.h` around lines 135 - 138, The inline functions
float16_to_uint8, float16_to_uint16, float16_to_uint32, and float16_to_uint64
are declared before the typedef half float16 exists, causing an undeclared-type
compile error; move these four CUDA_CALLABLE inline definitions so they appear
after the typedef half float16; alternatively, change their parameter type to
half here and then add thin wrappers named float16_to_uint* after the typedef to
preserve the float16 API (refer to the symbols float16_to_uint8/16/32/64 and
typedef half float16 to locate the correct places).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@warp/native/builtin.h`:
- Around line 110-131: All of the float->unsigned helpers (float32_to_uint8,
float64_to_uint8, float32_to_uint16, float64_to_uint16, float32_to_uint32,
float64_to_uint32, float32_to_uint64, float64_to_uint64) perform a
static_cast<int64>(x) which is UB for NaN/±inf and for values outside int64
range; add explicit checks before that cast: use isfinite/isnan/isinf to handle
non-finite inputs, and check numeric bounds (for small unsigned types compare
against 0 and UINTn_MAX, for uint64 use the pow2_63 branch plus INT64_MAX
limits) and clamp or return a defined saturated value instead of casting when
out-of-range so the signed cast is never applied to invalid floats.

---

Duplicate comments:
In `@warp/native/builtin.h`:
- Around line 135-138: The inline functions float16_to_uint8, float16_to_uint16,
float16_to_uint32, and float16_to_uint64 are declared before the typedef half
float16 exists, causing an undeclared-type compile error; move these four
CUDA_CALLABLE inline definitions so they appear after the typedef half float16;
alternatively, change their parameter type to half here and then add thin
wrappers named float16_to_uint* after the typedef to preserve the float16 API
(refer to the symbols float16_to_uint8/16/32/64 and typedef half float16 to
locate the correct places).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 7da35086-d352-4b4c-b389-2c5d7a759e28

📥 Commits

Reviewing files that changed from the base of the PR and between 41c61ec and 577ce1a.

📒 Files selected for processing (2)
  • warp/native/builtin.h
  • warp/tests/test_codegen_instancing.py

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
warp/native/builtin.h (1)

140-144: ⚠️ Potential issue | 🔴 Critical

float16 type is used before it is defined — compilation error.

These functions reference float16 on lines 141-144, but the typedef typedef half float16; appears on line 221. This will cause a compilation error since float16 is not yet declared at this point.

🔧 Suggested fix: Move these functions after line 221 or use `half` directly

Option 1: Move these functions after line 221 (after typedef half float16;)

Option 2: Use half directly here:

-// Handle float16 source
-CUDA_CALLABLE inline uint8  float16_to_uint8 (float16 x) { return float32_to_uint8 (float32(x)); }
-CUDA_CALLABLE inline uint16 float16_to_uint16(float16 x) { return float32_to_uint16(float32(x)); }
-CUDA_CALLABLE inline uint32 float16_to_uint32(float16 x) { return float32_to_uint32(float32(x)); }
-CUDA_CALLABLE inline uint64 float16_to_uint64(float16 x) { return float32_to_uint64(float32(x)); }

Then add after line 221:

// Handle float16 source (after typedef)
CUDA_CALLABLE inline uint8  float16_to_uint8 (float16 x) { return float32_to_uint8 (float32(x)); }
CUDA_CALLABLE inline uint16 float16_to_uint16(float16 x) { return float32_to_uint16(float32(x)); }
CUDA_CALLABLE inline uint32 float16_to_uint32(float16 x) { return float32_to_uint32(float32(x)); }
CUDA_CALLABLE inline uint64 float16_to_uint64(float16 x) { return float32_to_uint64(float32(x)); }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@warp/native/builtin.h` around lines 140 - 144, The four conversion functions
float16_to_uint8, float16_to_uint16, float16_to_uint32, and float16_to_uint64
are declared before the typedef typedef half float16 and thus use an unknown
type; fix by either moving these four functions below the typedef (after the
float16 typedef) or change their parameter type to half (e.g., use half x) here
and keep the typedef placement—update the signatures consistently so the
compiler sees a defined type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@warp/native/builtin.h`:
- Around line 140-144: The four conversion functions float16_to_uint8,
float16_to_uint16, float16_to_uint32, and float16_to_uint64 are declared before
the typedef typedef half float16 and thus use an unknown type; fix by either
moving these four functions below the typedef (after the float16 typedef) or
change their parameter type to half (e.g., use half x) here and keep the typedef
placement—update the signatures consistently so the compiler sees a defined
type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 041eeb89-46a3-40dc-8bd0-4ee9a33d78ad

📥 Commits

Reviewing files that changed from the base of the PR and between 577ce1a and 0e0d11d.

📒 Files selected for processing (1)
  • warp/native/builtin.h

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@warp/native/builtin.h`:
- Around line 111-128: Replace the hard-coded decimal upper bound in
safe_float_to_int64 with a type-aware predecessor of 2^63: compute F pow2_63 =
static_cast<F>(9223372036854775808.0) and set max_int64 =
std::nextafter(pow2_63, static_cast<F>(-INFINITY)) (or std::nextafter(pow2_63,
static_cast<F>(0)) if -INFINITY isn't available) so the upper clamp is the
largest representable F less than 2^63; adjust safe_float_to_uint64 to reuse
this pow2_63 logic (keep the existing branches but use the computed pow2_63 and
max_uint64 derived for type F) and add regression tests for both float32 and
float64 at the 2^63 boundary to assert the correct conversions (referencing
safe_float_to_int64 and safe_float_to_uint64).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: edf31c49-0661-498e-bb74-4af722d52439

📥 Commits

Reviewing files that changed from the base of the PR and between 0e0d11d and 4af85a4.

📒 Files selected for processing (1)
  • warp/native/builtin.h

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
warp/tests/test_codegen_instancing.py (1)

1103-1103: Minor: test parameter is unused.

The static analysis flagged that the test parameter is unused since this function uses wp.expect_eq inside kernels rather than test.assertEqual(). This is consistent with other tests in the file (e.g., test_type_closure_vector) that follow the same pattern. No action required as this is part of the add_function_test convention.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@warp/tests/test_codegen_instancing.py` at line 1103, The `test` parameter in
the function test_type_closure_scalar is unused but intentionally present due to
the add_function_test convention; leave the parameter as-is and do not remove
it—ensure test_type_closure_scalar continues using wp.expect_eq inside kernels
rather than test.assertEqual so it matches the pattern used by other tests like
test_type_closure_vector.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@warp/tests/test_codegen_instancing.py`:
- Line 1103: The `test` parameter in the function test_type_closure_scalar is
unused but intentionally present due to the add_function_test convention; leave
the parameter as-is and do not remove it—ensure test_type_closure_scalar
continues using wp.expect_eq inside kernels rather than test.assertEqual so it
matches the pattern used by other tests like test_type_closure_vector.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: aeb0535b-2e47-4029-81ce-280f2820e4b3

📥 Commits

Reviewing files that changed from the base of the PR and between 4af85a4 and c1f91c8.

📒 Files selected for processing (2)
  • warp/native/builtin.h
  • warp/tests/test_codegen_instancing.py

Signed-off-by: shivansh023023 <singhshivansh023@gmail.com>
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