Skip to content

Fix generation of long-typed defines when on Linux#699

Open
Exanite wants to merge 19 commits into
dotnet:mainfrom
Exanite:fix/long-defines
Open

Fix generation of long-typed defines when on Linux#699
Exanite wants to merge 19 commits into
dotnet:mainfrom
Exanite:fix/long-defines

Conversation

@Exanite

@Exanite Exanite commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

For context, C long is output as C# nint on Linux.

This causes an issue where a 64-bit ulong value can be used as the initializer for a const field of type nint, leading to 2 errors:

  1. There is a missing type cast from long to nint, causing a compile error.
  2. Because C# doesn't know the bit width of nint until run time, this errors with Constant initializer must be compile-time constant after fixing the first error. This is specified here: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md

This PR fixes the issue by updating the generator to output the expected test output below, along with some additional test cases.

Current test outputs (matching main branch)

This is the current output of the new CLongDefinesTestUnix test case on Linux when ran against the main branch.

public static partial class Methods
{
    [NativeTypeName("#define SIZE_MAX (18446744073709551615UL)")]
    public const nuint SIZE_MAX = (18446744073709551615U);

    [NativeTypeName("#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX")]
    public const nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = (18446744073709551615U);

    [NativeTypeName("#define LONG_MAX __LONG_MAX__")]
    public const nint LONG_MAX = unchecked(9223372036854775807);

    [NativeTypeName("#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)")]
    public const nuint ULONG_MAX = (9223372036854775807 * 2U + 1U);
}

The new CLongDefinesTestWindows test case already passes and is there to ensure that no unintended changes occur to the Windows behavior.

Expected test outputs

The expected test outputs are my proposal for roughly what the expected bindings output should look like.

Notably, we need to add a type cast and change the field to be a static readonly field instead of a const field.

public static partial class Methods
{
    [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")]
    public static readonly nuint SIZE_MAX = unchecked((nuint)(18446744073709551615U));

    [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")]
    public static readonly nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = unchecked((nuint)(18446744073709551615U));

    [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")]
    public static readonly nint LONG_MAX = unchecked((nint)(9223372036854775807));

    [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")]
    public static readonly nuint ULONG_MAX = unchecked((nuint)(9223372036854775807 * 2U + 1U));
}

Remaining Tasks

I'll handle these tasks before undrafting.

  • Add tests for the "similar" case in my comment below
  • Identify other test cases to add
  • Address the todo comments I left
  • Package this branch and test against Silk.NET 3.0 on both Windows and Linux
    • Linux
    • Windows
  • Add test case for SDL_size_add_check_overflow and fix unnecessary unchecked statement

@Exanite

Exanite commented Jun 29, 2026

Copy link
Copy Markdown
Member Author

There is also a similar case with nint typed const fields:

Also from OpenCL:

#if INTPTR_MAX == INT32_MAX
#define CL_ICD2_TAG_KHR ((intptr_t)0x434C3331)
#else
#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331)
#endif

This generates as the following on both Windows and Linux:

[NativeTypeName("#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331)")]
public const nint CL_ICD2_TAG_KHR = unchecked((nint)(0x4F50454E434C3331));

In this case, the only change needed to make this compile would be to replace const with static readonly.

Exanite added 11 commits July 2, 2026 06:10
Will keep investigating the conditions a bit more since I'm not quite convinced this is optimal.
These apparently error during compile time, but not edit time. I.e., there's no red underline displayed in the editor, but will fail with "The expression being assigned to 'MyMacro1' must be constant".
Looks like the ranges are consistent, but I didn't realize since the existing UncheckedConversionMacroTest cases were wrong (see previous commit).
typeNameBacking eventually calls into GetTypeName, which can return either the keyword or the underlying type name depending on the generator configuration, so I believe this change is correct.
public static partial class Methods
{{
[NativeTypeName(""#define MyMacro1 (long)0x80000000L"")]
public const IntPtr MyMacro1 = unchecked((nint)(0x80000000));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Old expected output doesn't actually compile and fails with The expression being assigned to 'MyMacro1' must be constant.

This is easy to miss since the error does not show until you actually compile.

case CXType_ULongLong:
{
if (typeNameBacking.Equals("nuint", StringComparison.Ordinal))
if (typeNameBacking is "nuint" or "UIntPtr")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This change and the 3 other related changes are inconsistencies I noticed while working on the main issue. These are otherwise not related to the main issue addressed by this PR.

typeNameBacking eventually calls into GetTypeName, which can return either the keyword or the underlying type name depending on the generator configuration, so I believe this change is correct.

@Exanite Exanite marked this pull request as ready for review July 6, 2026 22:45
@Exanite Exanite marked this pull request as draft July 6, 2026 22:46
Exanite added 2 commits July 6, 2026 19:20
…nge checks

Didn't realize that IsUnchecked were effectively two different methods with the same name rather than just a simple overload.
@Exanite

Exanite commented Jul 7, 2026

Copy link
Copy Markdown
Member Author

Going to mark ready for review for some feedback, but not ready for merge.

The new CLongDefinesRegressionTestUnix test case represents a fairly harmless, but non-ideal case that I'm not quite sure how to handle.

The difference is the extra/redundant unchecked scope in if (b > unchecked(18446744073709551615U) - a) in the C# output. This is because a has nuint as its targetTypeName, which makes the generator believe that the integer literal to the left is also a nuint literal, but in reality is a ulong literal.

I'll keep investigating on my end, but my current ideas go in a few directions:

  1. We make the generator understand widening/implicit casting rules. This is probably the most correct approach, but requires the most work, especially because I'm not familiar enough with these rules (especially on the C/cross platform side) to fully put them into code.
  2. Accept the extra unchecked scope. It's effectively harmless, but not ideal.
  3. Restrict my changes to apply to just VarDecls. This is a bandaid fix, but might be reasonable since I can't think of cases where the generator won't output unchecked when it is actually needed. This will be much easier to investigate when an actual case is discovered.

SDL_size_add_check_overflow, the utility method the test case is based on, is notably something that shouldn't really be part of Silk 3's bindings either, so the benefit to fixing this isn't high, at least for Silk 3. I'm unfamiliar with how much ClangSharpPInvokeGenerator is used for porting code.

The diffs are otherwise exactly as I want them to be.
Tested with OpenCL, GL, AL, XR, Vulkan, and SDL through Silk 3's generator on both Windows and Linux.

@Exanite Exanite marked this pull request as ready for review July 7, 2026 05:25
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