Porting Linux applications from x86, ARM, or other architectures to LoongArch is generally straightforward for C/C++ code, but requires attention to architecture-specific code, assembly language, and optimization strategies. This guide covers the complete porting process.

Overview of LoongArch Architecture

LoongArch is a RISC-style instruction set architecture with the following characteristics:

  • 32/64-bit architecture: Supports both 32-bit (LA32) and 64-bit (LA64) modes
  • Load-store design: Only load and store instructions access memory
  • Fixed instruction length: Most instructions are 32-bit (some are 16-bit in compact mode)
  • 32 general-purpose registers: 64-bit wide in LA64 mode
  • Little-endian byte order: Same as x86 and ARM
  • Memory model: Weakly ordered with memory barrier instructions

These characteristics make LoongArch similar to other modern RISC architectures, facilitating porting from ARM and MIPS.

Step 1: Build System Configuration

Update your build system to recognize LoongArch:

Autotools Projects

``bash

Update config.guess and config.sub to latest versions

that recognize loongarch64-unknown-linux-gnu

wget -O config.guess 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD'

wget -O config.sub 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD'

Configure for cross-compilation

./configure --host=loongarch64-unknown-linux-gnu \

CC=loongarch64-linux-gnu-gcc \

CXX=loongarch64-linux-gnu-g++

`

CMake Projects

`cmake

Create toolchain file toolchain-loongarch.cmake

set(CMAKE_SYSTEM_NAME Linux)

set(CMAKE_SYSTEM_PROCESSOR loongarch64)

set(CMAKE_C_COMPILER loongarch64-linux-gnu-gcc)

set(CMAKE_CXX_COMPILER loongarch64-linux-gnu-g++)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)

set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)

set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

`

Then configure:

`bash

cmake -DCMAKE_TOOLCHAIN_FILE=toolchain-loongarch.cmake..

`

Meson Projects

`bash

Create cross file loongarch.txt

cat > loongarch.txt << 'EOF'

[binaries]

c = 'loongarch64-linux-gnu-gcc'

cpp = 'loongarch64-linux-gnu-g++'

ar = 'loongarch64-linux-gnu-ar'

strip = 'loongarch64-linux-gnu-strip'

[host_machine]

system = 'linux'

cpu_family = 'loongarch64'

cpu = 'loongarch64'

endian = 'little'

EOF

meson setup build --cross-file=loongarch.txt

`

Step 2: Handle Architecture Detection

Update architecture detection code:

`c

// Old code might check for specific architectures

#if defined(__x86_64__) || defined(__i386__)

// x86 specific code

#elif defined(__arm__) || defined(__aarch64__)

// ARM specific code

#endif

// Add LoongArch detection

#if defined(__loongarch__)

#if defined(__loongarch64)

// LoongArch 64-bit code

#else

// LoongArch 32-bit code

#endif

#endif

`

Common predefined macros:

  • __loongarch__ - Defined on all LoongArch targets
  • __loongarch64 - Defined on 64-bit LoongArch
  • __loongarch32 - Defined on 32-bit LoongArch
  • __loongarch_double_float - Defined when double-precision FPU present

Step 3: Port Assembly Code

Assembly code requires the most effort to port. Options include:

Option A: Rewrite in C

For small assembly routines, consider rewriting in C. Modern compilers generate efficient code, and C is more portable and maintainable.

Option B: Use Inline Assembly

For performance-critical code, use GCC inline assembly:

`c

// Example: Read CPU cycle counter

static inline uint64_t read_cycles(void) {

uint64_t val;

__asm__ __volatile__ ("rdtime.d %0, $zero" : "=r"(val));

return val;

}

`

Option C: Port Assembly Files

For larger assembly modules, port the. S files to LoongArch syntax:

x86 to LoongArch mapping (common instructions):

x86LoongArchDescription
movmoveRegister move
addadd.dAdd (64-bit)
subsub.dSubtract (64-bit)
mulmul.dMultiply (64-bit)
andandBitwise AND
ororBitwise OR
callblBranch and link
retjirlJump indirect and link
push/popst.d/ld.dStack operations

Example porting:

`asm

x86 version

push %rbp

mov %rsp, %rbp

mov %rdi, -8(%rbp)

add $1, -8(%rbp)

pop %rbp

ret

LoongArch version

addi.d $sp, $sp, -16

st.d $fp, $sp, 0

addi.d $fp, $sp, 16

st.d $a0, $fp, -8

ld.d $t0, $fp, -8

addi.d $t0, $t0, 1

st.d $t0, $fp, -8

ld.d $fp, $sp, 0

addi.d $sp, $sp, 16

jirl $zero, $ra, 0

`

Step 4: Handle Endianness

LoongArch uses little-endian byte order, same as x86 and ARM (little-endian mode). If your code already supports x86 or ARM little-endian, no changes are needed.

For code that supports multiple endianness:

`c

#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__

// Little-endian code (includes LoongArch)

#else

// Big-endian code

#endif

`

Step 5: Memory Barriers

LoongArch uses a weakly-ordered memory model. Use appropriate memory barriers:

`c

// Compiler barrier

__asm__ __volatile__ ("" ::: "memory");

// Full memory barrier

__asm__ __volatile__ ("dbar 0" ::: "memory");

`

For kernel code, use standard Linux memory barrier macros which are architecture-independent.

Step 6: SIMD/Vector Code

LoongArch has LSX (Loongson SIMD eXtension) and LASX (Loongson Advanced SIMD eXtension) for vector operations:

`c

// Check for LSX support

#include <sys/auxv.h>

unsigned long hwcap = getauxval(AT_HWCAP);

if (hwcap & HWCAP_LOONGARCH_LSX) {

// LSX is available

}

`

For portable code, consider using compiler intrinsics or libraries like OpenCV that abstract SIMD operations.

Step 7: Testing and Validation

After porting, thoroughly test your application:

  • Functional Testing: Verify all features work correctly
  • Performance Testing: Benchmark against original platform
  • Stress Testing: Run extended tests for stability
  • Compatibility Testing: Test with different kernel versions
  • Use debugging tools:

    `bash

    Run with strace to check system calls

    strace -f./myapp

    Check for library dependencies

    ldd./myapp

    Profile performance

    perf record./myapp

    perf report

    `

    Common Porting Issues

    Issue 1: Unaligned Memory Access

    LoongArch handles unaligned access differently than x86. Fix unaligned accesses:

    `c

    // Instead of direct pointer cast

    uint32_t val = (uint32_t)ptr; // May fault on unaligned

    // Use memcpy for safe unaligned access

    uint32_t val;

    memcpy(&val, ptr, sizeof(val));

    `

    Issue 2: Signal Handling

    Signal context structure differs. Use ucontext_t for portable signal handling:

    `c

    #include <ucontext.h>

    void handler(int sig, siginfo_t info, void context) {

    ucontext_t uc = (ucontext_t)context;

    // Access registers through uc->uc_mcontext

    }

    `

    Issue 3: JIT Compilation

    For JIT compilers, add LoongArch code generation backend. Reference existing backends (x86, ARM) for implementation guidance.

    Performance Optimization

    After successful porting, optimize for LoongArch:

  • Compiler Flags: Use -march=loongarch64 -O3 -mtune=loongarch64
  • Profile-Guided Optimization: Use -fprofile-generate then -fprofile-use
  • Loop Optimizations: Enable auto-vectorization with -ftree-vectorize
  • Link-Time Optimization: Use -flto` for whole-program optimization
  • Getting Help

    If you encounter issues during porting:

    • Check Loongson documentation and wiki
    • Search existing ported software for reference implementations
    • Contact our FAE team for complex porting challenges
    • Contribute fixes back to upstream projects

    Most applications port with minimal changes. The key is systematic testing and addressing architecture-specific code sections.