Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CHAR_BIT instead of hard-coded literal 8 in generated C code #233

Open
pavel-kirienko opened this issue Jan 9, 2022 · 50 comments
Open
Labels
feature New feature or request

Comments

@pavel-kirienko
Copy link
Member

It complicates the usage of Nunavut-generated code with those odd (DSP) platforms where CHAR_BIT != 8. See details at OpenCyphal/libcanard#184 (comment)

@TYuD
Copy link

TYuD commented Jan 10, 2022

It complicates the usage of Nunavut-generated code...

As the example with the nunavutCopyBits function showed, it does not complicate much. Although it is possible that difficulties may arise in other places of the program.

Nevertheless, do you intend to implement this feature or not?

For example, because of this feature, I don't consider uavcan for my systems.

@pavel-kirienko
Copy link
Member Author

Neither I nor Scott is likely to work on this in the foreseeable future, but a pull request (from you or anybody else) would be welcome.

@TYuD
Copy link

TYuD commented Jan 10, 2022

I could take the C-code of the program. It seems C is not needed directly, but a python code generator. I don't use a python. Is it enough to fix only the C-code? Maybe I'll fix the C-code, and you'll make changes to Python?

@pavel-kirienko
Copy link
Member Author

@TYuD
Copy link

TYuD commented Jan 10, 2022

Templates of jinja2 I know much worse than python

@pavel-kirienko
Copy link
Member Author

I recommend reading the Jinja docs here (you don't need to read everything, just the intro is enough): https://jinja.palletsprojects.com/en/3.0.x/templates/

@TYuD
Copy link

TYuD commented Jan 10, 2022

Is the following sequence possible:

  1. write code in C;
  2. check it with tests;
  3. correct the jinja-templates based on the C-code;
  4. use the templates to generate a new C-code;
  5. check the generated C-code?

Are the tests of items 2) and 5) the same?

Are there these tests and where are they?

@pavel-kirienko
Copy link
Member Author

Yes, but you may need to tweak the build/test workflow a little. The tests are located here: https://github.com/UAVCAN/nunavut/tree/main/verification/c/suite

The build recipe is here: https://github.com/UAVCAN/nunavut/blob/main/verification/CMakeLists.txt

Normally you would run the verification suite using this helper: https://github.com/UAVCAN/nunavut/blob/main/.github/verify.py

Please read the contributing guide to understand how to use all that: https://nunavut.readthedocs.io/en/latest/docs/dev.html

The tweak I referred to amounts to changing the verification build recipe such that it does not regenerate the support header on build (since you would be modifying it manually). I mean you can do that but should you really? Editing the template seems like a no-brainer, it's basically the same C code but with a slightly weirder syntax.

@TYuD
Copy link

TYuD commented Jan 12, 2022

Pavel, there is another problem. In some C compilers, the size of double is not necessarily 64 bits. Is it possible to use macros like FLOAT32, FLOAT64 instead of 'float' and 'double' keywords? Can You set these macros in the settings of the nunavut code generator?

@TYuD
Copy link

TYuD commented Jan 12, 2022

Pavel, I have update all functions in serialization.h (see: https://github.com/TYuD/hello_world/blob/master/serialization_2.h) and pass tests
https://github.com/UAVCAN/nunavut/blob/main/verification/c/suite/test_support.c

Here is test output:
....\nunavut_qt\test_support.c:765:testNunavutCopyBits:PASS
....\nunavut_qt\test_support.c:766:testNunavutCopyBitsWithAlignedOffset:PASS
....\nunavut_qt\test_support.c:767:testNunavutCopyBitsWithUnalignedOffset:PASS
....\nunavut_qt\test_support.c:768:testNunavutSaturateBufferFragmentBitLength:PASS
....\nunavut_qt\test_support.c:769:testNunavutGetBits:PASS
....\nunavut_qt\test_support.c:770:testNunavutSetIxx_neg1:PASS
....\nunavut_qt\test_support.c:771:testNunavutSetIxx_neg255:PASS
....\nunavut_qt\test_support.c:772:testNunavutSetIxx_neg255_tooSmall:PASS
....\nunavut_qt\test_support.c:773:testNunavutSetIxx_bufferOverflow:PASS
....\nunavut_qt\test_support.c:774:testNunavutSetBit:PASS
....\nunavut_qt\test_support.c:775:testNunavutSetBit_bufferOverflow:PASS
....\nunavut_qt\test_support.c:776:testNunavutGetBit:PASS
....\nunavut_qt\test_support.c:777:testNunavutGetU8:PASS
....\nunavut_qt\test_support.c:778:testNunavutGetU8_tooSmall:PASS
....\nunavut_qt\test_support.c:779:testNunavutGetU16:PASS
....\nunavut_qt\test_support.c:780:testNunavutGetU16_tooSmall:PASS
....\nunavut_qt\test_support.c:781:testNunavutGetU32:PASS
....\nunavut_qt\test_support.c:782:testNunavutGetU32_tooSmall:PASS
....\nunavut_qt\test_support.c:278:testNunavutGetU64:FAIL: Unity 64-bit Support Disabled
....\nunavut_qt\test_support.c:284:testNunavutGetU64_tooSmall:FAIL: Unity 64-bit Support Disabled
....\nunavut_qt\test_support.c:785:testNunavutGetI8:PASS
....\nunavut_qt\test_support.c:786:testNunavutGetI8_tooSmall:PASS
....\nunavut_qt\test_support.c:787:testNunavutGetI8_tooSmallAndNegative:PASS
....\nunavut_qt\test_support.c:788:testNunavutGetI8_zeroDataLen:PASS
....\nunavut_qt\test_support.c:789:testNunavutGetI16:PASS
....\nunavut_qt\test_support.c:790:testNunavutGetI16_tooSmall:PASS
....\nunavut_qt\test_support.c:791:testNunavutGetI16_tooSmallAndNegative:PASS
....\nunavut_qt\test_support.c:792:testNunavutGetI16_zeroDataLen:PASS
....\nunavut_qt\test_support.c:793:testNunavutGetI32:PASS
....\nunavut_qt\test_support.c:794:testNunavutGetI32_tooSmall:PASS
....\nunavut_qt\test_support.c:795:testNunavutGetI32_tooSmallAndNegative:PASS
....\nunavut_qt\test_support.c:796:testNunavutGetI32_zeroDataLen:PASS
....\nunavut_qt\test_support.c:378:testNunavutGetI64:FAIL: Unity 64-bit Support Disabled
....\nunavut_qt\test_support.c:384:testNunavutGetI64_tooSmall:FAIL: Unity 64-bit Support Disabled
....\nunavut_qt\test_support.c:390:testNunavutGetI64_tooSmallAndNegative:FAIL: Unity 64-bit Support Disabled
....\nunavut_qt\test_support.c:396:testNunavutGetI64_zeroDataLen:FAIL: Unity 64-bit Support Disabled
....\nunavut_qt\test_support.c:801:testNunavutFloat16Pack:PASS
....\nunavut_qt\test_support.c:802:testNunavutFloat16Pack_NAN_cmath:PASS
....\nunavut_qt\test_support.c:803:testNunavutFloat16Pack_infinity:PASS
....\nunavut_qt\test_support.c:804:testNunavutFloat16Pack_zero:PASS
....\nunavut_qt\test_support.c:805:testNunavutFloat16Unpack:PASS
....\nunavut_qt\test_support.c:806:testNunavutFloat16PackUnpack:PASS
....\nunavut_qt\test_support.c:807:testNunavutFloat16PackUnpack_NAN:PASS
....\nunavut_qt\test_support.c:808:testNunavutFloat16Unpack_INFINITY:PASS
....\nunavut_qt\test_support.c:809:testNunavutSet16:PASS
....\nunavut_qt\test_support.c:810:testNunavutGet16:PASS
....\nunavut_qt\test_support.c:811:testNunavutSetF32:PASS
....\nunavut_qt\test_support.c:812:testNunavutGetF32:PASS
....\nunavut_qt\test_support.c:666:testNunavutGetF64:FAIL: Unity Double Precision Disabled
....\nunavut_qt\test_support.c:714:testNunavutSetF64:FAIL: Unity 64-bit Support Disabled


50 Tests 8 Failures 0 Ignored
FAIL

There are 7 failures with description 'Unity 64-bit Support Disabled' and 1 failure 'Unity Double Precision Disabled'. Is it normal? How to enable 64-bit Support?

@pavel-kirienko
Copy link
Member Author

Is it possible to use macros like FLOAT32, FLOAT64 instead of 'float' and 'double' keywords? Can You set these macros in the settings of the nunavut code generator?

That is not currently supported. If float is not IEEE754 binary32 compatible, OR if double is not IEEE754 binary64 compatible, you are guaranteed to get a compile-time error. The checks are done here:

https://github.com/UAVCAN/nunavut/blob/61232b31b82ba1b6a3b0bf82392975399a541137/src/nunavut/lang/c/support/serialization.j2#L86-L90

https://github.com/UAVCAN/nunavut/blob/61232b31b82ba1b6a3b0bf82392975399a541137/src/nunavut/lang/c/support/serialization.j2#L488-L651

We should perhaps define code-generation-time aliases for float32 and float64, similar to {{ typename_unsigned_length }} etc. @thirtytwobits would you support this?

There are 7 failures with description 'Unity 64-bit Support Disabled' and 1 failure 'Unity Double Precision Disabled'. Is it normal? How to enable 64-bit Support?

It is enabled already as you can see here: https://github.com/UAVCAN/nunavut/blob/61232b31b82ba1b6a3b0bf82392975399a541137/verification/CMakeLists.txt#L150
Are you sure you followed the instructions precisely? Did you change the unity submodule? The process name in your posted screenshot looks super suspicious, how can it be related to QtCreator?

N.B. it's always better to post CLI dumps as text rather than as images.

@TYuD
Copy link

TYuD commented Jan 12, 2022

Also I have much simplify functions nunavutGetIxx. Look at them please. Are they correct?

@TYuD
Copy link

TYuD commented Jan 12, 2022

N.B. it's always better to post CLI dumps as text rather than as images.

Sorry. It's my first activity with GitHub

@pavel-kirienko
Copy link
Member Author

Also I have much simplify functions nunavutGetIxx. Look at look at them please. Are they correct?

Technically they are not because they rely on non-standard implementation-defined behavior: shifting a negative integer to the right is not guaranteed to produce the desired outcome (which is to extend the sign bit).

@TYuD
Copy link

TYuD commented Jan 12, 2022

Technically they are not because they rely on non-standard implementation-defined behavior: shifting a negative integer to the right is not guaranteed to produce the desired outcome (which is to extend the sign bit).

I was sure this trick is standard. It would be to make sure additionally

@pavel-kirienko
Copy link
Member Author

For platforms that don't have small integer types, it should be safe to make small-size getters/setters mere aliases of their larger counterparts. E.g., nunavutGetU8 becomes an alias of nunavutGetU16.

I was sure this trick is standard.

See https://en.cppreference.com/w/c/language/operator_arithmetic and https://en.cppreference.com/w/cpp/language/operator_arithmetic. Actually, I should correct myself: the behavior is implementation-defined in C and undefined in C++.

@TYuD
Copy link

TYuD commented Jan 12, 2022

Are you sure you followed the instructions precisely?

I'm not following the instructions temporarily. I want to make sure that the solution exists without wasting time. Then I will study your technology stack. Am I distracting you a lot?

@pavel-kirienko
Copy link
Member Author

I understand what you're saying but you are at a far greater risk of wasting time when you are not following the instructions. Seriously. Also, I'm not sure if anybody has attempted to develop Nunavut on Windows, so that's another possible source of issues.

@TYuD
Copy link

TYuD commented Jan 12, 2022

until C++20: For negative a, the value of a >> b is implementation-defined (in most implementations, this performs arithmetic right shift, so that the result remains negative).

since C++20: The value of a >> b is a/2b, rounded down (in other words, right shift on signed a is arithmetic right shift)

@pavel-kirienko
Copy link
Member Author

Nunavut aims to support C++14 and C99, so C++20 is not that relevant.

@TYuD
Copy link

TYuD commented Jan 12, 2022

I'm not sure if anybody has attempted to develop Nunavut on Windows, so that's another possible source of issues.

I'm not going to be a nunavut developer. I just want a local feature for CHAR_BIT > 8 to appear in nunavut

@TYuD
Copy link

TYuD commented Jan 12, 2022

Nunavut aims to support C++14 and C99, so C++20 is not that relevant

It is possible with the help of conditional compilation (#if/#else) use a fast algorithm for those compilers that perform an arithmetic shift to the right. For the rest compilers - slow algorithm

@thirtytwobits
Copy link
Member

We should perhaps define code-generation-time aliases for float32 and float64, similar to {{ typename_unsigned_length }}

agree. This is a good plan

@thirtytwobits
Copy link
Member

Nunavut aims to support C++14 and C99, so C++20 is not that relevant.

I don't agree with this. C++17 is explicitly supported and C++20 implicitly (i.e. you should be able to use the C++17 headers with C++20)

@thirtytwobits
Copy link
Member

@TYuD , thanks for the effort here. Yes, I don't have access to a windows PC so I've never tried to develop this project in that environment. Please let me know if there are issues.

@TYuD
Copy link

TYuD commented Jan 12, 2022

I do not know the rules of Github. Is our discussion a flood?

@TYuD
Copy link

TYuD commented Jan 12, 2022

Please let me know if there are issues.

Ok.

c/suite/test_support.c works Ok on Windows/Qt.

@TYuD
Copy link

TYuD commented Jan 13, 2022

That is not currently supported. If float is not IEEE754 binary32 compatible, OR if double is not IEEE754 binary64 compatible, you are guaranteed to get a compile-time error.

There is a nuance here. Both IEEE754 binary32 and IEEE754 binary64 standart can be supported by the compiler, so the protection static_assert(NUNAVUT_PLATFORM_IEEE754_DOUBLE) will pass. But! 'float' and 'double' may be the same. Namely IEEE754 binary32. IEEE754 binary64 may be realized as a 'long double'.

Is it possible to exclude float64 (in future) using the code generator options? This is highly desirable for a number of systems

@TYuD
Copy link

TYuD commented Jan 13, 2022

It is enabled already as you can see here: nunavut/verification/CMakeLists.txt Line 150 in 61232b3 add_definitions(-DUNITY_SUPPORT_64=1

I have added predefined macros UNITY_SUPPORT_64, UNITY_INCLUDE_FLOAT, UNITY_INCLUDE_DOUBLE. Now all tests passed:

50 Tests 0 Failures 0 Ignored
OK

@pavel-kirienko
Copy link
Member Author

There is a nuance here. Both IEEE754 binary32 and IEEE754 binary64 standart can be supported by the compiler, so the protection static_assert(NUNAVUT_PLATFORM_IEEE754_DOUBLE) will pass. But! 'float' and 'double' may be the same. Namely IEEE754 binary32. IEEE754 binary64 may be realized as a 'long double'.

It doesn't matter. If you look at the code you will see that the assertion checks will pass if and only if float is IEEE 754 binary32 and double is IEEE 754 binary64.

It is possible to make the code rely only on float (as in binary32), yes, but I suppose at the moment this would be considered a low-priority feature.

@TYuD
Copy link

TYuD commented Jan 16, 2022

Hi! I have adapt tests for nunavut/support/serialization.h (test_support.c). Now they are suitable for platforms with CHAR_BIT > 8. I tested them with MinGW32 compiler on PC (CHAR_BIT==8) and with VisualDSP++ Studio on TS-201 processor (CHAR_BIT==32). All of 50 tests passed. Both projects are in nunavut_support_char repository. Now I am going to test it for TMS320F2802xx controllers (CHAR_BIT==16).

@TYuD
Copy link

TYuD commented Jan 24, 2022

Hi! Now tests passed for TI C2000 compiler v5.1.2 (controller TMS320F280023). CHAR_BIT == 16

@TYuD
Copy link

TYuD commented Jan 24, 2022

I came across a problem with float64 again. The compiler does not support such numbers, only float32. Why an ASSERT compilation error is generated if my system does not use messages with float64 type fields? For example, I can only use integer fields. Even without float32. Why not allow me to translate such messages from DSDL to C?

I suggest a simple solution. Instead of ASSERT, use conditional compilation: if the compiler does not support float32/float64, the corresponding sections are excluded from compilation with the help of #if pragmas

@pavel-kirienko
Copy link
Member Author

I came across a problem with float64 again. The compiler does not support such numbers, only float32. Why an ASSERT compilation error is generated if my system does not use messages with float64 type fields? For example, I can only use integer fields. Even without float32. Why not allow me to translate such messages from DSDL to C?

There is option --omit-float-serialization-support that you can use to, well, remove floats completely. There is no switch to enable/disable float64 selectively, although perhaps it is needed. Feel free to open a separate issue about this, the implementation should be trivial assuming that @thirtytwobits doesn't object.

I'm not sure about the conditional compilation because it is prohibited by MISRA/AUTOSAR. @thirtytwobits is it advised that we make a deviation in this case?

Hi! Now tests passed for TI C2000 compiler v5.1.2 (controller TMS320F280023). CHAR_BIT == 16

This is great. Would you be inclined to submit your patches to the upstream?

@TYuD
Copy link

TYuD commented Jan 29, 2022

Would you be inclined to submit your patches to the upstream?

Maybe later. To do this, I will have to deal with python, jinja, etc. It is important that the solution exists. If someone is interested, I repeat the link again: https://github.com/TYuD/nunavut_support_char

@thirtytwobits thirtytwobits added the feature New feature or request label Jan 27, 2023
@TYuD
Copy link

TYuD commented Mar 11, 2023

Greetings.

I recently resumed trying to implement CHAR_BIT != 8. Something succeeded. Now the C-code is being generated and all unit tests are being passed. Variants for CHAR_BIT == 8 and CHAR_BIT == 32 was checked. I tried to remain unchanged code for CHAR_BIT == 8. Such sections are wrapped with preprocessor directive #if CHAR_BIT == 8.

Unfortunately, I had to change the interface a bit. Instead of:

XXX_serialize_( const XXX* obj, uint8_t* buffer, size_t* inout_buffer_size_octets/chars?);

I had to write:

XXX_serialize_( const XXX* obj, void* buffer, size_t buffer_size_fits, size_t* input_offset_bits);

It seems that this is a fundamental problem and it is impossible to get around it. The fact is that with CHAR_BIT > 8, I cannot shift the pointer by 8 bits, but in the message, the data is packed with 8-bit alignment according to the DSDL specification.

My version is in the repository https://github.com/TYuD/nunavut

Do you have an interest in this direction?

@pavel-kirienko
Copy link
Member Author

@TYuD Yes, I think this is interesting. Unfortunately, it will likely be necessary to rewrite the serialization routines completely or at least alter them significantly to support these odd large-byte-radix platforms. I would start with the support library, specifically with the functions that (de-)serialize primitives like nunavutCopyBits, nunavutSetBit, etc.

Unfortunately, I had to change the interface a bit. Instead of:

I would suggest replacing uint8_t with unsigned char instead of void to avoid breakage of the existing interface. This is because if CHAR_BIT==8, then uint8_t is guaranteed to be an alias of unsigned char.

@TYuD
Copy link

TYuD commented Mar 12, 2023

Unfortunately, it will likely be necessary to rewrite the serialization routines completely or at least alter them significantly to support these odd large-byte-radix platforms. I would start with the support library, specifically with the functions that (de-)serialize primitives like

This is done. All tests from the \verification\c\suite folder passed. See https://github.com/TYuD/nunavut

I would suggest replacing uint8_t with unsigned char instead of void

At first, that's what I did: I used unsigned char*. Then I replaced it to void*. This does not limit in any way. You can pass a pointer of any type as a buffer, it is reduced to void* by default. Inside the library, char* is used as the minimum addressable word.

@pavel-kirienko
Copy link
Member Author

At first, that's what I did: I used unsigned char*. Then I replaced it to void*. This does not limit in any way. You can pass a pointer of any type as a buffer, it is reduced to void* by default. Inside the library, char* is used as the minimum addressable word.

Sure, but doesn't MISRA C prohibit casting void* to another pointer type? If we can get by without raising a deviation, we should.

This is done. All tests from the \verification\c\suite folder passed. See https://github.com/TYuD/nunavut

Okay, I must have misunderstood what you meant earlier when you referred to a fundamental problem that is impossible to get around.

I personally would like to see a more generalized serialization code that supports non-octet-byte platforms. @thirtytwobits wdyt?

@TYuD
Copy link

TYuD commented Mar 12, 2023

Sure, but doesn't MISRA C prohibit casting void* to another pointer type? If we can get by without raising a deviation, we should.

The void* type is widely used in the standard library. For example, the memcpy function. This is safe if void* is converted to char*. This is exactly the kind of casting I use.

It is very simple to return the type of parameters of the serialize function from void* to char*. But I wouldn't like to. Otherwise, you will have to make an explicit type conversion when calling serialize:

XXX_serialize( char* buf, ... ); // declaration
uint8_t buffer[...]; // buffer definition
XXX_serialize( (char*) buffer, ... ); // function call with pointer cast

@pavel-kirienko
Copy link
Member Author

You shouldn't need a type conversion because uint8_t is an alias of unsigned char, where it is present. The design of the standard library is of no importance because much of it is prohibited by MISRA anyway.

@TYuD
Copy link

TYuD commented Mar 12, 2023

The design of the standard library is of no importance because much of it is prohibited by MISRA anyway

But you widely use memmove with its void*

You shouldn't need a type conversion because uint8_t is an alias of unsigned char, where it is present.

Good. Then another example:

uint16_t buffer[...]; // buffer definition
XXX_serialize( (char*) buffer, ... ); // function call with pointer cast

@pavel-kirienko
Copy link
Member Author

But you widely use memmove with its void*

Conversion to void* is not a problem; the other way around is.

Good. Then another example:

uint16_t is misused here; it should be uint8_t (if supported by the platform, not in your case) or unsigned char.

@TYuD
Copy link

TYuD commented Mar 12, 2023

Conversion to void* is not a problem; the other way around is.

But internally memmove use reverse cast without problem

uint16_t is misused here; it should be uint8_t (if supported by the platform, not in your case) or unsigned char.

Then your code will become platform-dependent. It is necessary to think where uint8_t is, and where it is not. With the pointer 'void*' will always work. Another way is to use 'unsigned char*' always.

I don't insist on 'void*'. It's easy to go back to 'unsigned char*' if MISRA is so important

@pavel-kirienko
Copy link
Member Author

But internally memmove use reverse cast without problem

Exactly! What happens outside of our codebase is none of our concern. memmove may as well summon demons to do the copying, we don't care as long as it behaves up to the C standard and its usage is not prohibited by the applicable high-integrity coding standards.

if MISRA is so important

Indeed it is.

@TYuD
Copy link

TYuD commented Mar 12, 2023

I think that an explicit cast of (T*)p is always correct if T is the type of the primitive and sizeof(T)==sizeof(char). Because char is a minimally addressable memory cell. In particular, the cast (char*)p is always correct.

Another thing is that explicit type conversion is not a good practice itself. We could have passed a parameter with one meaning, and we converted it to an object with a different meaning. Maybe it means MISRA?

However, for byte-by-byte copying of memmove, serialization, etc., such a limitation is unnecessary. After all, the meaning of such functions is to work with an object as with a piece of memory, and not with its functionality.

But I will not argue with MISRA. If you insist, I will return to unsigned char.

In my version, there may be other questions besides void*. I will be glad to hear comments

@thirtytwobits
Copy link
Member

I personally would like to see a more generalized serialization code that supports non-octet-byte platforms. @thirtytwobits wdyt?

There's definitely value in this where is unlocks DSPs and other specialized processors.

@pavel-kirienko
Copy link
Member Author

@TYuD Can you please organize a pull request with your changes so we can discuss this more in-depth?

@TYuD
Copy link

TYuD commented Mar 15, 2023

@TYuD Can you please organize a pull request with your changes so we can discuss this more in-depth?

Of course I can. Is it a problem that my code still requires a little cosmetic?

@pavel-kirienko
Copy link
Member Author

Just make sure that the diff is sensible (no unnecessary changes), otherwise there should be no issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants