Learn about Large System Extensions (LSE)
Introduction
Introduction to Large System Extensions
Large System Extensions (LSE) Example
Next Steps
Learn about Large System Extensions (LSE)
Try Large System Extensions (LSE) using an example C program
You can build and run an example to learn more and find out if the compiler is generating LSE instructions.
Below is an example program from cppreference.com .
Use a text editor of your choice to save the example program in a file called atomic.c on your Arm Linux computer.
#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>
atomic_int acnt;
int cnt;
int f(void* thr_data)
{
for(int n = 0; n < 1000; ++n) {
++cnt;
++acnt;
}
return 0;
}
int main(void)
{
thrd_t thr[10];
for(int n = 0; n < 10; ++n)
thrd_create(&thr[n], f, NULL);
for(int n = 0; n < 10; ++n)
thrd_join(thr[n], NULL);
printf("The atomic counter is %u\n", acnt);
printf("The non-atomic counter is %u\n", cnt);
}
The atomic_int C data type is used to indicate that accesses to the acnt variable must be atomic.
The results on different AWS instance types are shown below. You can also try this on any Arm Linux computer.
A1 Instance
The AWS A1 instance uses Cortex-A72, without LSE. This can also be done on any Cortex-A53 or Cortex-A72 system.
On Ubuntu 26.04 the default gcc version is 15.2.0. Check this by running:
gcc --version
The output is:
gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0
Copyright (C) 2025 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
GCC 10.1 and later enable -moutline-atomics by default and generate a helper function that checks at runtime whether LSE is available.
Now compile the application without outline atomics:
gcc -g atomic.c -fverbose-asm -mno-outline-atomics -o a1 -march=armv8-a -lpthread
objdump -S a1 > a1.dis
Review the disassembly file a1.dis and check the instructions for incrementing acnt. The sequence is:
- Address of acnt is loaded into x0
- Value of acnt is loaded into w2 using load exclusive
- Add 1 to acnt
- Store exclusive to write the new value
- Check if the store succeed and if not loop back to 0x988 and load again
Run the following command to see the instructions used to increment acnt
cat a1.dis | grep -i "++acnt" -A 12
++acnt;
970: 52800020 mov w0, #0x1 // #1
974: b9001fe0 str w0, [sp, #28]
978: b9401fe0 ldr w0, [sp, #28]
97c: 2a0003e1 mov w1, w0
980: 90000100 adrp x0, 20000 <__data_start>
984: 91005000 add x0, x0, #0x14
988: 885ffc02 ldaxr w2, [x0]
98c: 0b010042 add w2, w2, w1
990: 8803fc02 stlxr w3, w2, [x0]
994: 35ffffa3 cbnz w3, 988 <f+0x60>
998: 2a0203e0 mov w0, w2
99c: b90023e0 str w0, [sp, #32]
Arm AGI CPU
This example is run on an Arm AGI CPU with LSE. You can also use another Neoverse N1 or later system, such as an AWS T4g instance.
Compile the same application:
gcc -g atomic.c -fverbose-asm -mno-outline-atomics -o agi -march=armv8.2-a -lpthread
objdump -S agi > agi.dis
Review the file agi.dis and check the instructions for incrementing acnt. The sequence is:
- Address of acnt is loaded into x0
- Value of acnt is updated using a single instruction to add 1 to a word in memory ( ldaddal ).
Run the following command to see the instructions used to increment acnt on the AGI CPU.
cat agi.dis | grep -i "++acnt" -A 9
++acnt;
920: 52800020 mov w0, #0x1 // #1
924: b9001fe0 str w0, [sp, #28]
928: b9401fe0 ldr w0, [sp, #28]
92c: 2a0003e1 mov w1, w0
930: 90000100 adrp x0, 20000 <__data_start>
934: 91005000 add x0, x0, #0x14
938: b8e10002 ldaddal w1, w2, [x0]
93c: 0b010040 add w0, w2, w1
940: b90023e0 str w0, [sp, #32]
Staying on the same Arm Linux machine with LSE, compile the application with outline atomics by omitting the -mno-outline-atomics flag:
gcc -g atomic.c -o agi.outline -lpthread
objdump -S agi.outline > outline.dis
Review the file outline.dis and see that the instruction to increment acnt is now a branch to something called __aarch64_ldadd4_acq_rel at address 0xb80:
a04: 9400005f bl b80 <__aarch64_ldadd4_acq_rel>
The code for both the load exclusive sequence and the atomic instruction are present as shown in the disassembly snippet below. The section of instructions before the first ret instruction is run on the AGI CPU and the following instructions are run on the A1. This binary will run on both instances with no changes. In exchange for this flexibility there is the overhead to take a branch and run the correct code path.
0000000000000b80 <__aarch64_ldadd4_acq_rel>:
b80: d503245f bti c
b84: 90000110 adrp x16, 20000 <__data_start>
b88: 39407210 ldrb w16, [x16, #28]
b8c: 34000070 cbz w16, b98 <__aarch64_ldadd4_acq_rel+0x18>
b90: b8e00020 ldaddal w0, w0, [x1]
b94: d65f03c0 ret
b98: 2a0003f0 mov w16, w0
b9c: 885ffc20 ldaxr w0, [x1]
ba0: 0b100011 add w17, w0, w16
ba4: 880ffc31 stlxr w15, w17, [x1]
ba8: 35ffffaf cbnz w15, b9c <__aarch64_ldadd4_acq_rel+0x1c>
bac: d65f03c0 ret
As a final check, move back to the A1 instance and compile for armv8.2-a architecture. The atomic instruction is illegal on the Cortex-A72 and fails.
gcc -g atomic.c -o a1 -march=armv8.2-a -lpthread
./a1
The result is:
Illegal instruction (core dumped)
How can I find out if my application has atomic instructions?
To check for atomic instructions in applications run objdump on the agi executable:
objdump -d agi | grep -i 'cas\|casp\|swp\|ldadd\|stadd\|ldclr\|stclr\|ldeor\|steor\|ldset\|stset\|ldsmax\|stsmax\|ldsmin\|stsmin\|ldumax\|stumax\|ldumin\|stumin' | wc -l
The above command will report a count of 1 instruction, the ldaddal instruction.
To check whether applications contain load exclusives and store exclusives run this command on the A1 executable. It will report a count of 2.
objdump -d a1 | grep -i 'ldxr\|ldaxr\|stxr\|stlxr' | wc -l
Running on the agi.outline executable which supports both architectures will report both types of instructions.
Another way to confirm an executable supports both architectures is to run the command:
nm agi.outline | grep __aarch64_have_lse_atomics | wc -l
If it returns a 1 then it was compiled with outline-atomics.
Summary
Large System Extensions introduce atomic instructions to improve performance for Arm systems with many processors. When migrating applications to Neoverse it helps to have an understanding of compilers, compiler options, and libraries. Also, think about the strategy for an application supporting only Neoverse or also including support for processors which don’t include LSE.