Vandad Nahavandipoor
http://www.oreilly.com/pub/au/4596
Email: [email protected]
Blog: http://vandadnp.wordpress.com
Skype: vandad.np
In this issue, I want to see how we can write a very simple assembly code (equivalent of the following Swift code), and call that assembly code from our Swift code:
func add(a: Int, _ b: Int) -> Int{
return a + b
}
the xcode version that i am using is Version 6.2 (6C131e)
. let's get started.
when we write our app in objc or swift, the compiler automatically generates the binary for the requested architectures, namely x86_64 and armvx, where x can be any of the crazy arm cpu architectures, like v7 or v7s. when we write assembly code, we have to do that manually. in other words, we have to write two separate pieces of code, for the aforementioned cpu architectures, assembly them, get the object files and mix the object files into one object file and then link against that object file. magic! no it's not, just joking.
so let's write the simple intel x86_64 asm code for our add method:
.globl _addMethod
_addMethod:
pushq %rbp
movq %rsp, %rbp
movq %rdi, -0x8(%rbp)
movq %rsi, -0x10(%rbp)
movq -0x8(%rbp), %rsi
addq -0x10(%rbp), %rsi
movq %rsi, %rax
popq %rbp
retq
great, let's compile this file now. i've named it "intel.s" so:
xcrun clang -arch x86_64 intel.s -c -o intel.o
and now i have a file named intel.o
on disk. let's disassemble it and see what we get:
xcrun otool -v -t intel.o
and we get this:
intel.o:
(__TEXT,__text) section
_addMethod:
0000000000000000 pushq %rbp
0000000000000001 movq %rsp, %rbp
0000000000000004 movq %rdi, -0x8(%rbp)
0000000000000008 movq %rsi, -0x10(%rbp)
000000000000000c movq -0x8(%rbp), %rsi
0000000000000010 addq -0x10(%rbp), %rsi
0000000000000014 movq %rsi, %rax
0000000000000017 popq %rbp
0000000000000018 retq
From the previous section, we should now have a file called intel.o
which is our object file. we can indeed now include this in our project and start calling it. so far, with my reasearch, i have not found a way to inject object files directly into swift and use them as external symbols like we have the extern
keyword in objc. so what we are going to do is create an objc class in our swift project and then import our object file into that objective-c class, then call the method from swift. Follow these steps:
-
create a new objc class in the project, called "ObjcClass"
-
when prompted to create a bridging header, do so
-
in your bridging header, import your class:
#import "ObjcClass.h"
-
add the method signature to the
ObjcClass.h
file:#import <Foundation/Foundation.h> @interface ObjcClass : NSObject - (NSInteger) add:(NSInteger)a b:(NSInteger)b; @end
-
in the
ObjcClass.m
file, add the external symbol for theaddMethod
:#import "ObjcClass.h" extern NSInteger addMethod(NSInteger a, NSInteger b); @implementation ObjcClass - (NSInteger) add:(NSInteger)a b:(NSInteger)b{ return addMethod(a, b); } @end
-
in your swift code, instantiate
ObjcClass
and call theadd
method on it:let objc = ObjcClass() println("Result of add is \(objc.add(200, b: 300))")
-
drag and drop the
intel.o
file that we just created, into your project, and ensure that it is added to your target. -
compile and run your project (note: at this stage, you can only compile and run your project in the simulator, as we have just linked the intel binary to our project. the arm binary is missing)
when you run the app, you should then get the following printed to your console:
Result of add is 500
note: if you want to get the project with the asm file and object file the way i have set it up, check out commit 695043 from the swift-weekly repo and you should have everything that you need to test this for yourself.
just like we wrote the x86_64 version of our asm file, we have to do the same for the arm architecture. i've named my file arm.s
and its content is:
.align 4
.global _addMethod
_addMethod:
sub sp, sp, #16
stp x1, x0, [sp]
ldp x1, x0, [sp]
add x0, x0, x1
add sp, sp, #16
ret
and then compile this into an object file:
xcrun clang -O3 -arch arm64 arm.s -c -o arm.o
let's disassemble it with xcrun otool -v -t arm.o
and see what we get:
arm.o:
(__TEXT,__text) section
_addMethod:
0000000000000000 sub sp, sp, #16
0000000000000004 stp x1, x0, [sp]
0000000000000008 ldp x1, x0, [sp]
000000000000000c add x0, x0, x1
0000000000000010 add sp, sp, #16
0000000000000014 ret
This is to make sure that we can run our code on a real device, like the iPhone 6 Plus. Follow these steps:
- ensure that the
intel.o
file is no longer attached to your target. - drag and drop the
arm.o
file that we created in the previous section, into your project and make sure it is attached to your target. - run your code on a real device.
we have a problem now. we can either run the project on the simulator, or on a device. if both intel.o
and arm.o
are attached to your target, and say that you want to compile your project for running on a device, you would get the following warning from Xcode:
ld: warning: ignoring file intel.o, file was built for unsupported file format ( 0xCF 0xFA 0xED 0xFE 0x07 0x00 0x00 0x01 0x03 0x00 0x00 0x00 0x01 0x00 0x00 0x00 ) which is not the architecture being linked (arm64): intel.o
and if you try to run the project on the simulator, you would get this warning:
(null): Ignoring file arm.o, file was built for unsupported file format ( 0xCF 0xFA 0xED 0xFE 0x0C 0x00 0x00 0x01 0x00 0x00 0x00 0x00 0x01 0x00 0x00 0x00 ) which is not the architecture being linked (x86_64): arm.o
so we need a way to combine arm.o
and intel.o
into one binary and then link against that binary. we use that using the lipo
command:
xcrun lipo -create intel.o arm.o -output both.o
this mixes both object files into a third file called both.o
. let's get its information:
xcrun lipo -info both.o
Architectures in the fat file: both.o are: x86_64 arm64
now make sure neither intel.o
nor arm.o
is attached to your target. then drag and drop both.o
into your target and compile and run. it works!