golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
123.46k stars 17.59k forks source link

x/mobile: add bitcode support #22395

Closed ashutosh-android closed 5 years ago

ashutosh-android commented 6 years ago

I am able to successfully generate and use .framework file using gomobile bind command, however generated lib doesn't have bitcode enabled Is there a way to enable bitcode in generated binary or are there are any plan to provide support in near future ?

Please answer these questions before submitting your issue. Thanks!

What version of Go are you using (go version)?

go version go1.8.3 darwin/amd64 gomobile version +44a54e9

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

MacOS

What did you do?

Generated .framework lib from go source code using 'gomobile bind'

What did you expect to see?

bitcode should be enabled in generated framework

What did you see instead?

generated framework doesn't have bitcode enabled

ashutosh-android commented 6 years ago

Can you please update on this. This is a blocker item for one of my project. It will be great if I get some visibility on release plan for this requirement. Thanks.

timcooijmans commented 6 years ago

I'm having the same problem: Currently the Go compiler doesn't support compiling to BitCode intermediate representation. I think something has to be done for Gomobile to have a future. It's expected that Apple will mandate BitCode for iOS-apps in the near future.

However there seems to be some work going on on Google-side: https://go.googlesource.com/gollvm/

You could also try https://dragonegg.llvm.org/ but I have no idea if this is going to work.

ashutosh-android commented 6 years ago

Any update on this ?

eliasnaur commented 6 years ago

I don't think there is any update other than gollvm is progressing. May I ask why this issue is blocking your project?

ashutosh-android commented 6 years ago

@eliasnaur thanks for looking into this query. I am working on a mobile project where we will be distributing our service as library project. We are writing alot of business logic in go code so that we can reuse them for both android and iOS using go-mobile. Since framework generated by go-mobile doesn't have bitcode enabled so any developer who will be integrating our solution will not be able to enable bitcode for there application. Considering how apple is strongly recommending developers to enable bitcode it will impact adoption our product in a big way.

I hope this answers your question. Let me know if you have any suggestions/query. Thanks again.

matti777 commented 6 years ago

Just ran into this. Yes, bitcode support would be a must. Any updates?

jpop32 commented 6 years ago

Yes, lack of bitcode support on iOS is a showstopper for library projects, and a big liability for others. We can safely assume that 'strong recommendation' to use bitcode is going to turn into 'requirement' sooner or later.

adam-p commented 6 years ago

Since it hasn't been mentioned here yet: Apple TV apps must support Bitcode. Not optional. Search for "bitcode" here: https://developer.apple.com/tvos/submit/

This has caused us problems when trying to support a client wanting to use our library on Apple TV.

zboralski commented 6 years ago

Just ran into this.

drewpitchford commented 6 years ago

I'll throw my hat in this ring too. We have iOS/Android sdks that use a go-mobile framework for some common encryption/decryption. Lack of bitcode is a major issue for us. I am attempting to replace the go-mobile framework in the iOS project with a js framework that does the same stuff, but it'd be nice to be able to keep the go framework

pavlo commented 6 years ago

It is a showstopper for us as well

biscottigelato commented 6 years ago

WWDC is coming soon, hopefully Bitcode will not become a requirement... The lack of any response from anyone in the know about how one an get Bitcode going for golang/gomobile is definitely concerning.

ianlancetaylor commented 6 years ago

As far as I know the only way to get the required Bitcode is to run LLVM. There is an active project to add Go support to LLVM; you can follow it at https://go.googlesource.com/gollvm/ . Until that project is complete and working, there will be no way to get Bitcode.

That is essentially what @eliasnaur said on March 16. While I understand that the pace of these things is frustrating, I don't think it's entirely fair to say that there has been no response. Go is an open source project; if you want this to happen faster, try building GoLLVM, report bugs, and send in fixes.

ashutosh-android commented 6 years ago

Hi,

Checking to see if there is any new development on this ?

Thanks, Ashutosh

ncastagnoli commented 6 years ago

Hi,

To add to the need, if that's important.

I'm working on a project that started cloud based, and elected to write the code in "go." It turns out we also need to run the code on the client, both iOS and Android. But, our partners are demanding bit-code compliance. We are at a decision point to decide whether to rewrite the entire set of code so we can have a single code base. Curious about the thinking regarding bit code compliance for "go."

Is it a project? If so, what is the time-frame? Go is perfect for our application, incidentally.

ashutosh-android commented 5 years ago

Hi,

Checking again on this. Is there any roadmap for this support ?

Thanks, Ashutosh

eliasnaur commented 5 years ago

Please refrain from asking about updates on this issue; fixing it is a lot of work and only benefits niche platforms for Go: tvOS and watchOS. @ianlancetaylor suggested you try gollvm; have you done that? If so, what problems did you run into?

ianlancetaylor commented 5 years ago

https://golang.org/wiki/NoPlusOne

matti777 commented 5 years ago

Ahem, what do you mean tvOS and watchOS? What about iOS? :) Not sure if that is a 'niche' environment for gomobile bind..

eliasnaur commented 5 years ago

Only watchOS and tvOS require bitcode; iOS does not.

drewpitchford commented 5 years ago

But we don’t know that will be the case forever. In fact, I’d say it’s highly likely to not be the case.

Drew

Sent from my iPhone XS Max

On Dec 18, 2018, at 10:48 AM, Elias Naur notifications@github.com wrote:

Only watchOS and tvOS require bitcode; iOS does not.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

eliasnaur commented 5 years ago

Whatever the likelyhood, let's worry about it when it happens and not waste precious development efforts on hypotheticals. Who knows, perhaps the very existence of non-bitcode gomobile apps will delay Apple's decision.

matti777 commented 5 years ago

iOS "requires" bitcode in the way that if you are making a library with golang / gomobile bind, the library you produce will force the iOS app developer to disable bitcode in their app; and we iOS devs really do not like that. :) Also of course there is a point to having bitcode support in the first place and that is now lost. third, who knows when iSteve will decide bitcode support will become mandatory ..

ianlancetaylor commented 5 years ago

Everyone agrees that it would be good if we could compile Go into bitcode. As mentioned above, the only way that is ever going to happen is if you use the GoLLVM compiler. So if you want Go compiled into bitcode, please help with GoLLVM.

tmm1 commented 5 years ago

The Kotlin/Native team has found a work-around to full bitcode support: they add a __LLVM,__asm marker (via https://github.com/JetBrains/kotlin-native/pull/2186) which tells Xcode the object was created with a bitcode-aware compiler, and that the object was generated from assembly and thus has no bitcode equivalent. (More details about the bitcode format on https://jonasdevlieghere.com/embedded-bitcode/).

See discussion on the kotlin-native repo: https://github.com/JetBrains/kotlin-native/issues/1202#issuecomment-433302550

The technique was discovered on an earlier PR, which attempted to emit bitcode but was unable to do so reliably without using Apple's version of LLVM: https://github.com/JetBrains/kotlin-native/pull/1564#issuecomment-390139261

Would it be possible to add a similar empty __LLVM,__asm section to the generated gomobile .framework?

randall77 commented 5 years ago

It would not be hard to add an empty section to the binary.

I worry that this is essentially a way to hack around Apple's policy. As such, they could revoke this exception at any time and then we're back to square one.

tmm1 commented 5 years ago

I worry that this is essentially a way to hack around Apple's policy. As such, they could revoke this exception at any time and then we're back to square one.

Similarly Apple could start enforcing bitcode requirement on iOS apps at any time, which would make go unusable on iOS the same way it is currently unusable on tvOS/watchOS.

Since iOS/tvOS/watchOS all support optimized assembly in apps, it seems highly unlikely that support for __LLVM, __asm is going away anytime soon. Certainly it is much more likely that Apple will begin enforcing bitcode support in the future.

From https://github.com/JetBrains/kotlin-native/pull/1564#issuecomment-390180142:

the implications would be the same as when Xcode project has assembly files. Assembly files are permitted on iOS even when bitcode is enabled. So this solution doesn't seem to cause any observable implications. Xcode is able to recompile an archive with such an application as any other bitcode-enabled application archive.

eliasnaur commented 5 years ago

Are you saying that tvOS and watchOS can run apps without bitcode support just by marking the object files with LLVM, asm? If so I believe that is worth adding support for in Go, even if it is a hack that might be disabled in the future. If for no other reason than other people are also using the hack.

If not, I don't see the much gain; you can simply disable bitcode for your Xcode project and it will work fine, even when uploaded to App Store. It might even be misleading to think Go supports bitcode when all it does is silence a linker error.

tmm1 commented 5 years ago

Yes my understanding is that this technique would allow the usage of golang on tvOS/watchOS. It tells the App Store app thinner that the golang parts are assembled and must stay in-tact, whereas other parts of the app which do contain bitcode can be recompiled as necessary.

The only way to make sure is to try this out, so I guess I will give it a go. I did manage to add the section to generated objects using this patch:

diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index 8315de5152..3afdc6f5ad 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -565,6 +565,10 @@ func Asmbmacho(ctxt *Link) {
        machoshbits(ctxt, ms, sect, "__DATA")
    }

+   if ctxt.LinkMode == LinkExternal {
+       newMachoSect(ms, "__asm", "__LLVM")
+   }
+
    /* dwarf */
    if !*FlagW {
        if ctxt.LinkMode != LinkExternal {

and it shows up as a new section, however I don't think I have the address/offset set correctly:

$ size -x -l -m test.a
test.a(go.o):
Segment : 0x0 (vmaddr 0x0 fileoff 4096)
    Section (__TEXT, __text): 0x9380c (addr 0x0 offset 4096)
    Section (__DATA, __rodata): 0x477e9 (addr 0x93820 offset 608288)
    Section (__DATA, __typelink): 0xbec (addr 0xdb020 offset 901152)
    Section (__DATA, __itablink): 0x60 (addr 0xdbc10 offset 904208)
    Section (__DATA, __gosymtab): 0x0 (addr 0xdbc70 offset 904304)
    Section (__DATA, __gopclntab): 0x774d5 (addr 0xdbc80 offset 904320)
    Section (__DATA, __noptrdata): 0xcc5c (addr 0x154000 offset 1396736)
    Section (__DATA, __mod_init_func): 0x8 (addr 0x160c60 offset 1449056)
    Section (__DATA, __data): 0x6910 (addr 0x160c80 offset 1449088)
    Section (__DATA, __bss): 0x1c790 (addr 0x1675a0 offset 0)
    Section (__DATA, __noptrbss): 0x2598 (addr 0x183d40 offset 0)
    Section (__LLVM, __asm): 0x0 (addr 0x0 offset 0)
    Section (__DWARF, __debug_abbrev): 0x1d3 (addr 0x187000 offset 1478656)
    Section (__DWARF, __debug_line): 0x24e75 (addr 0x1871d3 offset 1479123)
    Section (__DWARF, __debug_frame): 0x14224 (addr 0x1ac048 offset 1630280)
    Section (__DWARF, __debug_pubnames): 0x812f (addr 0x1c026c offset 1712748)
    Section (__DWARF, __debug_pubtypes): 0xae69 (addr 0x1c839b offset 1745819)
    Section (__DWARF, __debug_gdb_scri): 0x35 (addr 0x1d3204 offset 1790468)
    Section (__DWARF, __debug_info): 0x767d1 (addr 0x1d3239 offset 1790521)
    Section (__DWARF, __debug_loc): 0x6bb48 (addr 0x249a0a offset 2275850)
    Section (__DWARF, __debug_ranges): 0x26030 (addr 0x2b5552 offset 2717010)
    total 0x2d9934
total 0x0

If I use ld -bitcode_bundle to convert the c-archive into a dylib, there is no resulting __LLVM, __bundle present. Conversely when I use kotlinc-native, the generated framework does have a bitcode xar table of contents present:

$ kotlinc-native lib.kt -produce framework -target ios_arm64 -Xembed-bitcode -output ioslib
$ size -x -l -m ioslib.framework/ioslib
...
Segment __LLVM: 0x170000 (vmaddr 0x8c000 fileoff 557056)
    Section __bundle: 0x16f29b (addr 0x8c000 offset 557056)
    total 0x16f29b
...

This xar can be printed out as follows:

$ otool -v -s __LLVM __bundle ioslib.framework/ioslib
ioslib.framework/ioslib:
Contents of (__LLVM,__bundle) section
For (__LLVM,__bundle) section: xar table of contents:
<?xml version="1.0" encoding="UTF-8"?>
<xar>
 <subdoc subdoc_name="Ld">
  <version>1.0</version>
  <architecture>arm64</architecture>
  <platform>iOS</platform>
  <sdkversion>11.2.0</sdkversion>
  ...
 </subdoc>
 <toc>
  <file id="1">
    ...
   <file-type>Object</file-type>
  </file>
 </toc>
</xar>

Here you can see the xar uses <file-type>Object</file-type> to describe the assembled object. If you compare to a real bitcode compilation (i.e. via clang -fembed-bitcode on a simple C file), you'll notice <file-type>Bitcode</file-type> instead.

I will keep poking at this. I don't have a ton of experience in this area so I'm still trying to figure out all the pieces, but if anyone wants to lend a hand do let me know.

tmm1 commented 5 years ago

Here's an overview of how the Apple toolchain deals with assembled objects.

Given this simple program named exit.s:

.data
    output: .ascii "foo"
.bss
    bar: .quad 0
.text
_run:
  movl $0x2000001, %eax # system call $1 with $0x2000000 offset
  movl $0, %ebx         # set the exit code to be $0
  syscall

We can compile it (with bitcode enabled) into either 1) an object, 2) a shared archive, or 3) a static archive

When compiling into an object, the assembler adds a __LLVM, __asm section which is one byte long, containing 0:

$ clang -g -fembed-bitcode -masm=att exit.s -c -o exit.o

$ size -x -l -m exit.o
Segment : 0x139 (vmaddr 0x0 fileoff 872)
    Section (__TEXT, __text): 0xc (addr 0x0 offset 872)
    Section (__LLVM, __asm): 0x1 (addr 0xc offset 884)
    Section (__DATA, __data): 0x3 (addr 0xd offset 885)
    Section (__DATA, __bss): 0x8 (addr 0x131 offset 0)
    Section (__DWARF, __debug_info): 0x8f (addr 0x10 offset 888)
    Section (__DWARF, __debug_abbrev): 0x28 (addr 0x9f offset 1031)
    Section (__DWARF, __debug_aranges): 0x30 (addr 0xc7 offset 1071)
    Section (__DWARF, __debug_line): 0x3a (addr 0xf7 offset 1119)
    total 0x139
total 0x139

$ otool -v -s __LLVM __asm exit.o
exit.o:
Contents of (__LLVM,__asm) section
000000000000000c    00

When compiling to a shared archive, the linker takes the __LLVM, __asm and summarizes it into __LLVM, __bundle as a "Object / Mach-O Object" (instead of "Bitcode"):

$ clang -g -fembed-bitcode -masm=att exit.s -shared -o exit.dylib

$ size -x -l -m exit.dylib 
Segment __TEXT: 0x1000 (vmaddr 0x0 fileoff 0)
    Section __text: 0xc (addr 0xfac offset 4012)
    Section __unwind_info: 0x48 (addr 0xfb8 offset 4024)
    total 0x54
Segment __DATA: 0x1000 (vmaddr 0x1000 fileoff 4096)
    Section __data: 0x3 (addr 0x1000 offset 4096)
    Section __bss: 0x8 (addr 0x1003 offset 0)
    total 0xb
Segment __LLVM: 0x2000 (vmaddr 0x2000 fileoff 8192)
    Section __bundle: 0x1256 (addr 0x2000 offset 8192)
    total 0x1256
Segment __LINKEDIT: 0x1000 (vmaddr 0x4000 fileoff 16384)
total 0x5000

$ otool -v -s __LLVM __bundle exit.dylib
exit.dylib:
Contents of (__LLVM,__bundle) section
For (__LLVM,__bundle) section: xar table of contents:
<?xml version="1.0" encoding="UTF-8"?>
...
   <contents>
    <type>Mach-O Object</type>
    <unknown>
     <type>Mach-O Object</type>
    </unknown>
   </contents>
   <file-type>Object</file-type>
...

You can also invoke the linker manually yourself with the .o file, using ld -bitcode_bundle to ensure the __LLVM, __bundle section is added:

$ ld -bitcode_bundle -arch x86_64 -macosx_version_min 10.14 exit.o -dylib -o exit.dylib
$ size -x -l -m exit.dylib
Segment __TEXT: 0x1000 (vmaddr 0x0 fileoff 0)
    Section __text: 0xc (addr 0xfac offset 4012)
    Section __unwind_info: 0x48 (addr 0xfb8 offset 4024)
    total 0x54
Segment __DATA: 0x1000 (vmaddr 0x1000 fileoff 4096)
    Section __data: 0x3 (addr 0x1000 offset 4096)
    Section __bss: 0x8 (addr 0x1003 offset 0)
    total 0xb
Segment __LLVM: 0x2000 (vmaddr 0x2000 fileoff 8192)
    Section __bundle: 0x122c (addr 0x2000 offset 8192)
    total 0x122c
Segment __LINKEDIT: 0x1000 (vmaddr 0x4000 fileoff 16384)
total 0x5000

If you try to create a static archive with bitcode, the linker step will fail:

$ clang -g -fembed-bitcode -masm=att exit.s -static -o exit.a
ld: -static and -bitcode_bundle (Xcode setting ENABLE_BITCODE=YES) cannot be used together
clang: error: linker command failed with exit code 1 (use -v to see invocation)

The linker will also warn you if any of your objects are missing bitcode (or the __asm section in this case), and advise that you disable bitcode bundling altogether in order to submit the binary:

$ clang -g -masm=att exit.s -c -o exit.o
$ ld -bitcode_bundle -arch x86_64 -macosx_version_min 10.14 exit.o -dylib -o exit.dylib
ld: warning: all bitcode will be dropped because 'exit.o' was built without bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target.

On the golang side, ideally the linker could be used with -buildmode=c-shared to generate a dylib containing a XAR table of contents in a __LLVM, __bundle section. But since generating and writing the bundle is non-trivial, we can reduce scope instead by using -linkmode=external (to let Apple's linker take care of the the bitcode bundle generation). In this scenario, golang's compiler only needs to add the 1-byte __LLVM, __asm section to each generated object.

Assuming __asm sections existed, the external linker could ideally be used as follows. Unfortunately, it doesn't work because golang is using the incompatible linker option -headerpad (in order to retrofit DWARF details later).

$ go build -ldflags="-linkmode=external -extldflags=-fembed-bitcode -v" -buildmode=c-shared -o test.dylib test.go
host link: "clang" "-m64" "-Wl,-headerpad,1144" "-dynamiclib" "-o" "test.dylib" "-Qunused-arguments" "/var/folders/_2/hljyy_zj3912lv9qqpy70t5w0000gn/T/go-link-181289496/go.o" "-fembed-bitcode"
/usr/local/Cellar/go/1.12/libexec/pkg/tool/darwin_amd64/link: running clang failed: exit status 1
ld: -headerpad and -bitcode_bundle (Xcode setting ENABLE_BITCODE=YES) cannot be used together
clang: error: linker command failed with exit code 1 (use -v to see invocation)

As a work-around, we can use -buildmode=c-archive instead, and then invoke the linker ourselves to assemble the contained objects as a shared library using ld -force_load:

$ go build -buildmode=c-archive -o test.a test.go

$ ld -bitcode_bundle -force_load test.a -dylib -o test.dylib
ld: in test.a(go.o), building for macOS, but linking in object file built for , file 'test.a' for inferred architecture x86_64

This still fails because in c-archive mode the generate mach-o objects are missing LC_VERSION_MIN_MACOSX/LC_VERSION_MIN_IPHONEOS (or its newer combined variant LC_BUILD_VERSION). We can work around this for now with a hacky patch to force LC_VERSION_MIN_MACOSX to be added to each object:

diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index 8315de5152..45f8acfd5b 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -653,7 +680,7 @@ func Asmbmacho(ctxt *Link) {
                }
        }

-       if ctxt.LinkMode == LinkInternal {
+       if ctxt.LinkMode == LinkInternal || ctxt.BuildMode == BuildModeCArchive {
                // For lldb, must say LC_VERSION_MIN_MACOSX or else
                // it won't know that this Mach-O binary is from OS X
                // (could be iOS or WatchOS instead).

Now we can re-generate the c-archive and convert it into a dylib successfully with the external linker. However, since we are missing __LLVM, __asm sections, bitcode generation is disabled with a warning and the resulting dylib is missing the __LLVM, __bundle section:

$ ~/code/go/bin/go build -buildmode=c-archive -o test.a test.go

$ ld -bitcode_bundle -force_load test.a -dylib -o test.dylib -lpthread
ld: warning: all bitcode will be dropped because 'test.a(go.o)' was built without bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target.

$ size -x -l -m test.dylib | grep -A2 __LLVM

At this point, all that's left is patching the golang compiler to add the __LLVM, __asm sections. Once that is in place, ld -bitcode_bundle -force_load should stop warning and generate a xar bundle containing <file-type>Object</file-type> like it did for exit.o above. To do that, we can use this patch:

diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index 8315de5152..923995a02e 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -395,6 +395,13 @@ func (ctxt *Link) domacho() {
                s.Type = sym.SMACHOINDIRECTGOT
                s.Attr |= sym.AttrReachable
        }
+
+       if ctxt.LinkMode == LinkExternal {
+               s := ctxt.Syms.Lookup(".llvmasm", 0) // will be __asm
+               s.Type = sym.SMACHO
+               s.Attr |= sym.AttrReachable
+               s.AddUint8('\x00')
+       }
 }

 func machoadddynlib(lib string, linkmode LinkMode) {
@@ -481,6 +488,11 @@ func machoshbits(ctxt *Link, mseg *MachoSeg, sect *sym.Section, segname string)
                msect.flag = S_MOD_INIT_FUNC_POINTERS
        }

+       if sect.Name == ".llvmasm" {
+               msect.name = "__asm"
+               msect.segname = "__LLVM"
+       }
+
        if segname == "__DWARF" {
                msect.flag |= S_ATTR_DEBUG
        }

Now we can re-generate the c-archive and see the 1-byte __LLVM, __asm marker:

$ ~/code/go/bin/go build -buildmode=c-archive -o test.a test.go

$ size -x -l -m test.a
test.a(go.o):
Segment : 0x0 (vmaddr 0x0 fileoff 4096)
    Section (__TEXT, __text): 0x9380c (addr 0x0 offset 4096)
    Section (__DATA, __rodata): 0x477e9 (addr 0x93820 offset 608288)
    ...
    Section (__LLVM, __asm): 0x1 (addr 0x154000 offset 1396736)
    ...

And we can turn it into a dylib with ld and see that a full bitcode bundle TOC is generated!

$ ld -bitcode_bundle -force_load test.a -dylib -o test.dylib -lpthread

$ size -x -l -m test.dylib | grep -A2 __LLVM
Segment __LLVM: 0x327000 (vmaddr 0x187000 fileoff 1478656)
    Section __bundle: 0x326260 (addr 0x187000 offset 1478656)
    total 0x326260

$ otool -v -s __LLVM __bundle test.dylib
test.dylib:
Contents of (__LLVM,__bundle) section
For (__LLVM,__bundle) section: xar table of contents:
<?xml version="1.0" encoding="UTF-8"?>
<xar>
...
   <file-type>Object</file-type>
...
</xar>

By adding the __asm marker, the toolchain now knows the go.o object was built with a bitcode aware compiler, and happily indexes that object into the bitcode bundle! Hooray!


Getting the same thing working on iOS is even easier, since the first hacky patch above for macOS testing is not required.

In addition, the linker step to convert the .a into a .dylib is also not required. As long as the individual objects in the .a contain either __bitcode or __asm sections, Xcode will link them correctly just by dragging the .a into your xcode project. I compiled my library as follows:

$ CGO_CFLAGS="-isysroot ... -arch arm64 -fembed-bitcode"
$ CGO_LDFLAGS="-isysroot ... -arch arm64 -fembed-bitcode"
$ CC=clang
$ CGO_ENABLED=1
$ GOOS=darwin
$ GOARCH=arm64
$ go build -buildmode=c-archive -tags=ios -o test.a test.go

Then I confirmed that the test.a generated for iOS could be copied into an xcodeproject and deployed to my iOS device, with bitcode enabled on the entire project. Without the patch, Xcode refuses to build and install the ipa on a real device. I assume the ipa could also be submitted to the App Store given user reports on the kotlin-native thread.

Given that this works on iOS, I assume it will also work on tvOS and allow golang to be used in Apple TV apps. I have an Apple TV device that I will run some tests on...

drewpitchford commented 5 years ago

@tmm1 make a PR with that. It’d help a lot of people!

tmm1 commented 5 years ago

Given that this works on iOS, I assume it will also work on tvOS and allow golang to be used in Apple TV apps. I have an Apple TV device that I will run some tests on...

I was able to get my golang c-archive working on an Apple TV 4 as well!!

It was slightly more tricky there because the lack of a LC_VERSION_MIN_* in the go.o makes the Apple toolchain assume you built for iOS (which is why copying over the .a directly worked for the iOS case earlier). Without LC_VERSION_MIN_TVOS set in go.o I couldn't get Xcode to compile or run on device.

So I used this quick hack to see if it would work, and sure enough I am able to deploy and execute golang code on my bitcode-required tvOS device.

diff --git a/src/cmd/link/internal/ld/macho.go b/src/cmd/link/internal/ld/macho.go
index 923995a02e..fbc1c8cf16 100644
--- a/src/cmd/link/internal/ld/macho.go
+++ b/src/cmd/link/internal/ld/macho.go
@@ -678,6 +705,10 @@ func Asmbmacho(ctxt *Link) {
                ml.data[0] = 10<<16 | 7<<8 | 0<<0 // OS X version 10.7.0
                ml.data[1] = 10<<16 | 7<<8 | 0<<0 // SDK 10.7.0
        }
+       if ctxt.BuildMode == BuildModeCArchive && true /* tvos */ {
+               ml := newMachoLoad(ctxt.Arch, LC_VERSION_MIN_TVOS, 2)
+               ml.data[0] = 11<<16 | 0<<8 | 0<<0 // tvOS version 11.0
+       }

        a := machowrite(ctxt.Arch, ctxt.Out, ctxt.LinkMode)
        if int32(a) > HEADR {
eliasnaur commented 5 years ago

This is excellent news! I'm confused by the plethora of LC_VERSIONs. Are they mutually exclusive? If so, can we rely on the user (such as gomobile) setting the versions with -fmin-version-*? We don't use internal linking for c-archive anyway. Finally, you did this work for macOS as well. Is bitcode needed for macOS?

tmm1 commented 5 years ago

I'm confused by the plethora of LC_VERSIONs. Are they mutually exclusive?

Yes they are mutually exclusive. Each object must be tagged as for iOS, tvOS, macOS, etc. To do this a LC_VERSION_MIN for that OS must be added.

If so, can we rely on the user (such as gomobile) setting the versions with -fmin-version-*?

We can set this via CGO_CFLAGS for all the externally compiled objects, but the main go.o is generated by the go compiler and will not add any of the required LC_VERSION commands. It seems to me we need a way to teach the go compiler about sub-platforms within GOOS=darwin. Perhaps something like GODARWIN=ios ?

Finally, you did this work for macOS as well. Is bitcode needed for macOS?

Not that I know of. It was just easier for me to test everything on macOS first, to understand how the pieces fit together.

eliasnaur commented 5 years ago

Thank you.

@randall77 in light of the above, how do you think we should proceed? I hope adding the __asm section is ok, but what about correctly setting LC_VERSION_MIN?

eliasnaur commented 5 years ago

@tmm1 can you think of ways to auto-detect the platform? For example, UIKit usage indicates iOS, AppKit macOS. Is there a certain way to detect watchOS and tvOS?

tmm1 commented 5 years ago

@tmm1 can you think of ways to auto-detect the platform? For example, UIKit usage indicates iOS, AppKit macOS. Is there a certain way to detect watchOS and tvOS?

Interesting thought. If we're talking about CGO_ENABLED=1, then (since the user is already required to setup the compiler correctly, i.e. set -isysroot with the particular SDK they want to use) we could look for the symbols TARGET_OS_IPHONE, TARGET_OS_TV, TARGET_OS_WATCH etc from TargetConditionals.h

However I'm not sure if this information is readily available from the go compiler, nor what alternative solution might be available if cgo was disabled.

randall77 commented 5 years ago

A GODARWIN setting is a possibility. I'd like to see if we can figure out how to avoid it, though. Can we force external linking for everything but macOS? And can we set this using a linker flag somehow?

tmm1 commented 5 years ago

Can we force external linking for everything but macOS? And can we set this using a linker flag somehow?

I tried using -ldflags="-linkmode=external -extldflags=-mtvos-version-min=11.0" but the generated go.o is still missing the load command. I'm not clear on where the boundary between the go compiler and linker is. I assume the compiler generates the go.o object, but it seems to use link/internal/ld/macho.go to do it.

eliasnaur commented 5 years ago

@randall77 Xcode checks all object files in a c-archive, so the go.o object file produced by the Go linker also needs the LC_VERSIONMIN load commands. I would be surprised if any linker flag could override this setting on an existing object file. So I think that leaves setting the value from the Go linker. Since iOS et al are always externally linked, our linker could read load commands from the .o files produced by cgo and copy any LC_VERSIONMIN commands to go.o. Does that sound plausible? If so, any pointers to where in the linker to add this logic would be greatly appreciated.

randall77 commented 5 years ago

our linker could read load commands from the .o files produced by cgo

Does our linker see those files? In external linking mode I would think the Go linker only sees the output of the Go compiler - everything from C compiler gets passed directly to the C linker. At least, that's my (probably poor) understanding.

Also, just because we're externally linking doesn't mean we're using cgo.

But I do like the idea of tricking the C compiler into telling us what LC_VERSIONMIN* string to use. Maybe we compile a very simple .c file and grovel through the output? This would mean that building for these targets would require a proper CC setup, including cross-compiling if that's what you're doing.

Maybe a GODARWIN would be simpler.

gopherbot commented 5 years ago

Change https://golang.org/cl/168318 mentions this issue: cmd/go/internal/work: whitelist tvOS and watchOS compiler flags

gopherbot commented 5 years ago

Change https://golang.org/cl/168062 mentions this issue: cmd/gomobile: enable bitcode

tmm1 commented 5 years ago

@eliasnaur I'm curious what impact your whitelist change in https://github.com/golang/go/commit/b4f3b8a313a81105d923d2b7ed0b8b7524084b63 has?

eliasnaur commented 5 years ago

Very little; it allows the tvOS and watchOS flags in #cgo directives. I noticed the missing flags while working on this.

tmm1 commented 5 years ago

Very little; it allows the tvOS and watchOS flags in #cgo directives. I noticed the missing flags while working on this.

Aha, okay. It was working for me already via the CGO_CFLAGS env vars so I was curious what the change enabled.

Maybe a GODARWIN would be simpler.

Another idea would be to add some new flags to the go linker, so the user could do something like -ldflags="-machoplatform=ios -machominver=11.0.0"

If we can settle on a good approach here, I'm happy to start putting together some CLs.

Another thing to keep in mind is that the LC_VERSION_MIN_* have been replaced with LC_BUILD_VERSION for the next macOS release, and that this new load command will likely be required to build marzipan apps which work on both iOS and macOS. However since all existing macOS/iOS/tvOS/watchOS releases still use the older variant, we will need to keep supporting that for the foreseeable future.

For reference, here are the two load command definitions:

    struct version_min_command {
      uint32_t cmd;       // LC_VERSION_MIN_MACOSX or
                          // LC_VERSION_MIN_IPHONEOS
      uint32_t cmdsize;   // sizeof(struct version_min_command)
      uint32_t version;   // X.Y.Z is encoded in nibbles xxxx.yy.zz
      uint32_t sdk;       // X.Y.Z is encoded in nibbles xxxx.yy.zz
    };

    struct build_tool_version {
      uint32_t tool;      // enum for the tool
      uint32_t version;   // version of the tool
    };

    struct build_version_command {
      uint32_t cmd;       // LC_BUILD_VERSION
      uint32_t cmdsize;   // sizeof(struct build_version_command) +
                          // ntools * sizeof(struct build_tool_version)
      uint32_t platform;  // platform
      uint32_t minos;     // X.Y.Z is encoded in nibbles xxxx.yy.zz
      uint32_t sdk;       // X.Y.Z is encoded in nibbles xxxx.yy.zz
      uint32_t ntools;    // number of tool entries following this
    };

One advantage to the linker flag approach is that we could add more flags like -machosdkver etc as the need arises. Whereas GODARWIN could only support at-most platform and minver (via GODARWIN=ios or GODARWIN=ios11.0.0).

gopherbot commented 5 years ago

Change https://golang.org/cl/168321 mentions this issue: cmd/link/internal/ld: copy LC_VERSION_MIN_* commands to Go object file

gopherbot commented 5 years ago

Change https://golang.org/cl/168320 mentions this issue: cmd/link: enable bitcode build for iOS/tvOS/watchOS

eliasnaur commented 5 years ago

You can check out my current progress at the two CLs posted above. I got LC_VERSIONMIN* and LC_BUILD_VERSION copying to work, and only need tests.

gopherbot commented 5 years ago

Change https://golang.org/cl/168459 mentions this issue: cmd/link/internal/ld: extract Mach-O load command parsing