The Hidden Binary Treaty That Makes Languages Understand Each Other
When you call a function in another language –
Dart → C,
Java → C++,
Swift → Objective-C,
Rust → Go,
Python → C modules
A mysterious, invisible contract determines whether the call succeeds or the entire process explodes.
That contract is the ABI: Application Binary Interface.
An ABI is the machine-level diplomatic treaty that allows different runtimes, languages, compilers, and OSes to talk to each other safely. Without it, there is no FFI, no plugins, no Python C extensions, no Node.js N-API, no JVM native methods, no Swift bridging, no Go interop, no Rust FFI, no Flutter engine.
This blog explains the ABI in general, for all languages and all runtimes, with specific examples from Android/iOS (ARM64) to make the abstract concepts concrete.
1. What Exactly Is an ABI? (General Definition)
An ABI is not a language feature, not C, not Java, not Swift.
An ABI is a machine-level specification for:
✔ Call convention
How arguments are passed: which registers, which stack slots, in what order.
✔ Return convention
Which register returns values, how structs are returned.
✔ Stack frame layout
How a function pushes/pops stack frames, aligns them, and stores local variables.
✔ Register saving rules
Which registers the caller must save.This prevents functions from clobbering each other’s data.
✔ Struct / Class / Enum layout
Memory alignment, padding, packing rules.
✔ Dynamic linking rules
Symbol naming
Library loading rules
Relocation mechanism
✔ Exception propagation rules
C++ exceptions, Swift exceptions, JVM exceptions – all must obey ABI-defined stack unwinding.
2. Why All Runtimes Must Obey ABI
Because CPU doesn’t speak:
- Dart
- Swift
- Java
- Python
- Go
- Kotlin
- PHP
- Node.js
- Rust
- C++
The CPU speaks only opcodes, registers, stack, pointers, and memory addresses.
Thus every runtime must obey the OS + CPU ABI, or the machine cannot run the code correctly.
3. Different OS + CPU = Different ABI
OS | CPU | ABI Example |
Android | ARM64 | AAPCS64 |
iOS | ARM64 | AAPCS64 + Apple extensions |
Linux | x86-64 | System V ABI |
macOS | x86-64 | System V + Apple differences |
Windows | x86-64 | Microsoft x64 ABI |
Linux | ARM32 | ARM EABI |
Embedded | ARM Thumb | AAPCS |
So yes – ABIs differ across OSes and CPUs.
Then how do different languages call each other?
They don’t choose their ABI individually.
The OS defines the ABI. Every runtime and compiler on that OS follows it.
Thus:
- Dart FFI follows the OS ABI
- JVM native calls follow the OS ABI
- Python C extensions follow the OS ABI
- Swift runtime follows the OS ABI
- Node.js native modules follow the OS ABI
This is why a C function compiled on Linux x86-64 cannot be loaded into an iOS ARM64 program: the ABIs are completely incompatible.
4. The Universal Pattern of Calling Conventions
Regardless of OS, ABI always defines:
✔ Which registers hold the first N arguments
Examples:
ARM64 (iOS/Android)
integer/pointer → x0–x7
float → v0–v7
x86-64 (Linux/macOS)
RDI, RSI, RDX, RCX, R8, R9
x86-64 (Windows)
RCX, RDX, R8, R9
✔ How additional arguments go on stack
Aligned to 16 bytes on ARM64
Aligned to 8 bytes on x86-64
✔ Return values
ARM64 → x0 (int/pointer), v0 (float)
x86-64 → RAX, XMM0
✔ How stack frames look
Every OS + CPU has a strict stack layout.
✔ Register saving discipline
caller-saved
callee-saved
This prevents chaos between function boundaries.
Without this, a single function call could corrupt memory anywhere.
5. Example: Same C Function on Different ABIs
int add(int a, int b) {
return a + b;
}
iOS/Android (ARM64) ABI
int add(int a, int b) {
return a + b;
}
Linux/macOS (x86-64 System V)
int add(int a, int b) {
return a + b;
}
Windows x86-64 ABI
int add(int a, int b) {
return a + b;
}
Same code, three different ABIs.
6. Cross-Runtime Example: JVM → C (JNI)
Java:
native int add(int a, int b);
At runtime:
1. JVM collects arguments into registers according to the OS ABI
2. Calls the C function using ARM64 ABI or System V ABI
3. Reads the return value from the proper register
JVM does NOT define its own ABI. It strictly follows the OS ABI.
7. Swift/Objective-C Interop Uses ABI
Swift and Objective-C both follow the Apple ARM64 ABI.
That’s why:
@_cdecl("add")
func add(_ a: Int32, _ b: Int32) -> Int32 {
return a + b
}
Can be called directly from C, C++, Dart (via FFI), Python, Rust, etc.
They all rely on ABI.
8. Dart FFI → Same ABI
When Dart calls C:
1. Dart VM loads arg1 into x0
2. Dart VM loads arg2 into x1
3. Performs BL (branch with link)
4. Reads return value from x0
Exactly like C, Java, Swift, Rust, Python…
Because all runtimes follow the same ABI.
9. How Do Two Runtimes Communicate? (Generalized Explanation)
Step 1: Language → Runtime
Dart/JVM/Swift/Python transforms your function call into an internal runtime call.
Step 2: Runtime → Native Stub
Runtime generates a small piece of machine code that translates high-level data types into ABI-compatible raw values:
1. ints → registers
2. doubles → FP registers
3. structs → memory blocks
4. strings → pointers
Step 3: Native Stub → ABI Call
The stub follows the OS ABI:
1. pushes stack frames
2. aligns stack
3. fills argument registers
4. jumps to native function
Step 4: Callee Executes
Native code runs normally in its own stack frame.
Step 5: Return Value
Returned in ABI-defined register (x0, v0, RAX, etc.)
Step 6: Runtime Receives Raw Data
Converts raw binary representation back to high-level types.
10. Why ABIs Make Cross-Language Interop Possible
Because all languages ultimately:
- Use the same register conventions
- Use the same stack alignment rules
- Use the same return conventions
- Use the same struct memory layouts
- Follow the same dynamic linking format
- Follow the same name mangling rules
This is why:
- Python can load C modules
- Java can call C via JNI
- Node.js can load native addons
- Flutter can load .so and .framework files
- Swift and Objective-C interoperate
- Rust can call C or be called from Swift
- Go can call or export C functions
- Unreal Engine (C++) integrates Lua, Python, Blueprints
- Unity (C#) calls native plugins
11. The ABI Is NOT C - It’s Lower Than C
C is just the most common language that maps cleanly to ABI rules :
- no hidden metadata
- simple structs
- no exceptions
- no dynamic dispatch
- simple symbol names
This is why the world uses C ABI rather than Java ABI or Swift ABI.
12. Final Summary
### ABI = the universal binary contract of all runtimes.
It specifies:
- how arguments are passed
- how return values work
- how stack frames look
- how registers are used
- how memory is aligned
- how functions are named
- how exceptions unwind
All languages must obey the ABI of the OS they’re running on.
This guarantees interoperability between:
- Dart ↔ C
- Java ↔ C++
- Swift ↔ Objective-C
- Python ↔ C extensions
- Rust ↔ Swift
- Node.js ↔ C++ native modules
- Go ↔ everything
- Flutter ↔ Engine
Different OS+CPU = different ABI
Linux x86-64 ABI ≠ iOS ARM64 ABI ≠ Windows ABI.
But each platform enforces one ABI, so all languages can cooperate.





