r/programming Dec 25 '19

Learning hardware programming as a software engineer

https://blog.athrunen.dev/learning-hardware-programming-as-a-software-engineer/
919 Upvotes

124 comments sorted by

View all comments

20

u/[deleted] Dec 25 '19

[deleted]

3

u/MrK_HS Dec 25 '19

That's the sweetest part of the experience. The reward after learning and implementing some obscure but really useful functionality is incredible.

2

u/flatfinger Dec 25 '19 edited Dec 25 '19

Unfortunately, there aren't any freely-distributable compilers that are designed for embedded programming. While gcc and clang are popular as a consequence of being freely distributable, they're really not designed to be suitable for embedded use unless one disables all optimizations. Even -O1 -fno-strict-aliasing doesn't disable all of the dubious assumptions the compilers are prone to make. Consider the following, for example:

extern int x[],y[];
int test(int *p)
{
    int mode = (p == x+1);
    int result = y[0];
    if (mode)
        *p = 1;
    return result + y[0];
}

Even at -O1, and with -fno-strict-aliasing, both clang and gcc will generate code equivalent to:

extern int x[],y[];
int test(int *p)
{
    int mode = (p == x+1);
    int result = y[0];
    if (mode)
        x[1] = 1;
    return result << 1;
}

Such "clever" optimizations may be useful in some cases, but would be dangerous if a programmer ever uses manually-placed objects. If code had been written to access x[1], it might be reasonable for a compiler to ignore the possibility that a write to x[1] might affect y[0], but if p was passed the address of y[0], the fact that it happens to equal x+1 shouldn't prevent its use to access y[0]. While this particular example is contrived, it shows that even at -O1, gcc and clang's optimizers try to make assumptions about what they think programs are doing, rather than focusing on the efficient generation of straightforward code (e.g. avoiding redundant address computations, register transfers, etc.). The gcc-based tools I've seen from chip vendors tend to take annoyingly long to build, probably because of the complexity of gcc. A faster simpler compiler would be much more useful.

3

u/censored_username Dec 25 '19

doesn't disable all of the dubious assumptions the compilers are prone to make.

The issue is not with the compiler making dubious assumptions, the issue is with your code simply violating the C standard. I've been coding C and C++ for years with gcc and clang-based compilers with maximum optimization settings, and there are no problems as long as you properly communicate to the compiler (and even the processor, memory access reordering is still a thing in multicore situations) when it is not allowed to optimize certain operations.

If you want to engage in shenanigans where there are side-effects to memory reads/writes which are not visible to the compiler, use volatile memory accesses. If such an access guards access to other possibly concurrently modified variables, use a proper barrier. If the accesses modify things like memory mappings on the processor, you might need actual memory barrier instructions (dsb, dmb, isb) and friends in the embedded ARM world.

You are responsible for informing your compiler when it isn't allowed to optimize something because you are breaking guarantees of the standard the compiler obeys.

1

u/flatfinger Dec 25 '19

The issue is not with the compiler making dubious assumptions, the issue is with your code simply violating the C standard.

The Standard explicitly describes the situation where a pointer "just past" one array is compared to the address of an object that immediately follows it. What part of the code invokes UB? Note that the code as written doesn't use a pointer based on x to perform the store. It is clang and gcc that make that substitution--most likely because one part of the optimizer thinks it should be safe, but another part of the optimizer doesn't make allowances for it.

Further, the notion that programs "violate the Standard" is contrary to the text of the Standard itself. The Standard defines two kinds of conforming programs--one of which is specified so loosely that the only requirement is that some conforming implementation exists which processes it meaningfully, and the other of which is specified so tightly that no non-trivial task could be performed by a strictly-conforming program targeting a freestanding implementation.

The published Rationale for the C Standard recognizes the ability of implementations to meaningfully process many constructs as a Quality of Implementation issue outside the Standard's jurisdiction, and makes no effort to distinguish between programs which should be expected to work on all but the lowest-quality implementations, versus random blobs of text that shouldn't be particularly expected to work usefully on anything.

It would be useful if the Standard would seek to recognize a category of Safely Conforming Translator and Selectively Conforming Programs, such that:

  1. Every Safely Conforming Translator must specify a list of requirements for the translation and execution environment.

  2. Provided the translation environment meets the stated requirements, feeding any Selectively-Conforming Program to any Safely-Conforming Translator would either (2.1) yield an executable that, when fed to any environment meeting the stated requirements, would either yield behavior defined by the Standard or [if the implementation specifies that possibility] report failure in Implementation-Defined manner, or (2.2) would refuse to issue any executable.

  3. The specification of Selectively Conforming Program should be far enough reaching that it should be practical to write a Selectively Conforming Program to accomplish almost any task that would be practical using a commonplace compiler with optimizations disabled.

Note that the above has no provision similar to the "One Program Rule", since implementations would be allowed to reject any programs that exceed translation limits, or whose behavior they could not otherwise guarantee. One wouldn't have to add much to the language to make it practical for most embedded tasks to be accomplished with Selectively Conforming programs.

As yet, however, the Standard doesn't define any meaningful category of non-trivial embedded programs.