Mobile Development 14 min read

How to Shrink Kotlin/Native Binary Size for Mobile Apps: Practical Optimizations

This article details the Alipay Tech team's systematic approach to reducing Kotlin/Native binary size for Android, iOS, and HarmonyOS by tuning compiler parameters, leveraging LLVM optimization passes, and applying fine‑grained export and dead‑code‑elimination strategies, complete with test methods and measurable results.

Alipay Experience Technology
Alipay Experience Technology
Alipay Experience Technology
How to Shrink Kotlin/Native Binary Size for Mobile Apps: Practical Optimizations

Author Li Haohua from Alipay Technology shares practical methods for optimizing Kotlin/Native binary size, focusing on compiler parameter tuning and fine‑grained export symbol control combined with dead‑code‑elimination (DCE).

Background

Alipay's client adopts a "three‑platform one code" strategy (Android, iOS, HarmonyOS) using Kotlin Multiplatform (KMP) and Compose Multiplatform (CMP). While Android uses Kotlin/JVM, iOS and HarmonyOS rely on Kotlin/Native (with possible future Kotlin/JS or Kotlin/Wasm). Introducing these frameworks increased the overall app package size, prompting a deep analysis of Kotlin/Native output for further compression.

Optimization Plan

After upgrading from Kotlin 2.0 to 2.1, binary size grew noticeably, likely due to a newer LLVM toolchain. The team filed an issue (https://youtrack.jetbrains.com/issue/KT-74981) and proposed adding LLVM optimization flags and passes such as -Os, globaldce, and lto to reduce size.

In the "shell project" the freeCompilerArgs are extended as follows:

@OptIn(org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi::class)
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xllvm-module-passes=default<Os>")
        freeCompilerArgs.add("-Xllvm-lto-passes=internalize,globaldce,lto<Os>")
        freeCompilerArgs.add("-Xoverride-konan-properties=clangOptFlags.ios_arm64=-Os")
    }
    // or
    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
        compilations.all {
            kotlinOptions.freeCompilerArgs += "-Xllvm-module-passes=default<Os>"
            kotlinOptions.freeCompilerArgs += "-Xllvm-lto-passes=internalize,globaldce,lto<Os>"
            kotlinOptions.freeCompilerArgs += "-Xoverride-konan-properties=clangOptFlags.ios_arm64=-Os"
        }
    }
    // other configurations
}

This approach can shrink the binary by roughly 10% (a few megabytes), but additional methods are explored.

Binary Analysis

Kotlin/Native compiles Kotlin to native machine code (arm64 for iOS and HarmonyOS). The resulting artifacts are:

HarmonyOS: a libxxx.so file that is stripped of DWARF info before being packaged.

iOS: a framework containing header files and .o objects with compiled Kotlin code.

Two inspection methods are used:

iOS LinkMap analysis.

IDA Pro reverse‑engineering of symbols on both iOS and HarmonyOS.

Key Insight

Setting the export library to "no export" in build.gradle.kts dramatically reduces binary size (up to 50% for iOS frameworks) but also removes many exported symbols. The team identified the essential symbols that must remain and refined the export configuration.

Official Kotlin 2.1 Feature

Kotlin 2.1 introduces objCExportEntryPointsPath, allowing precise control over which Kotlin symbols are exported to Objective‑C/Swift. This enables DCE to keep only the code reachable from the export whitelist, discarding the rest.

Export Point Determination

Kotlin code uses ObjCEntryPoints to decide exposure. By default ObjCEntryPoints.ALL exports everything; a custom file can list specific entry points.

DCE Process

The DCE phase builds a call graph, marks all functions reachable from root nodes (exported symbols, program entry points, etc.), and removes unreachable functions. The relevant source files are TopLevelPhases.kt and DCE.kt.

private fun PhaseEngine<NativeGenerationState>.runCodegen(module: IrModuleFragment) {
    val optimize = context.shouldOptimize()
    module.files.forEach { runPhase(ReturnsInsertionPhase, it) }
    val moduleDFG = runPhase(BuildDFGPhase, module, disable = !optimize)
    val devirtualizationAnalysisResults = runPhase(DevirtualizationAnalysisPhase, DevirtualizationAnalysisInput(module, moduleDFG), disable = !optimize)
    val dceResult = runPhase(DCEPhase, DCEInput(module, moduleDFG, devirtualizationAnalysisResults), disable = !optimize)
    // ... other phases ...
    runPhase(CodegenPhase, CodegenInput(module, lifetimes))
}

Testing Methods

iOS

Build the release framework with: ./gradlew linkReleaseFrameworkIosArm64 Never use Debug mode; Release mode enables -opt which triggers LLVM passes affecting size. Measure the final IPA size, not just the intermediate framework.

HarmonyOS

Build with DevEco IDE using: ./gradlew linkReleaseOhosArm64 Analyze both the un‑stripped libkmp.so inside the hap package and the stripped version after packaging.

Results

iOS framework size reduced from 28 MB to 15 MB (≈ 50% reduction).

Overall iOS IPA decreased by ~4.4 MB.

HarmonyOS KMP dynamic library shrank by ~8 MB after strip, a ~15% reduction.

Final Optimizations

Enhanced -Xbinary=objcExportEntryPointsPath to control class‑level exports.

Introduced @ObjCExport and @CExport annotations to replace @CName / @ObjCName for finer export control.

These annotations are processed in ObjCExportMapper.shouldBeExposed, allowing selective exposure of symbols and enabling DCE to eliminate unused Kotlin code.

Conclusion

By configuring an export whitelist and leveraging DCE, the team achieved substantial binary size reductions for both iOS and HarmonyOS platforms. The approach demonstrates how precise export control combined with LLVM optimization passes can make Kotlin/Native binaries much more lightweight for mobile deployments.

Mobile Optimizationbinary sizeKotlin MultiplatformKotlin/NativeCompiler FlagsDCE
Alipay Experience Technology
Written by

Alipay Experience Technology

Exploring ultimate user experience and best engineering practices

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.