Skip to content
This repository has been archived by the owner on May 9, 2022. It is now read-only.

TY-1793 replace swift dummy call #100

Merged
merged 2 commits into from
Jun 10, 2021
Merged

Conversation

janpetschexain
Copy link
Contributor

@janpetschexain janpetschexain commented Jun 9, 2021

References

Summary

  • remove ffi dummy function, which got compiled independently of the ai ffi code in certain build conditions
  • use a call to the actual ai in place of the dummy function for swift to avoid the former issue, tested with ios simulator

The reason for this change:

During #71 we noticed that the produced debug builds of the job build-ios-libs fail at runtime with the error:

[VERBOSE-2:ui_dart_state.cc(186)] Unhandled Exception: Invalid argument(s): Failed to lookup symbol (dlsym(RTLD_DEFAULT, error_message_drop): symbol not found)
#0      DynamicLibrary.lookup (dart:ffi-patch/ffi_dynamic_library_patch.dart:31:29)
#1      XaynAiFfi._error_message_drop_ptr (package:xayn_ai_ffi_dart/src/ffi/genesis.dart:336:57)
#2      XaynAiFfi._error_message_drop_ptr (package:xayn_ai_ffi_dart/src/ffi/genesis.dart)
#3      XaynAiFfi._error_message_drop (package:xayn_ai_ffi_dart/src/ffi/genesis.dart:338:7)
#4      XaynAiFfi._error_message_drop (package:xayn_ai_ffi_dart/src/ffi/genesis.dart)
#5      XaynAiFfi.error_message_drop (package:xayn_ai_ffi_dart/src/ffi/genesis.dart:330:12)
#6      XaynAiError.free (package:xayn_ai_ffi_dart/src/result/error.dart:174:11)
#7      new XaynAi (package:xayn_ai_ffi_dart/src/reranker/ai.dart:56:13)
#8      new XaynAi.fromInputData (package:xayn_ai_ffi_dart/src/reranker/ai.dart:65:9)
#9      _MyAppState.initAi.<anonymous closure> (package:xayn_ai_ffi_dart_e<…>

By inspecting the compiled xayn_ai_ffi_dart framework with nm, we saw that the binary only contained the symbol for the dummy_function. However, the build of the flutter example was successful so what's going on here?

Debug build

First of all, a static library is just a collection of object files. We can extract the object files via ar -x libxayn_ai_ffi_c_aarch64-apple-ios.a.

.
├── __.SYMDEF
├── absvdi2.o
├── absvsi2.o
├── addr2line-4e43a39374bcf563.addr2line.bd5a69x6-cgu.0.rcgu.o
├── addvdi3.o
├── addvsi3.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.0.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.1.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.10.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.11.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.12.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.13.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.14.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.15.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.2.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.3.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.4.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.5.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.6.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.7.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.8.rcgu.o
├── aho_corasick-eadbc38989c37442.aho_corasick.39jke1wu-cgu.9.rcgu.o
├── alloc-b7983a2179610da2.alloc.30zmyte2-cgu.0.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.0.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.1.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.10.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.11.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.12.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.13.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.14.rcgu.o
├── anyhow-f6885974aa2fd848.anyhow.9uf318jh-cgu.15.rcgu.o
...(reduced output)

The object files are produced by llvm and as you can see llvm splits the code generation of a crate into multiple codegen units (rcgu stands for rust codegen unit).

Let's see in which file our functions are definded:

nm -m libxayn_ai_ffi_c_aarch64-apple-ios.a

...(reduced output)

xayn_ai_ffi_c.xan8n3l3v4of3g8.rcgu.o:
---------------- (LTO,CODE) private external __ZN58_$LT$$LP$$RP$$u20$as$u20$xayn_ai_ffi_c..utils..IntoRaw$GT$8into_raw17h22a8db3d51fa17bfE
---------------- (LTO,CODE) external _dummy_function

xayn_ai_ffi_c.4wbsddsvu9yd3n22.rcgu.o:
...(reduced output)
                 (undefined) external _rust_eh_personality
---------------- (LTO,CODE) external _xaynai_analytics
---------------- (LTO,CODE) external _xaynai_drop
---------------- (LTO,CODE) external _xaynai_faults
---------------- (LTO,CODE) external _xaynai_new
---------------- (LTO,CODE) external _xaynai_rerank
---------------- (LTO,CODE) external _xaynai_serialize


xayn_ai_ffi_c.2vdtpqfeaq2gqsht.rcgu.o:
...(reduced output)
---------------- (LTO,CODE) external _faults_drop

...(reduced output)

What is noticeable is that xayn_ai_ffi_c.xan8n3l3v4of3g8.rcgu.o does not contain any undefined symbols. This means that the linker only has to link this file if a user of the library only uses/calls the dummy_function. And that's exactly what happens in our case. In bindings/dart/ios/Classes/SwiftXaynAiFfiDartPlugin.swift we only call dummy_function, therefore linker only links that one object file, but not the files that for example contain the symbols for _xaynai_new or error_message_drop.

If the object files would contain undefined symbols the linker would search until the linker found them or not. This blog post explains it quite good how the linker works.

We can also illustrate this behavior on a small example.

Example

We create a main that calls dummy_function and compile it via clang.

#include "ffi.h"
int main () {
  dummy_function();
}

clang -c main.c -arch arm64 -mios-version-min=9.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.5.sdk

See what object file the linker will load:

Next we want to see which object file the linker would load when building the final executable.

ld main.o libxayn_ai_ffi_c_aarch64-apple-ios.a -why_load -arch arm64

   Options for introspecting the linker
     -why_load   Log why each object file in a static library is loaded. That is, what symbol was needed.  Also called -whyload for compatibility.
_dummy_function forced load of libxayn_ai_ffi_c_aarch64-apple-ios.a(xayn_ai_ffi_c.xan8n3l3v4of3g8.rcgu.o)
ld: dynamic main executables must link with libSystem.dylib for architecture arm64

That's it, it would only load xayn_ai_ffi_c.xan8n3l3v4of3g8.rcgu.o.

Building the executable

Finally, let's build the executable.

clang main.o libxayn_ai_ffi_c_aarch64-apple-ios.a -arch arm64 -mios-version-min=9.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.5.sdk

nm a.out 
0000000100007fa0 t __ZN58_$LT$$LP$$RP$$u20$as$u20$xayn_ai_ffi_c..utils..IntoRaw$GT$8into_raw17h22a8db3d51fa17bfE
0000000100000000 T __mh_execute_header
0000000100007f90 T _dummy_function
0000000100007f74 T _main
                 U dyld_stub_binder

The total size of a.out: 34KB

The final executable contians the symbole for the dummy_function but none of the _xayn_ai_... symbols.

Release build

Now, we let's do the same with a release build (w/o lto=true and codegen-units=1).

nm -m libxayn_ai_ffi_c_aarch64-apple-ios.a

...(reduced output)

xayn_ai_ffi_c.xayn_ai_ffi_c.2c44ru24-cgu.6.rcgu.o:
...(reduced output)
---------------- (LTO,RODATA) private external _anon.d13854c30d43221c69961b5cf18cca93.8.llvm.689422277163091636
---------------- (LTO,RODATA) private external _anon.d13854c30d43221c69961b5cf18cca93.9.llvm.689422277163091636
---------------- (LTO,CODE) external _dummy_function
                 (undefined) external _rust_eh_personality
                 
                 
xayn_ai_ffi_c.xayn_ai_ffi_c.2c44ru24-cgu.5.rcgu.o:
...(reduced output)
                 (undefined) external _rust_eh_personality
---------------- (LTO,CODE) external _xaynai_analytics
---------------- (LTO,CODE) external _xaynai_drop
---------------- (LTO,CODE) external _xaynai_faults
---------------- (LTO,CODE) external _xaynai_new
---------------- (LTO,CODE) external _xaynai_rerank
---------------- (LTO,CODE) external _xaynai_serialize

...(reduced output)

See what object file the linker will load:

ld main.o libxayn_ai_ffi_c.a -why_load -arch arm64

_dummy_function forced load of libxayn_ai_ffi_c.a(xayn_ai_ffi_c.xayn_ai_ffi_c.2c44ru24-cgu.6.rcgu.o)
...(reduced output)
__ZN7bit_vec15BitVec$LT$B$GT$4grow17h1386079845775371E forced load of libxayn_ai_ffi_c.a(xayn_ai_ffi_c.xayn_ai_ffi_c.2c44ru24-cgu.5.rcgu.o)
...(reduced output)

Since xayn_ai_ffi_c.xayn_ai_ffi_c.2c44ru24-cgu.6.rcgu.o contains undefined symbols the linker loads more object files (until it finds them or not), including xayn_ai_ffi_c.xayn_ai_ffi_c.2c44ru24-cgu.5.rcgu.o which contains our _xaynai_* functions.

Building the executable

clang main.o libxayn_ai_ffi_c.a -arch arm64 -mios-version-min=9.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS14.5.sdk -framework Security -framework Foundation

nm a.out 
...(reduced output)
000000010093b6f0 s _str.6
                 U _strerror_r
                 U _strlen
                 U _symlink
                 U _sysconf
                 U _tan
                 U _tanf
                 U _tanh
                 U _tanhf
                 U _unlink
                 U _unsetenv
                 U _utimes
                 U _waitpid
                 U _write
                 U _writev
000000010001b21c T _xaynai_analytics
000000010001b240 T _xaynai_drop
000000010001b1f8 T _xaynai_faults
000000010001b160 T _xaynai_new
000000010001b19c T _xaynai_rerank
000000010001b1d4 T _xaynai_serialize
                 U dyld_stub_binder
00000001006eb440 t non_linear_addc_i32
00000001006eaf50 t store_strides_i32

The total size of a.out: 17,5 MB

By replacing dummy_function with xaynai_new the chances are higher that all of our symbols are included in the final executable. To be on the safe side, we should "call" all of our functions in the swift plugin. However, we have to maintain this part manually and I don't know if it's worth it because it is only affects debug build. When compiling the release build we don't have this issue since we compile it with the flags -Ccodegen-units=1 -Clto=on -Cembed-bitcode=yes which will produce a single file for xayn-ai that contains all of our exported symbols.

Copy link
Contributor

@Robert-Steiner Robert-Steiner left a comment

Choose a reason for hiding this comment

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

LGTM 👍
I have added the explanation to the pr description

@janpetschexain janpetschexain merged commit 89f3204 into master Jun 10, 2021
@janpetschexain janpetschexain deleted the TY-1793-swift_dummy_call branch June 10, 2021 10:25
acrrd pushed a commit that referenced this pull request Jun 28, 2021
* remove dummy function

* use ai in swift dummy call
acrrd pushed a commit that referenced this pull request Aug 24, 2021
* remove dummy function

* use ai in swift dummy call
acrrd pushed a commit that referenced this pull request Nov 4, 2021
* remove dummy function

* use ai in swift dummy call
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants