Mechanics of MCU startup - Page 1
Forum section: electronics / general

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 05:48:21 pm
content: I'd like to ask what are the typical steps that an MCU goes through when power is applied? Consider say STM32 devices as an example.I wasreading thisand the question came up why can't the startup code also be generated by the compiler? why must there be assembler involved at all?

author: eutectique
author_rank: Frequent Contributor
timestamp: February 10, 2023, 06:13:54 pm
content: Quote from: Sherlock Holmes on February 10, 2023, 05:48:21 pmBecause the compiler has no idea what interrupts your particular MCU can generate, and hence no knowledge about interrupt handlers thereof. Do you want to initialise .data and .bss sections? What about the rest of RAM? Do you want it to be filled with a specific value? Which one? What about a no-init memory region used for communication between the bootloader the main code?Quote from: Sherlock Holmes on February 10, 2023, 05:48:21 pmThere is absolutely no need or reason for assembly. I always write startup code in C.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 06:19:20 pm
content: Quote from: eutectique on February 10, 2023, 06:13:54 pmOK thanks, yes I can see more now, I've tweaked a project here so I can see the linker map and the startup code - a .c file.I'm looking at the declaration of an array of pointers with the addresses of a bunch of functions for the various handlers, the declaration has:Code:void * g_pfnVectors __attribute__ ((section (".isr_vector"), used)) =So it seems that __attribute__ enables a "section" name to be specified, this is slowly making sense!

author: T3sl4co1l
author_rank: Super Contributor
timestamp: February 10, 2023, 06:30:15 pm
content: Just spitballing on the compiler motivations here as I'm no designer of them, but if nothing else, consider this a possible explanation. Also, somewhat curious myself, and, Godwin's Law and all, y'know?Compilers prefer to keep things as general as possible; avoiding special cases means less stuff to check, and there's already so many thousands of things to check, in so many combinations (in something this complex, you're constantly fending off the combinatorial explosion). Sometimes this isn't possible, and special cases must be made (e.g.__attribute__((signal))or whatever platform-specific equivalent defines an ISR). For startup, it is only ever needed exactly once, and in a form specific to that platform. There's no point in making the compiler go through all the semantics of compiling machine code, when it's starting from an ill-defined state. Just shove all that off to one side and be done with it.Actually, what's shown in the link isn't all that special. Pure C is used to define data structures such as control registers and IVT; which are then mapped in by the linker (the linker script will give the physical addresses for the sections e.g..isr_vector-- although not the registers, as those are accessed by raw pointer ("#define RCC_BASE 0x58024400", "#define RCC ((rccStruct*) RCC_BASE)"), but could be done this way as well), and the only ASM is the very bare bones stuff -- stack pointer and memory init, and at that, mainly because 1. ASM can be better optimized, 2.memcpy/sethave semantics that don't add anything here, and 3. if overridden by an arbitrary (read: inappropriate, buggy, or out-and-out wrong or malicious) user function, might ruin the memory state entirely.That leaves the compiler operating on the assumptions that all data is initialized (memory is already magically set up) as expected, the stack, well, works; and the ABI can be followed (register allocation conventions, sharing/pushing between function calls, etc.).Finally, optimizations can be performed, like inlining main() and init() and etc. (they're only called once), stripping out function pre/postambles (particularly when inlined, and particularly that main() does not return), so although these are separated out as functions, they all end up in one block with almost no overhead.Meanwhile, many of these are "weak" so they can be overridden by the user -- perhaps one wants slightly different inits, maybe rolling register inits into a block (rather than using the mess of HAL calls), perhaps optimizing memory init for faster startup (more often done by placing variables in .noinit or something like that; if .data and .bss end up zero length, LTO should remove the for() loop -- the asm (in this syntax) cannot be removed though), who knows. Wel,, most ofthesethat are weak are the ISRs of course, but other platforms have more init()s, or you can add them to this file of course.And, as far as some asm vs. linker vs. compiler routes, it depends how much visibility and generality is needed (e.g. symbols with named types and semantics and optimizations available) versus single-use, special-case objects and getting a faster build time (probably registers are done by naked pointers because burdening the linker with the allocation of fixed addresses would be both trivial and a waste of time?). Remember, the compile chain itself is subject to optimization too -- developers looove a quick build cycle.Related question: why can't it be handled by the hardware?I think there might actually be some platforms with a .data section in the hardware. CPU/system registers of course can be set up with default (on reset) values for a given typical operating configuration. It could very well be that init() is stubbed out on such a platform, and RESET_VECTOR jumps right into main(). But these are going to be more specialized or limited platforms as well.Tim

author: eutectique
author_rank: Frequent Contributor
timestamp: February 10, 2023, 06:33:42 pm
content: Quote from: Sherlock Holmes on February 10, 2023, 06:19:20 pmYes, one thing__attribute__ ((section (".isr_vector"), used))specifies is the section name. Why? The answer is in the linker script, search it for.isr_vectorsection. Or examine the map file.Another thing isusedattribute. Why? Because you want the linker to keep the vector table in the final image, otherwise it will be removed. Why? Because no one referencesg_pfnVectorssymbol.And so on, round and round.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 06:45:50 pm
content: This is interesting gentlemen, many thanks. I've been designing a programming language and have recently started to move away from conventional grammar concerns and towards the nitty gritty machine level aspects of this.Right away I can see a case for additional, optional attributes that correspond to the stuff used by the linker.Code:procedure startup (root_ptr) section(".text") retain;// codeend;The "section" and "retain" (or "used" in the C examples) can be applied to code like above or to externally visible static declarations (as in the interrupt vectors).Neither of these seem to be specific to anything, they are general terms applicable to all linkers I suspect. This exercise is currently concerned with what language features do we need to consider given that MCU's are a design target for the language.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: February 10, 2023, 08:01:43 pm
content: Mind if you're just starting with language, it might not be very important/useful to add a "retain" keyword or the like; back in the bad old days for example the linker would just jam together everything it was fed, used or not. Or something like that, I think. Removing unused sections/objects is easy enough, but make very sure you have all links accounted for, so that things don't get removed when they're very much still necessary.(Not to say having the facility present from an early stage won't be beneficial; an attribute system (or whatever you want to call it) is probably a good thing to architect around, then expand later as needed.)Tim

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 10, 2023, 08:51:17 pm
content: Quote from: Sherlock Holmes on February 10, 2023, 05:48:21 pmIs the underlying question really about being able to use a single, "high-level" language for MCU development, without needing any other language (be it assembly, C or whatever)?

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 09:01:28 pm
content: It's two pronged I suppose. The first being what would a language need in order to eliminate the need for some assembler preamble code, it seems this is not a big issue though.The second prong is what kind of attribute specifiers would be needed to cater for the kinds if things we see with MCU's and linkers and basic setup code.I think there are two classes of these, general one's that aren't really restricted to specific CPU's and platform one's that have a narrower specialized meaning.So a way of specifying a section seems pretty general, but something like "bitband" seems more restricted.Because I'm not restricted to a C like syntax I am free to invent and add attributes as needed so I'm just doing basic groundwork here.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 09:30:13 pm
content: What creates a "linker script" file? usually? Is it fixed, like a standard setup for certain kind if projects or is it somehow generated?

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 10, 2023, 09:31:44 pm
content: What's needed for MCU "startup" really depends on the family of MCU.Sure copying typical data segment and zeroing bss is "trivial" without requiring assembly.But some other things often need to be initialized directly acessing some CPU registers, such as stack pointers, and/or other things like CSRs in RISC-V.That's not something you can do in a high-level language - direct access to as low-level as CPU registers would pretty much defeat the idea of being high-level. Now sure if you introduce some means of doing that in your language, then 1/ it becomes target-specific, and 2/ it just becomes some glorified assembly extension inside your language, just like one can use assembly say in GCC by using a GCC extension, and making sure this wouldn't get optimized out.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 09:53:56 pm
content: Quote from: SiliconWizard on February 10, 2023, 09:31:44 pmOK I see:Code:void __attribute__((naked, noreturn)) Reset_Handler(){//Normally the CPU should will setup the based on the value from the first entry in the vector table.//If you encounter problems with accessing stack variables during initialization, ensure the line below is enabled.#if defined(sram_layout) || defined(INITIALIZE_SP_AT_RESET)__asm ("ldr sp, =_estack");#endifvoid **pSource, **pDest;for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)*pDest = *pSource;for (pDest = &_sbss; pDest != &_ebss; pDest++)*pDest = 0;SystemInit();__libc_init_array();(void)main();for (;;) ;}That loads the SP right there with whatever value is set in the link file for "_estack". (But that code, from my small project, is disabled, the "ifdefs" arent enabled yet the SP is set to 0x20020000 as specified in the linker file...I understand that a line has to be drawn between what a HLL can do and can't, I'm just exploring where that line can be placed.I think every CPU has a stack pointer, so an ability to set that in a HLLseemsto be reasonable, don't you think?

author: nctnico
author_rank: Super Contributor
timestamp: February 10, 2023, 09:55:33 pm
content: Quote from: SiliconWizard on February 10, 2023, 09:31:44 pmI agree. The ARM Cortex Mx CPU cores are more like an exception than the rule where it comes to needing assembly. For almost every other microcontroller out there you'll need assembly for startup and sometimes even demultiplexing interrupts because the CPU core doesn't have a vectored interrupt controller bolted onto it.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 10:10:57 pm
content: Hmm, look:Why the for loop? is that in case or when, "main" returns? that's at least 18 bytes wasted right there!

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 10:21:47 pm
content: I'm also wondering too if it might be possible under certain circumstances to dispense with a link step, certain simplistic scenarios might lend themselves to that, the compiler can generate a pure executable - just an idea...

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 10:37:42 pm
content: That code doesn't set SP:So how does it get set...I tweaked the Link script and can see that at runtime SP is set to the value in the script, I wonder how its getting set...OK I see, it is the first 4 byte address stored in the vector table.

author: nctnico
author_rank: Super Contributor
timestamp: February 10, 2023, 10:44:43 pm
content: Read the Cortex Mx CPU core manual and you'll see how the SP gets set.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 10:47:32 pm
content: Quote from: nctnico on February 10, 2023, 10:44:43 pmYes I see now, many thanks.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 10, 2023, 11:32:52 pm
content: I'm confused, please look:Code:extern void *_estack;That's in the ST C startup code.Here's the link script:Code:_estack = 0x20020000;So far so good, the name is a 4 byte pointer and gets set - at link time to that value. But look, further down in the startup code:Code:void * g_pfnVectors __attribute__ ((section (".isr_vector"), used)) ={&_estack,&Reset_Handler,&NMI_Handler,&HardFault_Handler,&MemManage_Handler,&BusFault_Handler,&UsageFault_Handler,NULL,...Why isn't that:Code:void * g_pfnVectors __attribute__ ((section (".isr_vector"), used)) ={_estack,&Reset_Handler,&NMI_Handler,&HardFault_Handler,&MemManage_Handler,&BusFault_Handler,&UsageFault_Handler,NULL,...? Why store theaddressof_estackin the array rather than it's value? Unless that linker directive is setting the address and NOT the value of_estack? I can see under debug that_estackdoes indeed have an address of 0x20020000 and a value of 0x00000000...Also the initialization of each element of the arrayg_pfnVectorscannot take place until runtime surely? because until the code is linked the addresses of all the functions are undefined...yes?Yet there's no generated assembly code for that array initialization...

author: eutectique
author_rank: Frequent Contributor
timestamp: February 10, 2023, 11:52:37 pm
content: Quote from: Sherlock Holmes on February 10, 2023, 10:10:57 pmYes.Quote from: Sherlock Holmes on February 10, 2023, 10:10:57 pmIf you have a closer look at the commandCode:0x08001e1c b.n 0x08001e1cyou might spot that it branches to itself. Following 18 bytes worth of rubbish are probably literal pool disassembled with -D option.As was already suggested, Cortex-M Reference Manual(s) would help a lot.

author: ejeffrey
author_rank: Super Contributor
timestamp: February 11, 2023, 12:10:39 am
content: Quote from: Sherlock Holmes on February 10, 2023, 09:30:13 pmGenerally all simple projects for the same MCU can use the same linker scripts. It defines the memory regions of the device and which sections should be mapped where. It also defines symbols that can be referenced by C (or assembly) particularly for startup processes such as zeroing bss. A basic linker scripts can be written for a particular processor just by reading it's data sheet.More complicated projects often need to change the linker scripts when they want to use the available memory in a different way. For instance if you want to build a bootloader, and then programs to be loaded by the bootloader, they might have different flash areas reserved, but for basic programs this is not needed.QuoteSure. .COM files in DOS didn't use a real link step. They had to have all needed symbols defined in a single translation unit, and thus the compiler was able to do all symbol resolution and the base address was fixed. I think technically the linker was still invoked to generate the .COM from the .OBJ file, but as I understand it was just copying the byte code into the correct file format.QuoteYeah. this is a bit confusing. The linker deals with locations of objects, not their values.Code:extern void *_estack;That is a variable whose *value* is a pointer to void. However the location of the _estack variable is set to address 0x20020000 by the linker. The value of the void * is meaningless. First off, trying to read it would likely be an access violation since it points to the top edge of the stack. However, if that were somehow a valid memory location, it would be treating the data stored above the stack as a void *.You could just as easily have declared:Code:extern int _estack;and it would work exactly the same way, but people might try to access the value of _estack -- which would just be the value at the top of the stack. Likewise you could use any other data type, as long as you don'tThe ideal thing to do would be:Code:extern void _estack;This would declare a variable that has an address, but whose value is meaingless. But that isn't valid C. So they use a void pointer.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 11, 2023, 12:17:36 am
content: Quote from: eutectique on February 10, 2023, 11:52:37 pmI have that and other manuals, sometime I prefer human interaction.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 11, 2023, 12:19:34 am
content: Quote from: ejeffrey on February 11, 2023, 12:10:39 amVery helpful, I appreciate your time.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 11, 2023, 07:10:19 am
content: TLDR for ARM MCUs:Read DWORD from memoryWrite it to Stack Pointer.Read DWORD from memoryWrite it to Program Counter (i.e., jump there).Rest is just tooling to achieve a programmable binary which takes advantage of this trivially simple process.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 11, 2023, 05:57:54 pm
content: I've been looking at various examples of MCU startup code and one thing that is needed over and over is the initialization of the stack pointer. This can be done in a linker script and can be done with embedded assembler (as is done in some examples of reset handlers).So I am wondering why there is no C language support for this? there are many __attribute__ options for many things low level, but oddly not for setting the stack pointer.Why is this? surely setting a register to some value is a pretty basic abstract idea?

author: coppice
author_rank: Super Contributor
timestamp: February 11, 2023, 06:13:19 pm
content: Quote from: Sherlock Holmes on February 11, 2023, 05:57:54 pmMost attributes are compiler dependent. Some C compilers for MCUs have attributes to allow basic things like the stack pointer to be set, and startup code can be written entirely in C. Before your code is even run, most modern MCUs have some kind of code running, to initialise the hardware, which is completely inaccessible to the programmer. I wonder if any MCU had that hidden ROM code written in C?

author: eutectique
author_rank: Frequent Contributor
timestamp: February 11, 2023, 06:14:52 pm
content: ARM Cortex-M3 has two stack pointers. Which one the compiler should initialise?Here is the chapter from corresponding User Guide:https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-processor/programmers-model/stacks

author: GromBeestje
author_rank: Frequent Contributor
timestamp: February 11, 2023, 06:19:29 pm
content: Quote from: coppice on February 11, 2023, 06:13:19 pmProbably. Stuff like thishttps://dmitry.gr/?r=05.Projects&proj=23.%20PSoC4is probably written in C. I can't imagine they've written (all of) it in assembly.

author: mfro
author_rank: Regular Contributor
timestamp: February 11, 2023, 07:06:59 pm
content: Quote from: Sherlock Holmes on February 11, 2023, 05:57:54 pmProbably because nobody missed it, I assume. Application programs started by an OS usually don't need it as that will control stack pointer initialization.On the bare metal level, many µC don't need it as well: ARM pulls the startup SP from the vector table, m68k and ColdFire do the same.Even a PDP11 will have the stack pointer initialized from the hardware on startup.

author: DiTBho
author_rank: Super Contributor
timestamp: February 11, 2023, 07:24:06 pm
content: Quote from: SiliconWizard on February 10, 2023, 08:51:17 pmindeed. crt0 is fine.crt0.S -> assembly.

author: DiTBho
author_rank: Super Contributor
timestamp: February 11, 2023, 07:26:30 pm
content: Quote from: mfro on February 11, 2023, 07:06:59 pmMIPS dooes not.crt0 sets the SP.

author: slugrustle
author_rank: Frequent Contributor
timestamp: February 11, 2023, 07:50:44 pm
content: Quote from: Sherlock Holmes on February 10, 2023, 09:53:56 pmPIC10 / PIC12 / PIC16 don't have a software-accessible stack or stack pointer, for what it's worth. The call stack is implemented in hardware. It's possible to implement a software stack for data storage using indirect addressing, but it's extra work.PIC18s give the software access to the data at the top of the call stack via a special set of registers and the PUSH and POP instructions. The stack pointer is also directly readable and writable, but it points to a special segment of memory only accessible by the aforementioned means. PIC18s also have an optional extended instruction set mode which makes it easier to implement a data storage software stack than the series of PICs mentioned above.I suppose someone making a new programming language might not be looking to support 8-bit PICs. They're a good choice if you want to challenge yourself with a difficult target.You might also want to look into setting MCU option bytes. These are typically stored in flash or EEPROM and significantly affect the operation of the MCU, including such things as boot mode, startup oscillator configuration, ROM and RAM access protection, and others. Sometimes a special tool from the MCU vendor is used to set the option bytes, other times, the option bytes are specified with assembler directives or C intrinsics in the vendor's IDE. There can also be very small storage areas for checksums, unique IDs, or other similar data that are programmed by the same mechanism.Yet another MCU oddity is the inclusion of a separate flash area for data storage, typically with a higher cycle life (true EEPROM) and/or the ability to write this memory while executing code from program flash. Some users may want to initialize the data in this segment separately from the other segments.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 11, 2023, 08:10:51 pm
content: Quote from: slugrustle on February 11, 2023, 07:50:44 pmMany people have told me that PIC is so unusual that trying to support it with any kind of conventional programming language is almost not worth the effort or barely feasible. I am totally unfamiliar with the device, but let me ask - would you agree? I know of a very experienced PIC assembler developer who complains that C is a waste of time, generates much bigger code than he'd ever write just using assembler.

author: nctnico
author_rank: Super Contributor
timestamp: February 11, 2023, 08:17:58 pm
content: PIC is an acronym for Pic Is CrapBut you can write compact code in C for '8 bit' PICs, you just need to know HOW and what C constructs to avoid. Likely your developer friend doesn't know C very well and/or used a crappy compiler and/or forgot to turn the right optimisations on. But the result is C code that is so platform specific that you can't really port it to any other architecture (and vice versa). Nowadays super limited devices like PIC only make sense for extremely cost sensitive devices but nowadays there are even cheaper microcontrollers from China.

author: slugrustle
author_rank: Frequent Contributor
timestamp: February 11, 2023, 08:37:26 pm
content: Quote from: Sherlock Holmes on February 11, 2023, 08:10:51 pmI'd say that 8-bit PICs have an architecture that is not a good fit for a high-level language that behaves like C under the hood. Especially PIC10/12/16. One working register / accumulator, banked RAM, separate RAM and ROM spaces, ROM paging, hardware call stack. They're real fun to program in assembly, or at least I think so. PIC18s have extra features that seem explicitly targeted at making things easier on a C compiler.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 11, 2023, 09:51:01 pm
content: Quote from: slugrustle on February 11, 2023, 08:37:26 pmYes this does mirror what I've read, I'll take a look sometime, I'm curious as to what so unusual about the chip that makes a C like language so tough.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 11, 2023, 09:54:33 pm
content: I'm guessing that ELF obj files have something akin to "fixups" that COFF has. I assume further, that this is how this declared global array gets the runtime addresses of all those functions into each element:Code:void * g_pfnVectors __attribute__ ((section (".isr_vector"), used)) ={&_estack,&Reset_Handler,&NMI_Handler,&HardFault_Handler,&MemManage_Handler,&BusFault_Handler,&UsageFault_Handler,&SVC_Handler,&DebugMon_Handler,NULL,&PendSV_Handler,...}Because the absolute address of each of those functions isn't known at compile time...

author: GromBeestje
author_rank: Frequent Contributor
timestamp: February 11, 2023, 10:47:50 pm
content: Quote from: Sherlock Holmes on February 11, 2023, 09:54:33 pmAddresses of symbols are put there into the array, so yeah, these are known at link time. This is true for any function, whether one calls them, or puts their addresses in an array, one got to know their address. This should hold for ELF, COFF or any other binary format. I'm not familiar with the concept of fixups, but before linking, there is a name, an after linking, the address. should hold for any format.edit: typo

author: T3sl4co1l
author_rank: Super Contributor
timestamp: February 11, 2023, 11:38:51 pm
content: Yeah those are known at link time, the runtime values are the same of course but the lower level description is the stronger one (runtime equals linktime if static, runtime is unknown at linktime if dynamic).Note that the compiler per se doesn't know any of those addresses, necessarily. Only if they're in the same module (which all the defaults here are, but, not once the stubs are overridden). The compiler leaves offsets in the object code, for the linker to replace with addresses once they are known.Tim

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 12, 2023, 01:49:15 pm
content: Thanks gents, here's some explanation.http://netwinder.osuosl.org/users/p/patb/public_html/elf_relocs.htmlI wrote a ton of this for COFF on a previous compiler project but that was 25 years ago and I'm a bit rusty.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 12, 2023, 04:56:52 pm
content: Why does a linker script need to specify KEEP for some section when the source code that declares the item includes the attribute "used" and the attribute naming the section?

author: eutectique
author_rank: Frequent Contributor
timestamp: February 12, 2023, 05:09:22 pm
content: The documentation says exactly why:https://sourceware.org/binutils/docs/ld.html#Input-Section-KeepAttributeusedserves the same purpose, but in the source code.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 12, 2023, 05:33:26 pm
content: Quote from: eutectique on February 12, 2023, 05:09:22 pmYes, so we don't need to specify both? it's fine to do so, but either one would generate the same result?

author: eutectique
author_rank: Frequent Contributor
timestamp: February 12, 2023, 06:56:12 pm
content: IME, one is enough.Say, you want to have a version string in your binary -- it does not matter where it is placed, it just should be there somewhere.Save the following asversion-string.cand execute the three commands in the comments:Code:// arm-none-eabi-gcc -Os -Wl,-lnosys -o version-string.elf version-string.c// arm-none-eabi-objcopy --output-target=binary version-string.elf version-string.bin// strings version-string.bin#include <stdlib.h>//__attribute__((used))static const char version[] = "(@) gimbal-2.7.3-RC1-ga865e9142-dirty";void exit (int code) {while(1){}}int main (void) {return rand ();}The output will be:Code:>%>3>>%>3>Now uncomment__attribute__(used), build and enjoy:Code:>%>3>(@) gimbal-2.7-RC-ga865e9142-dirty>%>3>Note that there is no explicit linker script.You can achieve the same effect with KEEP() directive in the linker script and no__attribute__(used), but for that you would need to keep the constant name in the source and in the linker script in sync.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 13, 2023, 02:50:02 pm
content: There are many things which are required by hardware. But also there are lots of other things which are made by convention. When you write a new language you're bound by what is required by hardware, but you're not required to follow any conventions. Many conventions are traditional, and getting rid of them has certain benefits. For example, you can get rid of the linker entirely and do your own linking. People will never have to edit linker scrips as there's none. This way you can build your own ecosystem, with your own rules.On the other hand, if you follow all the conventions, you will be able to produce files which are linked with existing object files compiled from C or assembler. This will structure your language as C, but with different syntax. Borland built a Pascal compiler and further used it in Delphi. But people were pressing them - they wanted to do things which can be done in C (after all Windows API was written in C) - so they modified Pascal to the point where it could do everything as C. The authentic Pascal things, such as pascal strings, were inefficient, so people started using C equivalent - zero-terminated strings. This may happen to your language as well (if it lives long enough).

author: eutectique
author_rank: Frequent Contributor
timestamp: February 13, 2023, 03:44:36 pm
content: Long-forgotten scent ofSymbian OS Descriptors... And it was all C++, in and out.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 13, 2023, 07:42:22 pm
content: Quote from: Sherlock Holmes on February 10, 2023, 09:53:56 pmI think you already know my answer, which is "no".First point is that it's completely target-dependent and by nature you may more or less always need to resort to CPU instructions to set up some stuff depending on the target. Supporting that in a "high-level" way makes almost no sense. Unless you restrict your language to just a specific target.Second point that I already made is that I personally don't think the 'one-language-fits-all' paradigm is a good idea. It is flawed, has been tried before, and either leads to monsters or to failures. Sometimes both, even though some monsters have become successful, while they should probably have become failures anyway.Mixing languages in a single project and using what is most adapted to the task at hand for various parts makes more sense, is usually cleaner, and is closer to 'engineering' - using appropriate tools for different aspects of a design rather than trying to force one tool for everything. I can use a hammer on a screw, but it's not going to be pretty.So the main goal should be to make interfacing easy, and that's what you get using standard ABIs and linkers.Just my 2 cents anyway - I'm sensing this isn't really compatible with your approach here.

author: nctnico
author_rank: Super Contributor
timestamp: February 13, 2023, 08:03:42 pm
content: Quote from: eutectique on February 13, 2023, 03:44:36 pmSymbian... where is the vomit emoticon when you need it. Yes, it was C++ but the solution was horribly contorted to work around limited resources. IMHO the failure of Nokia was to hanging onto Symbian for way too long.For an upcoming project I'm about to get started on, the low level stuff will be C with micropython on top of that to implement the logic. Interesting times...

author: westfw
author_rank: Super Contributor
timestamp: February 13, 2023, 08:04:21 pm
content: QuoteI consider it "really important" that a language be able to somehow include subroutines written in the target assembly language. (Preferrably NOT using some weird and constrained "inline assembly" syntax.) It's bad enough that the gnu assembler "takes liberties." And the easiest way to do that seems to be supporting one of the same linkable binary formats of an already existing assembler.(You should support industry standard ABIs for function calls as well.)AFAIK, elf was designed for "big iron", and there is no particular reason that it is used for embedded targets (rather than one of the other linkable formats (like coff) other than the fact that so many embedded compilers are based on gcc, and by using elf format they inherit the relatively large swath of binutils utilities for free.

author: eutectique
author_rank: Frequent Contributor
timestamp: February 13, 2023, 08:19:25 pm
content: Quote from: nctnico on February 13, 2023, 08:03:42 pmAt the time, they developed 3 platforms: S40 (non-Symbian), S60 (Symbian), and S80 (Symbian), all competing inside the same company.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 13, 2023, 11:25:47 pm
content: I think this is an interesting area. I totally agree that interfacing to code written in other languages is essential, there's plenty of detailed info about the various calling conventions around and these are rather mechanical ultimately, just a matter of generating code that meets the needs of that interface.In my first compiler in fact I was able to call code written in assembler from PL/I, here's a sample of thathere. This is how the early builds were able to do any interaction with the outside world. That assembler code used the same prolog instruction sequences as PL/I itself but that was just a technicality.So yes, being able to interact with other code is a definite, not something I envisage any major problems with.But the whole grey area of what should or should not be provided by the language is, well, a grey area.So I'm interested in this whole MCU start up problem, I'm currently spending time understanding that and the variants of it. We abstract call/return and interrupt handling so what dictates whether we can abstract something or not? Frankly setting an stack pointer register to some value seems like a simple abstraction, if it isn't if there are cases where it's not then I'd like to learn about those and take a look.The C language today demonstrates how some of these questions were never thought about when the language was designed, so I want to learn from that and think about them as the design is unfolding.If you look at some typical MCU code you can see a large number of functions all decorated with various special __attribute__ directive, perhaps specifying an ELF section name or specifying some calling convention and so on. Well why? why not be able to specify that collectively? so that's what I did:Code:traits proc(naked, section(".text"), cold); // specify some procedure traits for all of these...// put our handlers hereproc Reset;end;proc Systick;end;end;This might seem like a small point, be able to specify such things for multiple functions at once but it has a direct bearing on how we use a language for systems programming, in fact Clang has a rather convoluted way of doing this with its "push" and "pop" for attributes, likely because the C grammar is so difficult to expand any simpler way.So whatshouldwe use to decide whether some mechanism be language innate or bolted on with (say) embedded assembly code? Portability is always important, embedding assembler is not portable. Now perhaps there's no choice, it could be so specific that there's no choice but each case needs to be considered against some criteria.So this is all I'm saying, there are no rules, so what should guide us? how do we decide if some capability has a place as an innate language feature or something outside of the language? Why is assembler needed at all for MCU software? When do we make some operation an "intrinsic" and when we do have the code generator create it? Compilersgeneratevery specific code, it is what they do.I'd venture to say that the only reason we need stack pointer register setup code to be written as an assembler instruction is precisely because C has no support for the idea, if it did whywouldn'twe use it? If we expect the ability to have a language generate aplethora of differentcall/return sequence why can't we expect the same for stack initialization sequences?Finally why tolerate so many of these things being vendor specific? Much of what I'm saying here just reflects an aspiration to start to rationalize much of this, a new language provides a superb opportunity for that.

author: coppice
author_rank: Super Contributor
timestamp: February 13, 2023, 11:56:50 pm
content: Quote from: eutectique on February 13, 2023, 08:19:25 pmOne of the key reasons for the downfall of large companies is getting into such a dominant position that it mostly competes within itself, ignoring the outside world catching up and passing it by. Once that starts to happen, it seems rare for a company to take stock of its position, and correct its course. I read that in the 80s and 90s Sony's plants had big posters with slogans emphasising that they needed to compete with those on the outside, not the inside. I heard they had BMW posters that were nothing to do with cars. Its was Beat Matsushita Whatever.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: February 14, 2023, 03:04:16 am
content: Regarding stack: it's hardware that controls execution flow (namely when returning), as well as storing data (usually; but this could be solved with a block of RAM and a software solution, albeit a big mess with interrupts considered). It's not an abstraction -- it is anenabling assumptionfor those abstractions, I would say. Most C machines assume some kind of stack machine, enabling recursive function calls. (I don't know offhand how the C VM expresses this, if it does any more concretely than this.) C doesn't have a stack register as such, but almost all(?!) implementations depend upon one, the value of which is almost never written directly (i.e. used relative only, such as increment/decrement in PUSH/POP, or ADD/SUB for allocating larger stack frames), but which must be perfectly consistent throughout program execution. The only consistency necessary then is the initial value -- a special case which can then be handled however you like, so, a single line asm() is good enough.The main C-ish exception is stuff like task switching, or the uh, I forget the function calls, there's something in, is it stdlib, or one of the less core libraries? Something like generalized gotos (jump to entirely different functions, not just within one -- or maybe I'm remembering a GNU-specific thing?), or for implementing multitasking (save registers and stack, switch stack and load different set of registers, resume). Which, if I remembered what they were, I'd go look up a typical implementation, but... I'm just going to guess they use a little asm() and that's that?Tim

author: ejeffrey
author_rank: Super Contributor
timestamp: February 14, 2023, 05:41:18 am
content: Just to be clear, it is a misnomer to say "the stack pointer must be set by the linker or with inline assembly" The linker doesn't generate code (mostly), and certainly doesn't initialize the stack pointer. It does assign the top (and possibly bottom) of stack address to a symbol which can be access by either assembly or C code. But in the case of a fixed stack that isn't needed, you could equally well just set those in C or assembly.On some platforms you do indeed need to set the stack pointer at startup time (usually using that symbol defined by the linker), but not all. Arm Cortex-M does *not* require the startup code to initialize the stack pointer, that is done automatically by the CPU from the vector table. The vector table can be constructed entirely in C (again, with the linker simply assuring the table gets placed at the correct location). Other platforms simply initialize the stack pointer to a platform specific value at startup, while some simple processors have a small hardware stack rather than using RAM (although these have limited support for C).So on some platforms it is unnecessary and others it is even impossible to set the stack pointer at startup. On Arm Cortex-M, people use assembly for startup code because that is what they are used to, and because it's pretty simple. But you can absolutely do it in plain C. Explicitly setting the stack pointer in the startup is just superstition, it's not needed at all.As for having a generic compiler provided mechanism to set the stack pointer... I'm not sure how that would work. I think you are going to have to show some sort of concrete proposal for what it would look like and how it would work. Generally the stack pointer is managed exclusively by the compiler for the purpose of implementing the C abstract machine. If you go manipulating the stack pointer unilaterally, you are going to immediately break local variables and return addresses. It's hard to see how you would do that in a way that was useful and not so machine specific that you might as well do it in assembly.

author: westfw
author_rank: Super Contributor
timestamp: February 14, 2023, 09:08:13 am
content: Some AVR C compilers use a separate stack for code returns and for local variables...Many architectures allow any register to be used as a stack pointer.Allowing a compiler to modify key CPU registers that affect the runtime behavior seems like a really bad idea. What is supposed to happen if a function, with local variables on a stack, changes the stack pointer and then returns?You can make SP assignment out-of-band compared to normal program statements, but that's effectively what the linker does now, in a language-independent way.

author: DiTBho
author_rank: Super Contributor
timestamp: February 14, 2023, 09:30:04 am
content: Quote from: SiliconWizard on February 13, 2023, 07:42:22 pmIndeed, but how many times have we to *repeat* it?It's always the same question, always the same answer.

author: DiTBho
author_rank: Super Contributor
timestamp: February 14, 2023, 09:34:52 am
content: Quote from: westfw on February 14, 2023, 09:08:13 amYes, MIPSRight now I'm writing a restricted assembly MIPSII monitor-bootloader for my MIPS32LE router (IDT chip, I have some issue with the cache), and it's very convenient to be able to decide which register to use as stack pointer.I decided r1 for all project modules.

author: NorthGuy
author_rank: Super Contributor
timestamp: February 14, 2023, 01:56:03 pm
content: Quote from: DiTBho on February 14, 2023, 09:34:52 amIn what way? They're all the same (except r0). How can one be more convenient than others?Later they figured out that having designated registers for common uses is beneficial. microMIPS has instructions with hard-coded gp (r28), sp (r29), and ra (r31) registers, which improves code density.

author: DiTBho
author_rank: Super Contributor
timestamp: February 14, 2023, 02:58:08 pm
content: Quote from: NorthGuy on February 14, 2023, 01:56:03 pmit's convenient that you can choose which register works as SP and design your modules as you wish and need.

author: DiTBho
author_rank: Super Contributor
timestamp: February 14, 2023, 03:04:13 pm
content: Quote from: NorthGuy on February 14, 2023, 01:56:03 pmyeah, that's another point of view, more important than the freedom to assign a purpose to a registerin my case, it's convenient because accessing the registers-file through the ICE costs less than accessing a dedicated register.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 14, 2023, 03:16:15 pm
content: Well again, very interesting comments on all this, much appreciated.Yes, I did learn that the STM32 devices (Arm) expect the stack pointer to be the address specified in the first "slot" of the (initialized) vector array. Now the value of that address is (indirectly) supplied in C source code as follows:Code:void * g_pfnVectors __attribute__ ((section (".isr_vector"), used)) ={&_estack,&Reset_Handler,&NMI_Handler,&HardFault_Handler,...}The numeric value is directly specified inside the associated linker script:Code:...MEMORY{FLASH (RX) : ORIGIN = 0x08000000, LENGTH = 512KSRAM (RWX) : ORIGIN = 0x20000000, LENGTH = 128K}_estack = 0x20020000;SECTIONS{.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)). = ALIGN(4);} > FLASH...So there are several things going in here, a numeric value in one place, a variable name in two places, a section name in two places, and alignment in one place and so on. I understand all this, I appreciate what's going on, but I also look at it and ask myself "Could the language do more here? if so then how? The details in that.isr_vectorsection specification could be supplied by the compiler - in principle - alignment is set by thesh_addralignfield in the sections header.I suspect that some vendors' compilers do support the__attribute__value for this, we havesectionandused(equivalent to KEEP it seems) andalignmenttoo. There's also an attribute at I read about, suggesting the0x20020000could also be stated in source code.So part if what I've been looking at is simply how could a new systems language handle that any better? Not that it should, but could, design in additional features that give us that ability in a simpler way. this is likely just academic, me thinking aloud but this does fall within the scope of language design, not suggestion we never use the linker but only that we provide options to do these things more simply.Now, about the concerns that adjusting the stack pointer within the language is "dangerous" well the fact is that we canalready do this today in C, it's just that we have to embed assembler to do it. So I'm not suggesting an increase in control or risk or specificity only analternativeway to represent it. An intrinsic (for example) would be less dangerous that an unconstrained ability to embed arbitrary machine instructions - IMHO.Admittedly all of this is very tiny part of a new language, very tiny indeed but I just want to be cognizant of the fact that what we see as common practice today might in some cases be capable of improvement and those improvements might have a deep influence over how the language looks.I mentioned recently that I'm quite interested in an ability to write an MCU app without the use of a linker, not to dispense with the linker in all cases that's not what I mean, but I'd like the language to be capable of consuming several source files and generating a .EXE ELF file (or COFF), so from that standpoint - for the language to allow that - we need ways to specify, in the source code, certain details.Of course a crude way to do that is to embed stuff in the source code, that is a clone of linker directives, then dynamically create the script and then dynamically invoke the linker, but that's now what I'm driving at, it is the language itself, what would it need to support linguistically in order for the compiler to just generate an ELF executable, this is just food for thought...

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 14, 2023, 03:21:47 pm
content: You guys are MCU experts so to speak, so may I ask, could we list all of the known concrete cases where one absolutely has to embed assembler in C code?The first can be stack pointer initialization, some devices need that so that's the first, any more???

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 14, 2023, 03:35:23 pm
content: Look at this too, this is a simple STM32F446RE project, it's far from clear to me when or why one would need to enable that__asmstatement given the device expects the SP to be slot zero of the vector table. The comment is disconcerting too "If you encounter problems with accessing stack variables"...Code:void __attribute__((naked, noreturn)) Reset_Handler(){//Normally the CPU should will setup the based on the value from the first entry in the vector table.//If you encounter problems with accessing stack variables during initialization, ensure the line below is enabled.#if defined(sram_layout) || defined(INITIALIZE_SP_AT_RESET)__asm ("ldr sp, =_estack");#endifvoid **pSource, **pDest;for (pSource = &_sidata, pDest = &_sdata; pDest != &_edata; pSource++, pDest++)*pDest = *pSource;for (pDest = &_sbss; pDest != &_ebss; pDest++)*pDest = 0;SystemInit();__libc_init_array();(void)main();for (;;) ;}

author: NorthGuy
author_rank: Super Contributor
timestamp: February 14, 2023, 04:45:22 pm
content: Quote from: Sherlock Holmes on February 14, 2023, 03:16:15 pmIt cannot. If you want to link your language with objects written in C, assembler, or any other languages, you should leave all these things to the linker and religiously follow the existing conventions - you're not allowed any freedom in this area.The MCU initialization code is already written (in assembler, C, or whatever) and working. There's no reason for you to write your own. And if you do write your own, if you allocate things in your own way for whatever reason, you need to make sure that the linker and other languages can play along with it. And by doing this you will inevitably recreate the existing initialization, or something very close to it.For example, C uses stack for local variables. RTOS creates many stacks and switches them as needed. While it is not necessary to have a stack at all, you must have a stack if you want to support C and existing RTOSes. Moreover, the stack should work exactly as they expect. Especially if you want your language to work under RTOS. So, nothing new you can do here.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 14, 2023, 05:26:06 pm
content: Quote from: Sherlock Holmes on February 14, 2023, 03:35:23 pmIt's a weird comment. Of course the hardware stack pointer initialization works, and it's the same for all Cortex-M MCUs. There is nothing uncertain about that.There is a reason why one wants to write stack pointer explicitly, namely writing abootloader. The hardware feature of setting stack pointer is convenient, but only works with the actual CPU reset signal (hardware reset pin, or software reset triggered by writing to a CPU control register). If you want to, by software, just jump somewhere else "as if" the MCU booted there, then you have to set the stack pointer manually. This is what a typical bootloader does; it reads the two first DWORDS from the application binary and sets SP and PC accordingly, emulating what ARM HW normally does.Maybe that define becomes handy when one uses a broken-by-design bootloader which fails to do one of its two jobs; I don't know.

author: westfw
author_rank: Super Contributor
timestamp: February 14, 2023, 06:01:16 pm
content: Arm cm initializes sp from 0x0 at reset. If you’re using a bootloader, it could theoretically fail to load the application sp from the app’s vectors (if it even has them. Not all arm cm0 have the ability to relocate the vector table, so an app might use some other scheme for interrupts.

author: ejeffrey
author_rank: Super Contributor
timestamp: February 14, 2023, 06:28:39 pm
content: Quote from: Sherlock Holmes on February 14, 2023, 03:16:15 pmYes. But you could just as well do:Code:#define STACK_TOP 0x80000000void * g_pfnVectors __attribute__ ((section (".isr_vector"), used)) ={(void *)STACK_TOP,&Reset_Handler,&NMI_Handler,&HardFault_Handler,...}That would "avoid" needing to specify the stack address in the linker script. I'm not sure why you would want to do that, but it's possible. You still need to define the sections in the linker script. This is something you *could* avoid with attributes, similar to the ORG assembler directive you could explicitly specify the base address for objects. I think it's a lot more convenient to have the memory layout defined separately from the program code, especially as soon as your project is more than a single source file. If you just don't like the linker syntax, you could certainly make an alternative, but at that point, you are working against years of development and experience and widespread compatibly.QuoteI'm not sure why you would want the program code to be intermixed with memory layout. Generally, defininng symbolic constants in a consistent fashion and then using them: whether it is variables like _estack or section names like _isr_vector is good regardless of the language. You could certainly define a language that included more linker commands directly in the "source code" but I think you would still want to separate the declarations in the same way 99% of the time.

author: DiTBho
author_rank: Super Contributor
timestamp: February 14, 2023, 07:56:34 pm
content: be mind, GNU/AS .org does not work like Motorola .org

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 14, 2023, 11:06:09 pm
content: Well after much discussion with others it has emerged that a useful improvement will be to have a thing that looks a bit like a procedure or function but is not, it could be simply called an "intrinsic" a new kind of entity. Then implement things so that it is onlyinsidean "intrinsic" that one can embed assembler, it will not be possible to embed assembler freely inany oldcode.Furthermore an intrinsic will specify a target in some way, perhaps as simply as "target(x64)" and then only the intrinsics that target thesametarget (as the compiler when it builds) will be visible and accessible to the rest of the code and if there are multiple intrinsics with the same name but differing targets, the can be resolved on that basis.An intrinsic willappearlike a procedure or function when referenced, but will not be, it will represent a literal embedding of the associated machine instructions with support for passing arguments and returning values in a similar way to many of today's common C intrinsics.

author: ejeffrey
author_rank: Super Contributor
timestamp: February 15, 2023, 02:43:08 am
content: I don't understand how that is different than a C function with the entire body an inline assembler block. Do you want the compiler to write the prologue and epilog and handle the ABI? Or do you want to do all that in assembly?

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 03:57:49 am
content: Quote from: Sherlock Holmes on February 14, 2023, 11:06:09 pmYes, this pattern is already in use in some high-performance computational software in Linux. I've used it myself. In GNU C/C++, it look roughly like__attribute__((always_inline, target("machine-dependent-options")))static inlinereturn_typename(args...) {return_typereturn_value; // and any other local variablesasm volatile( "extended assembly":output-operands//return_value:input-operands:clobbers);returnreturn_value;}Seecommon function attributesandextended asmfor the details. This is supported by both GCC and Clang C and C++ frontends, although some of the attribute names and definitions vary a bit; better use preprocessor macros.The key here is that it isextended assembly, not just copy-pasted to an assembler and the result included in the object code. Instead of specifying exact register names, you defineoperands, with theirconstraintsspecifying which registers the compiler may choose for each operand. Each operand is automatically numbered in order they're defined from%0onwards, but can also be named. For example, on 32-bit x86, you might useCode:__attribute__((always_inline, const))static inline int64_t mul32(const int32_t lhs, const int32_t rhs) {int64_t result;asm volatile ( "imul\t%2": "=A" (result): "a" (lhs), "r" (rhs));return result;}instead of((int64_t)((int32_t)(lhs))*(int64_t)((int32_t)(rhs))), to ensure you get a 64-bit product from two 32-bit multiplicands. The x86 imul machine instruction requires one multiplicand to be in the eax register (aconstraint), but the other can be in any register (rconstraint); and the result is put in edx:eax register pair (Aconstraint).(We could also have used the named version,"imul %" : "=A" (result) : "a" (lhs),  "r" (rhs); but the numbering is more common. I find the named version easier to maintain.)(In case you wonder, the GCC convention is to add newline and tab,\n\t, after each instruction, but not after the last instruction. Due to how GCC does this, this makes the code look "normal" if someone compiles the file to assembly using-S, like e.g.Compiler Explorerdoes.)When inlining, the compiler can choose the registers used to reduce unnecessary register moves. This is very important, because it means that instead of simply plopping down a copy of the inlined body, the compiler can optimize both the inlined body register choices, and the surrounding code, at the back-end/machine code level. (In other words, this is not usually translatable to intermediate representation... but it can yield pretty tight inlined code.)Obviously, looking at this example above, the syntax GCC/Clang use for this is pretty bad. Nobody remembers the machine constraints; a list of allowed registers (and memory reference types) would be much better, for example. Also the named operand format in the assembly,%, is pretty cumbersome.One option to consider is to let the entire function/intrinsic body be written in an assembly-like language, which is compiled by the same compiler, so that it can determine the possible registers to be used automatically, for example. This would be a very nice mechanism to embed other-language function-like objects, like OpenCL/CUDA/GPGL computing kernels, pixel shaders, and so on, in a much easier to maintain way; but it would require either a multi-language compiler, or co-operating compilers. But this is just one option to consider, and I'm just describing what I've thought of before myself having used the above pattern in real-world code (and how annoying it is to maintain – faster to rewrite, really, than it is to debug); I'm pretty sure there are even better ways of doing this.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: February 15, 2023, 04:43:44 am
content: Extended syntax is pretty neat. It's peculiar, and the documentation isn't exactly exhaustive (I think... the docs are old, somewhat out of date?), and it varies between targets (it follows the traditional asm style(s) of the target), which I'm sure is a pain to keep all the docs updated or whatever. And of course it's peculiar, it has to encode additional information that asm never otherwise needs; in short, you need to tell the compiler what it can do with it and where/how.I guess I just wish it weren't so god awful ugly? Get rid of all the quotes and tabs and newlines, it's all in the source as it is, just consume it verbatim, come on... Anyway.Like, I finally sat down and crafted this operation last year:Code:/***Multiplies two 16-bit integers, with rounding, as an intermediate*format in 16.16 fixed point, returning the top (integral, 16.0) part.*/uint16_t asm_umul16x0p16(uint16_t a, uint16_t b) {uint16_t acc;//acc = (((uint32_t)a * (uint32_t)b) + 0x8000ul) >> 16;__asm__ __volatile__("mul %A, %A\n\t""mov r19, r1\n\t""mul %B, %B\n\t""movw %A, r0\n\t""mul %A, %B\n\t""add r19, r0\n\t""adc %A, r1\n\t""eor r18, r18\n\t""adc %B, r18\n\t""mul %B, %A\n\t""adc r19, r0\n\t""adc %A, r1\n\t""adc %B, r18\n\t""subi r19, 0x80\n\t""sbci %A, 0xff\n\t""sbci %B, 0xff\n\t""eor r1, r1\n\t":  "=&d" (acc):  "r" (a),  "r" (b): "r18", "r19");return acc;}On AVR8, there is only 8x8 hardware multiply; this gets used inline well enough (and even 8x16 if you only need the low part), but 16x16 is implemented with a software library (_mulhisi3, etc.)*, the object of which is static, no optimization semantics or anything -- so it's never inlined, and usually makes a mess when patching up to whatever registers the calling function is using.*Which interestingly enough, don't strictly obey the ABI; they bend it a bit to get better register utilization. (So, it's not even as bad as it could be. Mind, not to say what they did is bad in all respects; it's simply a cromulent solution. Clearly there is room to improve, but it's certainly better than a completely stand-alone function call would be.) I'm not sure what custom patchups/hacks are internal to GCC to enable this.So I copied this bit from an earlier pure-asm module, fixed up the register allocations, and used the extended syntax. This can be inlined properly, and only uses two extra registers (clobber), which are normally free to use (r18-r27 are call-saved registers i.e. any function can use them without having to push/pop).At least, I think I got the allocations and everything right? Maybe there's a few edge cases I forgot, but it compiled correctly (output matches on inspection) in the contexts I used it on. (Yes yes, one can work out all the constraints perfectly, well, must be nice to be able to reason about these things. I'm a bit hopeless on combinatorial problems like that, I'm afraid.)So, besides being a bit shorter, it's got no calling overhead (when inlined), and less register pressure overhead, which did nicely for its purpose. I forget if it fully halved the cycles in the critical path, but it did help out.Tim

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 06:46:43 am
content: Sometimes we need primitives whose implementation is defined at the machine instruction level, it's that simple.While such oddities can always be linked in as an external object file (compiled by another toolchain), having the small primitives be inlined and optimized by the compiler to efficiently integrate into the machine code generated by the compiler makes for better end results. In particular, the primitive may be so often used in some task that the function call overhead becomes unacceptable. Rare, but does happen.Typical use case examples include math and locking primitives, and foreign function interfaces. For example, to do an OS syscall, you cannot just call some given address on most architectures; specific parameters need to be put into specific registers, and then a specific instruction or instruction sequence executed. (On some OSes and architectures, there may be more than one such – consider SYSV ABI on x86-64, and the int 80h (for 32-bit support) and native 64-bit SYSCALL interfaces.)I do not believe any kind of exhaustive list is possible –– other than "whenever we do need machine instruction level control of the generated code". But that can happen in many different situations from MCU to OS development to crucial performance increases in heavily-used SIMD tasks; things like FFTs, bignum libraries, float emulation, and JIT compilers.

author: DiTBho
author_rank: Super Contributor
timestamp: February 15, 2023, 08:01:12 am
content: Quote from: Nominal Animal on February 15, 2023, 06:46:43 amYup!Just, in Avionics you cannot use assembly_inline, so when you need machine instruction level, you have to *segregate* code into assembly modules.pure C module -> is a .c file without *ANY* assembly_inlineHigh Level Application (aka "mission" in MTC terms (mission tactical computers) -> pure ada -> .ada fileHigh Application level || glue application level(1) (between HAL and iBSP -> pure C -> .c fileglue application level (aka BSP) -> pure C module -> .c filecrt0, crt1 (C run-time) || machine specific -> assembly -> .s fileboard constraints -> linker file and BDT stuff -> .ld, .bdt fileIt's even simpler and makes QA life simpler because even IA-assisted tools don't need complex mixed rules (too prone to mess up things), and also for humans because they immediately know the level of what they are observing.source level ---> source rulesedit:(1) the simplest example of glue application level is "unchecked conversion" functionsuint8_t ---> uint32_tuint32_t uint8_to_uint32(uint8_t)On MIPS and PowerPC is implemented with a simple assembly function that does nothing but returning the same register used as parameteryou invoke the function and put uin8_t into registerX, and the function returns with uin32_t into registerXit's "unchecked conversion" because its body is beyond the C implementation (body), so where the "code validators" don't look there ()is it useless? well ... it's a very usefulhacktrick to avoid wasting time with all the C-nonsense to avoid casting.(of course you have to comment the assembly part, which however is instrumental, so it is tested directly by ICE)

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 09:10:10 am
content: Quote from: DiTBho on February 15, 2023, 08:01:12 amOne related interesting detail is the GCCconstfunction attribute: such functions do not affect and are not affected by the (observable) state of the program; they do an operation on their parameters only.Considering static code analysis, these inline snippets can be fully specified by what they can access and what they clobber, at the hardware level. Let's say the developer specifies these explicitly, as say "effects" (need better term, cannot think of one now), somehow.When the compiler can simulate the assembly –– which is necessary for it to auto-allocate operands, without the programmer doing so by specifying operand constraints –– it can also verify if those accesses conform to the developer-stated "effects", and thus include the static analysis down to machine code.That would beniiice.A big reason why I prefer mixing programming languages even within a single project, rather than trying to find a language that lets me do everything in it, is because it helps me keep things at their proper complexity level. Yet, as we know from e.g. PHP (which tries to be everything to everyone, with multiple ways of doing even basic string manipulation), we know that a single programming language that mixes approaches (object-oriented vs. procedural programming) and complexity levels, tend to be abused rather than used.It would be quite possible to have an ELF-derived object file format capable of recording the abovementioned "effects", even down to registers used for parameters (both in and out), but register allocation etc. has to be done at a higher level, sometime during compilation, before linking is possible. (By adding a custom ELF section (a different beast from symbol sections) that contains the additional information not included in the standard ELF symbol table.)There are too many differences between various instruction set architectures to create an abstract assembly language that would still be useful at the hardware instruction level, although one could probably get pretty darn close. Such an assembler by itself would be a very interesting programming language project, but it'd require a very wide experience across many different MCU and computer instruction set architectures.At the opposite end of the complexity level, I might like a declarative language where the order of side effects within each function is undefined unless explicitly defined by the developer. This maximizes the opportunities for optimization, since there is no strict "law" as to how side effects must occur; with only the necessary ordering (and perhaps atomicity) rules specified explicitly by the programmer. We can see this in functional languages, which for certain type of problems can compile to surprisingly efficient machine code binaries, simply by avoiding unnecessary computation, and reordering the computation for best-effort efficiency. I'd also like the compiler to interleave unrelated operations, maximizing throughput on superscalar architectures. I've mentioned elsewhere about data-parallel loop optimizations already; in this one, all loops could be by default data-parallel, i.e. run in unspecified iteration order, unless a specific order is required (by an ordering rule, or by the data dependencies).Many operations even in my MCU code is like that: I don't really care what happens when, as long as certain set of expectations/limits is fulfilled.Mixed in there are often those small details where I really want/need a specific machine instruction sequence (leaving the exact register selection to the compiler).I wonder if this varying level of complexity and control is something other members here observe in their real-world projects?Practical real-world experience tends to provide very useful information in the design process.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 02:58:39 pm
content: Quote from: Nominal Animal on February 15, 2023, 03:57:49 amThanks, this is exactly the kind of thing I'm exploring, much appreciated.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 03:18:04 pm
content: Quote from: ejeffrey on February 15, 2023, 02:43:08 amThe precise syntax isn't pinned down yet and when some new stuff like this added one needs to step back and ensure it fits the existing "ethos" of the language, doesn't look like a "bolt on" job as Fred Dibnah would phrase it.I changed it last night too, so having "procedure" and "function" and "intrinsic" was unworkable because an intrinsic can look likeeithera proc or a func (returning something) so I made this a new attributeforproc/func and that is neater now:Code:procedure breakpoint(X) intrinsic(arm) ;emit ("bkpt 255");emit ("bx lr");end;Here the target is an arg to theintrinsicoption keyword.To answer your questions, it looks like a conventional proc/func block but is not, the compiler proper will process an "intrinsic" differently. Basically it will transform the embedded assembler into a byte sequence suitable for the target and will inline that byte block at the point it is referenced. There will likely be restrictions and stuff here but that's academic just now. This now opens the door for writing muti-target intrinsics where the resolution of the specific target is done at compile time, so one could "overload" so to speak.The key limitation on an intrinsic is that it must be capable of being converted to a machine byte block at compile time so we'll disallow an intrinsic from containing anything other than constant expressions perhaps and using optional arguments, there are several details to work out but this the big picture.The emit keyword represents the act of "assembly" generation and will allow the language to support this feature independent of potential differing implementations.The precise nature of the assembly text, its format and so on isn't cast in stone.Consumer code will reference an intrinsic in the usual way, either via a "call" statement or an operand in a expression, again, in each the reference being literally replaced with the generated machine byte block.Finally this provides an nice basis for implementing innate, inherent language intrinsic too that are written as part of a compiler implementation, so it brings together the usual idea of a compiler intrinsic and user written embedded assembly code.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 03:21:20 pm
content: Quote from: T3sl4co1l on February 15, 2023, 04:43:44 amThis is a very interesting real-world example.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 03:32:51 pm
content: Quote from: Nominal Animal on February 15, 2023, 03:57:49 amYes, the register protection is an interesting point, I guess today the coder has to be very careful to save and restore as needed, some machine help here would be nice. I looked at ARM assembly language (Thumb) in some detail and it seems rather straightforward to design atext->binaryoperation so the actual assembler conversion can readily be coded in a uniform manner in .Net, a small library per target seems very doable.I did a code generator years ago for X86 (originally 16 bit then later 32 bit) and found that rather interesting work, this is simpler too, literally just a text conversion and validation, perhaps even doable without a parser, just a simple lexing/regex might even do it.The operation of the compiler choosing registers is quite interesting but would require the compiler to know the exact nature of the code at ever call site, that's a global analysis, no room for linking in precompiled object files that might also leverage an intrinsic.I too find the text strings a little unwieldy, so I'm wondering if there might be a abstract grammar that could work for any assembly language, I'd need to some research on this as it could make the code much more readable and open the door to syntax coloring for assembler code. (take a look intolanguage servers, they are very powerful).The Antlr grammar tools are very powerful too, one can define context aware grammars so that when some thing is recognized, the tokenizer can enter a mode specific to that thing. This literally would enable support for this, that is the compiler when it sees the intrinsic target, can then enter a target specific assembler mode and literally recognize that specific targets assembly syntax, totally eliminating the crude strings.This is precisely how I'd implement say comments with embedded XML directives like seen in Java or C#, its designed for this kind of parsing.If you're interested, here are someformal Antlr grammarsfor a bunch of targets.

author: DiTBho
author_rank: Super Contributor
timestamp: February 15, 2023, 03:39:16 pm
content: Quote from: Nominal Animal on February 15, 2023, 09:10:10 amGreenHill' CCis a bit different from Gcc, but yup, there is no problem if you segregate assembly-inline into .c files that are somehow known - in your project - as "not pure C" file.Umm, just make a folder, and put them into it. Don't mix pure C file with impure-because-full-of-assembly-inline-s, that was the point, because it makes the ICE-AI go nut, QA guys angry, etc.p.s.the big challenge is ... when you have .o files that come form Haskell sources, and you need to link them with .o files that come from C89 sources and Ada sources.Haskell + C = difficult (crt0 + crt1 + C-gluecode for IO monads, feasible )Haskell + C + Ada = more difficult (crt0 + crt1 + ada-RT + C-gluecode for IO monads, more difficult but feasible )Haskell + Ada = very difficult (ada-RT + ada-gluecode for IO monads?!? not impossible, but toooooo difficult)Quote from: Nominal Animal on February 15, 2023, 09:10:10 amThat's why I suggested OP to get experienced before starting loooong discussions like he did in his topic.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 04:02:52 pm
content: Quote from: DiTBho on February 15, 2023, 03:39:16 pmandQuote from: DiTBho on February 15, 2023, 08:01:12 amIs this a true statement? Ada fully supports embedding assembler code, Ada Core's GNAT Profully supports thisand is the epitome of mission critical software development tools and languages.So who said you "cannot use assembly inline" in an avionics setting? what sources do you have for this claim? I do hope you have sufficient experience to discuss this here...

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 04:07:50 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 04:02:52 pmIt's not "incapable of using", it's "not allowed to use", i.e. due to design guidelines required by the client the software is written for. Specs likeMISRA C.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 04:09:40 pm
content: Quote from: Nominal Animal on February 15, 2023, 04:07:50 pmThat document does not contain the term "assembler" or "assembly" or "inline".

author: DiTBho
author_rank: Super Contributor
timestamp: February 15, 2023, 04:17:02 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 04:02:52 pmGNAT is not used in avionics, 99% of times, Green Hills Ada is what I find and useQuote from: Sherlock Holmes on February 15, 2023, 04:02:52 pmDO178B-C, with all of its integrated rules.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 04:26:31 pm
content: Quote from: DiTBho on February 15, 2023, 04:17:02 pmQuoteQuoteFromLockheed Martin Selects GNAT Pro for C-130J SoftwareAs I explained to you GNAT (which include assembly embedding capabilities in Ada source code) is used for implementing avionics software, I think that settles the matter.Quote from: DiTBho on February 15, 2023, 04:17:02 pmDO-178B is not a standard, it was in fact a guideline, you should be aware of DO-178C, the devil's in the detail as they say.But once again, what isactuallystipulated in this document that youinterpretas a prohibition on the use of assembler or inline assembler?

author: DiTBho
author_rank: Super Contributor
timestamp: February 15, 2023, 04:32:42 pm
content: Quote from: Nominal Animal on February 15, 2023, 04:07:50 pmYup, ... you are allowed to use it, but *ONLY* if the project is level D or E.Levels A and B are for supersonic aircraft units, war nuclear-powered andTyphoon-classsubmarines (with titanium hull, and it's an high-grade titanium), high speed trains, etc.Something similar to DO178B-level-A is what you find in nucler power stations, while Level C can be used in FoM and MotoGP (FoM = Formula One Managment, because it's not so different from their specs), and it's good in automotive.Level D and E are for domestic gears and common units used in offices and coffee/food machines.GNU/Linux is classified level E (poor my GNU/Linux router), while "Hardened GNU/Linux" is classified level D.(I don't know about OpenBSD ... but I suspect it's level D too)Level A,B,C also need certified compilers, and certified analysis tools, including certified ICEs and QA code validators.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 15, 2023, 04:39:44 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 04:26:31 pmIn the end, what the standard actually says, is the standard relevant at all, is the standard wisely chosen, is the design safe at all, does the standard help or hinder, does not matter. All that matters is what the clientthinksis important, and what the clientthinksthe standard says. If DiTBho's work gets accepted by his client, then that's it.I totally loathe stuff like that and try to avoid such red tape as much as possible, because it almost always results in a system with mediocre safety, for astronomical cost.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 04:40:56 pm
content: Quote from: DiTBho on February 15, 2023, 04:32:42 pmYet you wrote earlier:Quote

author: DiTBho
author_rank: Super Contributor
timestamp: February 15, 2023, 04:58:30 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 04:26:31 pmIt's not "a standard". You are asking here for "guidelines", and that's simply the cleanest guideline I have ever experienced in paid job experiences.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 05:02:30 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 04:40:56 pmBecause DiTBho referred to avionics projects, i.e. level A and B (and not D or E):Quote from: DiTBho on February 15, 2023, 04:32:42 pmSo, no conflict. Avionics = A, B; and A, B, C do not allow inline assembly. What's your problem?

author: DiTBho
author_rank: Super Contributor
timestamp: February 15, 2023, 05:06:35 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 04:40:56 pmcannot = not allowed, if it's a cognitive problem, consider like never written.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 05:36:25 pm
content: Quote from: DiTBho on February 15, 2023, 05:06:35 pmLet me guess, you'll next be sharing your proof thattrue = false? I can't wait...

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 05:41:58 pm
content: Quote from: Nominal Animal on February 15, 2023, 05:02:30 pmMy problem is that the claim that inline assembler is not permitted by some standard or other, in avionics software (in whatever context you care to dream up) remains unsubstantiated, anecdotal, conjecture.If as has been repeatedly asserted dogmatically, thisistrue then it should be a simple matter to quote said paragraph here and cite the source,that'smy problem.I am simply asking for evidence, yet none is being presented, so I consider it false, or at best a misinterpretation, unless you can show why I should regard it otherwise.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 06:04:58 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 05:41:58 pmYou mean, the kind where my experience is "anecdotes", but the opinion of a random person whose blog you quote is "proof"?No, you're playing the kind of social word games that you're used to playing in your workplace, instead of doing actual work.DiTBho does work for clients that require DO-178 compliance. They're just telling you what their clients demand.If you do not trust a single word DiTBho says anyway, why would anyone bother to prove to you anything related to what DiTBho says?We've tried that with electrodacus and aetherist, and it just does not work, even when we do. You'll find a way to dodge that and find some other detail to complain about.If you weren't such a colossal asshole, you would have just said "OK, my mistake; apologies." and moved on.Instead, you scrambled like crazy to find any detail at Wikipedia or StackOverflow/StackExchange that you could use to take a snipe at DiTBho.Stop it. This is a technical forum, not some social media politically-correct snide-fest. Talk tech, or take a walk.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 15, 2023, 06:29:27 pm
content: Quote from: Nominal Animal on February 15, 2023, 06:04:58 pmThis is a key point. I sometimes find myself in a situation, on this very forum, whereIhappen to be one of the maybe top-1000 experts on a subject, and average google result is 99.9% likely a weaker source. Yet, I'm aggressively being asked forsources. In such situations, I simply answer: "source: me". If that does not suffice, then it's the problem of the reader, and they can just f*** o**. The idea thatanyrandom blog post, or a hastily written random remark on a Wikipedia page anyone can modify, and no one bothered to properly maintain, is usable as a weapon against true experience from working on or researching a field.It is very very very difficult to find truly reliable sources. EEVblog forum is a surprisingly high-quality one. But of course, one always needs to apply critical thinking toanysource. Just concentrate on thecontent, not to the fact it was posted on a forum instead of a static website, or Wikipedia.

author: tggzzz
author_rank: Super Contributor
timestamp: February 15, 2023, 08:19:35 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 05:41:58 pmTwo phrases spring to mind: "a leopard cannot change its spots", and "so it goes".

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 08:39:10 pm
content: Quote from: Nominal Animal on February 15, 2023, 06:04:58 pmSorry but losing your temper and stamping your feet and making things up when disagreed with won't get you anywhere with me. I questioned the following claim (emphasis mine)QuoteWhen I did question it (which one has every right to do, question claims made by others) this was the respone:Quotethen I asked:QuoteAnd then people get all bent out of shape, what is wrong with you? I was just asking a question, where are these prohibition rules? whatexactlydo they say?Since when does questioning someone equate to "do not trust a single word" the person says? the melodrama from some here is juvenile. You should be eternally grateful that you didn't take a career in law, you wouldn't last five minutes in a court room with such childish outbursts, your clients would end up suing you!This is America, we have the right toask questions, if you don't have an answer that's not my concern.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 15, 2023, 09:06:29 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 08:39:10 pmNope.Dave's site, Dave's forum, Dave's server, so this is Australia, mate.It does tell a lot about you toassumethis is America, though. Go stick your head somewhere moist.

author: Sherlock Holmes
author_rank: Frequent Contributor
timestamp: February 15, 2023, 09:47:14 pm
content: Quote from: Nominal Animal on February 15, 2023, 09:06:29 pmYou're all confused again - mate. You even think Finland is in Australia now too, anyway keep trying, I'm sure you'll say something intelligent eventually, might even get on topic at last too if we're lucky or even get the thread locked like last time, you're good at that at least.

author: tggzzz
author_rank: Super Contributor
timestamp: February 15, 2023, 10:01:45 pm
content: Quote from: Sherlock Holmes on February 15, 2023, 08:39:10 pm