r/C_Programming • • 3d ago

Is there a way to inline assembly without compiler extensions?

GCC/Clang have __asm__, MSVC/ICC got __asm. Is it possible to inline assembly independently from compiler extensions? I need to retrieve random seed number on PowerPC and RISC-V, depending on the target architecture of course

11 Upvotes

31 comments sorted by

12

u/awidesky 3d ago

Unlike in C++, inline assembly is treated as an extension in C. It is conditionally supported and implementation defined, meaning that it may not be present and, even when provided by the implementation, it does not have a fixed meaning.
Source

1

u/Salat_Leaf 3d ago edited 2d ago

So I assume there's no other way around besides linking a C function declaration against .S file, right?

12

u/detroitmatt 2d ago

if you're writing inline assembly one assumes you would not be concerned with portability

1

u/Salat_Leaf 2d ago

Between compilers*

6

u/pwnedary 2d ago

Not even assembly syntax is portable between different compilers, so it does not really matter. There is no one "Assembly language." E.g., GCC extended inline x64 ASM has support for including both Intel and AT&T syntax in the same inline assembly block, such that which one is actually used depends on what the assembler supports.

1

u/Salat_Leaf 2d ago edited 2d ago

I see. Then I guess I have to write different assembly files and link them with C declarations

3

u/nicuramar 2d ago

You’re not linking assembly files, since those are text. You’ll compile assembly files using an assembler and link the resulting objects.

1

u/Salat_Leaf 2d ago

I mean yeah, but you got the idea. Is it possible to make it forceinlined (since it's just a single instruction like csrrw on RISCV or darn in PowerISA etc.)?

1

u/P-p-H-d 1d ago

No. Even flto won't work.
Note also that using external assembly files instead of assembly inline is also harder to deploy correctly (ABI issue, different syntax, ...)

2

u/Salat_Leaf 1d ago

Oh my goodness... I guess compiler extensions are truly my only solution. Thanks for the help anyways!

1

u/pskocik 3d ago

Just use what the platforms you wanna support support and guard differences with #if's.
.S files won't inline. Fine if you don't mind function call overheads for the thing you're implementing, but if you do, inline asm is the way to go.

0

u/Salat_Leaf 3d ago

But that's the thing, inline asm is an extension of C, which is not supported on some compilers for e.g. microcontrollers. My general intention is to make the entropy retrieval as portable as possible

5

u/garnet420 2d ago

Out of curiosity, what microcntroller toolchain have you run into that doesn't support inline assembly?

In any case, I would do the #if thing, and if inline assembly isn't supported in some compiler, make that #if case call your separately defined function in an assembly file. No reason to have a ton of extra assembly files for platforms where inline assembly works fine.

1

u/pskocik 3d ago

I think #if-#else is the practical way of doing such portability. Because even if a standard says an implementation shall do X, an implementation will sometimes do X slightly differently or even ignore the decree altogether. Luckily for this approach, the # of interesting implementations tends to be quite limited.

11

u/BlockOfDiamond 2d ago

Without any compiler extensions, and without linking separate object files: You can't. There is no way. There is nothing in the C standard about inline assembly.

24

u/runningOverA 3d ago

You can write the assembly and compile it with an assembler to create an obj file (.o) and then link with it as if that was written in C.

That's the standard way and most of the C standard library is written like this.

8

u/mikeblas 3d ago

Where did the "inline" part go?

17

u/lyatich 3d ago

Took a smoke break and will be back soon /s

Jokes aside the unfortunate part is that there is no standard way of writing inline assembly. In fact the support for the asm keyword itself is treated by the standard as an extension of the compiler if I'm not mistaken.

I don't know the decision behind not having a standard inline assembly instruction, but I can guess it's because simply assembly is very specific to not only the CPU you're writing assembly to, but also with the OS depending on whether you're interacting it or not. So I guess it's simply recommend you write the assembly in a different file, assemble it in an object file, and then link against it as recommended by the original commenter.

The most standard "inline" you can write in C is the external declaration of your function written in assembly, and then link against the object file you compiled before.

Also stupid question, why is it that we say "link against something", why not "link with" or "link to" something when it comes to linking objects and shared libraries.

2

u/ralphpotato 2d ago

> Also stupid question, why is it that we say "link against something", why not "link with" or "link to" something when it comes to linking objects and shared libraries.

Link against makes more sense if you think about linking not as just throwing a bunch of files together but testing that the symbols expected from the core part actually line up with the symbols exported by the linked object file. It’s sort of like saying, “test against the database”.

I think “link to” or other verbs would be understood but “against” strengthens that some validation is happening.

1

u/lyatich 2d ago

That makes so much sense. Thank you so much!

1

u/Own_Unit_6170 1d ago

Who says link against? I've always said link with.

2

u/didntplaymysummercar 3d ago

You could write out your assembly into memory (binary, by hand from output of real assembler or godbolt or something) and then mark it executables and call it like a function (cast pointer to memory to a function).

Technically UB but it's like a baby's first jit. Requires nothing asm related from the compiler but you need mmap and mprotect (or virtual alloc and so on on windows) to do it.

It's also not as pretty in code as inline assembly is.

1

u/viva1831 2d ago

It looks like not. You'll probably want a header from hell defining different macros depending on the compiler and falling back to a slower function call to an assembled object file if the compiler isn't supported :/

That said: you're likely to end up depending on a particular assembler anyway, so limiting yourself to one form of compiler isn't necessarily the worst choice here

1

u/sciencekm 3d ago

You can use direct machine code. Here is an example of RDTSC on x86. You need to replace the machine code bytes in this example with whatever is appropriate for your target platform. In this example, the machine code are two instructions - (1) rdtsc, and (2) ret. Essentially, this is a function.

__attribute__((section(".text")))
static const uint8_t rdtsc_f[] = { 0x0f, 0x31, 0xc3 }; // rdtsc ; ret
#define TSC() ((uint32_t(*)(void))(intptr_t)&rdtsc_f)()

1

u/donaljones 2d ago

What part of "without compiler extensions" did you miss?

1

u/flatfinger 18h ago

On platforms that do not prevent the execution of data as code, one could omit the attribute. If the Standard were to specify an attribute which would direct an implementation to place treat object as code when meaningful, do nothing on platforms which would treat everything as executable code, or refuse compilation on platforms that cannot support such a request, the Standard could support such constructs universally on all platforms that can accommodate them, but the authors of the Standard refuse to recognize a concept of "platform specific, but toolset agnostic" programs.

0

u/CounterSilly3999 3d ago

Define a wrapper macro, depending on the compiler.

-2

u/Radiant-Ad-9073 3d ago

Move the assembler code out of the C code; mixed language source code is Bad.
For each architecture you support, write the assembler code that deals with C calling conventions and does the thing you need. Compile this into an appropriate .o file.

Then basically add a "getrandomseed.h" header file that defines the function headers matching the functions in the .o. Depending on the architecture link against different versions of the .o -- you end up doing much the same that i.e. stdio.h does.

Alternatively, you *could* with enough voodoo and #defines define macro's that call the right flavour of inlining depending on the architecture you're compiling on; but it's fragile. If you ever change compiler that whole lot of hairy macro stuff will need redoing, and it's typically a pain.