Skip to content

core: introduce a system wide MIN macro#3691

Closed
OlegHahm wants to merge 3 commits intoRIOT-OS:masterfrom
OlegHahm:kernel_macro_min
Closed

core: introduce a system wide MIN macro#3691
OlegHahm wants to merge 3 commits intoRIOT-OS:masterfrom
OlegHahm:kernel_macro_min

Conversation

@OlegHahm
Copy link
Copy Markdown
Member

Move the definition from the crypto module to core since other features are likely to need this macro, too.

Move the definition from the crypto module to core since other features are likely to need this macro, too.
@OlegHahm OlegHahm added Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation Area: core Area: RIOT kernel. Handle PRs marked with this with care! Impact: minor The PR is small in size and might only require a quick look of a knowledgeable reviewer labels Aug 22, 2015
@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 22, 2015

Can you introduce a corresponding MAX macro, while you are at it?

@kaspar030
Copy link
Copy Markdown
Contributor

Can't we use a static inline for this?

@jnohlgard
Copy link
Copy Markdown
Member

Using a static inline would make this function type strict, using a macro will let the same macro be used for both signed and unsigned integers as well as floats.

@OlegHahm
Copy link
Copy Markdown
Member Author

Can you introduce a corresponding MAX macro, while you are at it?

I thought about it, but then I decided against introducing something that has no use case currently. It's a small change to introduce whenever we need it.

@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 22, 2015

ACK.

@miri64 miri64 added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Aug 22, 2015
@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 22, 2015

For reference, the following C files use some form of min (searched with git grep -i "\<_\?min\>(")`:

  • sys/chardev_thread.c
  • sys/crypto/modes/ccm.c
  • sys/net/gnrc/network_layer/sixlowpan/frag/gnrc_sixlowpan_frag.c
  • tests/pip/main.c

@OlegHahm
Copy link
Copy Markdown
Member Author

Good catch. Updated the PR.

@OlegHahm OlegHahm added the CI: needs squashing Commits in this PR need to be squashed; If set, CI systems will mark this PR as unmergable label Aug 22, 2015
@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 22, 2015

Care to add a unittests to tests/unittests/tests-core/ for the macro?

@OlegHahm
Copy link
Copy Markdown
Member Author

Will do.

@kaspar030
Copy link
Copy Markdown
Contributor

NACK. what happened to "we try to avoid macros"?

@OlegHahm
Copy link
Copy Markdown
Member Author

Actually, this PR does not change the number of macros. Of course, we can make an inline function for every comparable data type, but this one of the few cases where even a macro-allergic person like me prefer it.

@Kijewski
Copy link
Copy Markdown
Contributor

The name MIN is bound to cause name clashes.

I'd prefer an implementation that does not evaluate the parameters twice:

#define MIN(A, B) ({ \
    __typeof__(A) __a = (A); \
    __typeof__(B) __b = (B); \
    (__a < __b) ? __a : __b; \
})

Actually I think int16_t min_i16(int16_t, int16_t), uint16_t min_u64(uint64_t, int64_t), ... wouldn't be too bad either:

for m in ('min<', 'max>'):
    for p in ('u',''):
        for i in range(4):
            print('''
static inline {p}int{j}_t {m}_{q}{j}({p}int{j}_t a, {p}int{j}_t j)
{{
    return (a {o} b) ? a : b;
}}'''.format(m=m[:-1], o=m[-1], p=p, q=p or 'i', j=8<<i))

@OlegHahm
Copy link
Copy Markdown
Member Author

Brrrr. Here I'm with @kaspar030 (and the coding conventions: "Do NOT use global macros defining more than one line of code. Use inline functions instead."). That looks awful.

@Kijewski
Copy link
Copy Markdown
Contributor

So ... you don't like

static inline uint8_t min_u8(uint8_t a, uint8_t j)
{
    return (a < b) ? a : b;
}

static inline uint16_t min_u16(uint16_t a, uint16_t j)
{
    return (a < b) ? a : b;
}

static inline uint32_t min_u32(uint32_t a, uint32_t j)
{
    return (a < b) ? a : b;
}

static inline uint64_t min_u64(uint64_t a, uint64_t j)
{
    return (a < b) ? a : b;
}

static inline int8_t min_i8(int8_t a, int8_t j)
{
    return (a < b) ? a : b;
}

static inline int16_t min_i16(int16_t a, int16_t j)
{
    return (a < b) ? a : b;
}

static inline int32_t min_i32(int32_t a, int32_t j)
{
    return (a < b) ? a : b;
}

static inline int64_t min_i64(int64_t a, int64_t j)
{
    return (a < b) ? a : b;
}

static inline uint8_t max_u8(uint8_t a, uint8_t j)
{
    return (a > b) ? a : b;
}

static inline uint16_t max_u16(uint16_t a, uint16_t j)
{
    return (a > b) ? a : b;
}

static inline uint32_t max_u32(uint32_t a, uint32_t j)
{
    return (a > b) ? a : b;
}

static inline uint64_t max_u64(uint64_t a, uint64_t j)
{
    return (a > b) ? a : b;
}

static inline int8_t max_i8(int8_t a, int8_t j)
{
    return (a > b) ? a : b;
}

static inline int16_t max_i16(int16_t a, int16_t j)
{
    return (a > b) ? a : b;
}

static inline int32_t max_i32(int32_t a, int32_t j)
{
    return (a > b) ? a : b;
}

static inline int64_t max_i64(int64_t a, int64_t j)
{
    return (a > b) ? a : b;
}

neither? :)

@kaspar030
Copy link
Copy Markdown
Contributor

If defined static inline in a header, couldn't we go with the 64bit versions only? Within a single compilation unit, the compiler would use optimized instructions anyways.

@OlegHahm
Copy link
Copy Markdown
Member Author

@kaspar030 should love it, but I would actually rather prefer to replace all calls of MIN() by ((a > b) ? a : b) literally over one of these solutions.

@kaspar030
Copy link
Copy Markdown
Contributor

Wow, five core maintainers disscussing a min/max macro PR with already finished documentation an unit-tests. I think we should use this as an opportunity to learn how to come to a fruitful result that everyone agrees with, if possible.

@kaspar030
Copy link
Copy Markdown
Contributor

I propose being a little more methodical on how to choose one of the possible solutions.

@kaspar030
Copy link
Copy Markdown
Contributor

-. let's get "personal preferences" out of the way by backing them up with technical arguments
e.g. "I prefer X over Y because X uses less lines of code"

@kaspar030
Copy link
Copy Markdown
Contributor

  • let's collect the pro's and con's of each solution in an objective manner
    e.g.

"Solution A:
pro:

  • is type-agnostic

con:

  • it's a macro, so it possibly evaluates arguments twice, and might behave differently depending on which parameter types are being used in which context

Solution B:

pro:

  • deterministic behaviour

con:

  • needs per-type definitions
    "

@OlegHahm
Copy link
Copy Markdown
Member Author

Solution A:

  • pro:
    • saves 25 lines of code

Solution B:

  • con:
    • introduces hundreds new lines of code where most of them are probably never used

@kaspar030
Copy link
Copy Markdown
Contributor

With unsigned it's only 8 bytes bigger.

Just tried, I get 2 bytes less e for "unsigned". How do you test?

I got this:

[kaspar@booze hello-world (wtimer)]$ cat min_test.c 
#include "minmax.h"
unsigned min_test(unsigned a, unsigned b) {
    return MIN(a,b);
}
[kaspar@booze hello-world (wtimer)]$ 

and replace MIN with min, defined as

static inline unsigned min(unsigned a, unsigned b) {
   return a<b?a:b;
}

@kaspar030
Copy link
Copy Markdown
Contributor

introduces hundreds new lines of code where most of them are probably never used

Could you elaborate?

@OlegHahm
Copy link
Copy Markdown
Member Author

See #3691 (comment) (probably not "hundreds", but "tens").

@kaspar030
Copy link
Copy Markdown
Contributor

All current uses of min are using unsigned or size_t, correct? (chardev doesn't, but it should, and it is about to get removed from the code).

@OlegHahm
Copy link
Copy Markdown
Member Author

Just tried, I get 2 bytes less e for "unsigned". How do you test?

https://github.com/OlegHahm/miniature-dangerzone/tree/master/min-test

The random stuff is just there to prevent the compiler from optimizing.

@OlegHahm
Copy link
Copy Markdown
Member Author

All current uses of min are using unsigned or size_t, correct?

The cipher stuff seems to use uint8_t.

@OlegHahm
Copy link
Copy Markdown
Member Author

Gotta run. Will be offline for about 1 hour.

@kaspar030
Copy link
Copy Markdown
Contributor

Did some more testing.

While using the correct typed version of min/max as static inline (like @Kijewski's proposal) always leads to smallest code, using a macro "most of the times" produces as-small code, across multiple platforms (avr8, msp430, cortexM0+3), with just some exceptions.
Using only, e.g., signed or unsigned static inline functions for all data types is least efficient.

So do we use the macro, document it's drawbacks, and accept a little inefficiency in corner cases, but have the convinience of a single line of macro code, or do we go with 16 static inline functions (64 lines of mostly identical code), all containing the same tertiary operator, forcing the user to choose the correctly typed one, but ensuring maximum efficiency while ruling out possible side effects caused by involving the preprocessor?

@OlegHahm
Copy link
Copy Markdown
Member Author

@kaspar030, thanks for the thorough analysis. I would still prefer the macro solution for simplicity.

@Kijewski
Copy link
Copy Markdown
Contributor

I prefer my convoluted macro solution but I guess you two don't. :)

@kaspar030
Copy link
Copy Markdown
Contributor

I actually prefer the typeof based macro over the simple one. It's even ISO C, right?

@OlegHahm
Copy link
Copy Markdown
Member Author

As far as I can tell, it's not ANSI C and I will not let the coding conventions be bent here (again). One-line macro, inline functions, or literal replacement - these are the options.

@kaspar030
Copy link
Copy Markdown
Contributor

So, then let's go with the static inlines...

@OlegHahm
Copy link
Copy Markdown
Member Author

Stuck in a loop.

@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 25, 2015

MIN() is perfectly fine IMHO, defining 20+ inline function, that would require casting do not help anybody (there are even types missing, what's with mixed comparisons e.g.). On the other hand I agree, that we should try to avoid double-evaluation of the parameters. But that can be done by fixing it at the caller as well.

@cgundogan
Copy link
Copy Markdown
Member

IMO, having min as typed inlines is more verbose and hence the code becomes clearer about the context it is used in. Besides, why would you need casting? You can use the inline with the bigger width of the two parameters. The compiler won't complain if you put an uint8_t into a function which expects an uint32_t

@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 25, 2015

So reduces the types of functions needed to

static inline min(int a, int b)
{
    return (a < b) ? a : b;
}

static inline minu(unsigned int a, unsigned int b)
{
    return (a < b) ? a : b;
}

static inline minll(long long int a, long long int b)
{
    return (a < b) ? a : b;
}

static inline minllu(long long unsigned int a, long long unsigned int b)
{
    return (a < b) ? a : b;
}

Or am I wrong?

@kaspar030
Copy link
Copy Markdown
Contributor

In theory, but in practice, this enlarges the code...

@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 25, 2015

Why?

@cgundogan
Copy link
Copy Markdown
Member

For cortex platforms it could be that the instruction width selection fails to choose the optimal one. So that e.g. a 32-bit instruction instead of a 16-bit instruction is used for a comparison of two uint8_ts. But I didn't look into the machine code here, just speculating.

@miri64
Copy link
Copy Markdown
Member

miri64 commented Aug 31, 2015

I have obviously to little of an opinion about that => Reassign. Call for a second (formality-check) ACK if needed ;-)

@miri64 miri64 assigned cgundogan and unassigned miri64 Sep 1, 2015
@OlegHahm
Copy link
Copy Markdown
Member Author

OlegHahm commented Sep 1, 2015

Actually, I'm about to close this PR since we're apparently failing to conclude on something.

@cgundogan
Copy link
Copy Markdown
Member

I really have no strong opinion on this anymore. But looking at the diff, I see several calls to the min macro with a subtraction as a parameter. I am not sure about how smart the compiler is with regards to caching the result in a register, it probably depends on the use case, but this again is IMO a showcase for the weakness of macros: double evaluation. I am fine with having a min macro defined somewhere. It's still up to the developer if she or he wants to use the macro or not.

@OlegHahm OlegHahm closed this Sep 1, 2015
@OlegHahm
Copy link
Copy Markdown
Member Author

OlegHahm commented Sep 1, 2015

Closed because of lack of consensus.

@kaspar030
Copy link
Copy Markdown
Contributor

Poor, for a PR where someone (me) proposed to use it to actually find a solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: core Area: RIOT kernel. Handle PRs marked with this with care! CI: needs squashing Commits in this PR need to be squashed; If set, CI systems will mark this PR as unmergable CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Impact: minor The PR is small in size and might only require a quick look of a knowledgeable reviewer Type: enhancement The issue suggests enhanceable parts / The PR enhances parts of the codebase / documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants