Skip to content

Conversation

@walruscraft
Copy link
Contributor

@walruscraft walruscraft commented Jan 13, 2026

This adds startup code for powerpc64le (ELF v2 ABI) and mipsel (O32 ABI).

Two new arch modules: powerpc64.rs and mips32.rs. Each one has the usual set: entry point, trap(), dynamic_table_addr(), relocation helpers, thread support, etc.

The tricky part on PowerPC64 is r2 (TOC pointer) setup. On ELF v2, r12 contains the entry point address at program start, and we need to compute r2 from it before touching any global/static data:

addis 2, 12, .TOC. - _start@ha
addi 2, 2, .TOC. - _start@l

Without this, any access to static data gives EFAULT because r2 is garbage. Syscalls use the sc instruction with error indicated by the cr0.SO flag (not negative return like x86).

MIPS uses __start as entry point (MIPS convention). The O32 ABI requires a 16-byte argument save area on the stack, and indirect jumps should go through $t9 for PIC compatibility. Thread pointer is accessed via the userlocal register: rdhwr $3, $29.

I tested with a minimal no_std binary that reads a static variable and exits with code 42. Both architectures work under QEMU user-mode.

I also tested with a larger application (kv) that does file I/O and string formatting. MIPS works fully including argument parsing. PowerPC64 works the same way except for an argv issue - as far as I can tell from various workarounds and debugging, it's the same correct behavior, but there's a QEMU user-mode bug that corrupts the argv array for non-glibc PPC64LE binaries. The argument strings are placed correctly in memory, but the pointer array has NULLs interleaved. This only affects QEMU emulation, not the origin code itself.

Add startup code for powerpc64le (ELFv2 ABI) and mipsel (O32 ABI) targets,
enabling no_std programs to run on these architectures.

PowerPC64 (powerpc64.rs):
- _start sets up r2 (TOC pointer) using r12 before calling entry
- trap() uses the `trap` instruction
- Full thread support scaffolding (clone, thread pointer, TLS)
- Relocation support for PIC binaries

MIPS32 (mips32.rs):
- __start entry point (MIPS convention, not _start)
- 8-byte stack alignment with 16-byte O32 save area
- trap() uses the `break` instruction
- Thread support using rdhwr for thread pointer
- Syscall convention: $v0=syscall#, $a0-$a3=args, $a3=error flag

Both architectures:
- Implement origin-start feature for program startup
- Support optimize_for_size feature
- Follow existing origin patterns from other architectures

Tested with QEMU user-mode emulation (qemu-ppc64le-static, qemu-mipsel-static).
Note: QEMU ppc64le has a bug with argv setup for non-glibc binaries, but
the startup code itself is correct and works on real hardware.
Copy link
Owner

@sunfishcode sunfishcode left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, thanks for contributing these! I have a few comments below, and CI wants a rustfmt.

Also, we should probably mention in README.md that these new targets and also ARM aren't currently tested in CI. I can do that if you prefer though.

/// MIPS O32 ABI: $sp holds the stack pointer, $a0-$a3 are argument registers.
/// At entry, argc is at 0($sp), argv at 4($sp), etc.
// MIPS uses __start as the default entry point, not _start.
// We provide both names for compatibility.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says that both names are provided, but I only see code the __start defined here. Origin is all about not dragging along old baggage, so if plain __start is enough, then perhaps just the comment should be changed.

// yet initialized at this point.
trap();
}
let _ = r0;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If r0 isn't needed, can the asm be simplified to avoid having it as an out param?

);
// Note: child_tid handling is simplified here; full implementation would
// need to push it to the stack frame per O32 calling convention.
let _ = child_tid;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that CLONE_CHILD_CLEARTID and/or CLONE_CHILD_SETTID are not supported? If so, could we explicitly check for those and fail with ENOTSUP or so, so that they aren't silently ignored?

asm!(
"move $a0, {0}",
".set noreorder",
"li $v0, 4283", // __NR_set_thread_area
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it work to import __NR_set_thread_area from linux_raw_sys and use it as a const operand, rather than hard-coding the value here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants