emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.79k stars 3.31k forks source link

Unable to compile a simple Swift file (maybe not possible?) #2427

Closed geelen closed 4 years ago

geelen commented 10 years ago

Hi team, playing around with Emscripten in earnest for the first time, not my usual area of work so apologies if this is clearly never going to work. What I'd like to do is compile a simple Swift program to JS:

println("Hello, world!")

So far what I've tried is:

xcrun swift hello_world.swift -emit-bc -o hello_world.bc
emcc hello_world.bc

but that fails because it can't find certain symbols (looks like the standard library isn't present):

Value:   %1 = call { i8*, i64, i64 } @_TFSS37_convertFromBuiltinUTF16StringLiteralfMSSFTBp17numberOfCodeUnitsBw_SS(i8* bitcast ([14 x i16]* @0 to i8*), i64 13)
LLVM ERROR: Unrecognized struct value
Traceback (most recent call last):
  File "/Users/glen/Downloads/emsdk_portable/emscripten/1.16.0/emcc", line 1540, in <module>
    shared.Building.llvm_opt(final, link_opts)
  File "/Users/glen/Downloads/emsdk_portable/emscripten/1.16.0/tools/shared.py", line 1267, in llvm_opt
    assert os.path.exists(target), 'Failed to run llvm optimizations: ' + output
AssertionError: Failed to run llvm optimizations:

Drilling a bit further, and looking at how xcrun swift -v hello_world.swift actually works, I'm able to use this linking command to compile the bitcode to an executable using normal ld.

cp /Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/macosx/libswift_stdlib_core.dylib .
/usr/bin/ld hello_world.bc \
              -lSystem -arch x86_64 \
              -L . -rpath . \
              -o hello_world
./hello_world
=> Hello, world!

So, it seems like libswift_stdlib_core.dylib is the only dependency for hello_world.bc to be properly linked to an executable.

But I'm stuck - is there some equivalent between the -L and -rpath flags on ld that I should be passing to emcc? Or is a dylib like that not possible to be used in emscripten? The final command I tried was:

> emcc libswift_stdlib_core.dylib hello_world.bc                                 ~/src/experiments/swift.js • 2.1.0p0
WARNING  root: emcc: cannot find library "swift_stdlib_core"
Value:   %1 = call { i8*, i64, i64 } @_TFSS37_convertFromBuiltinUTF16StringLiteralfMSSFTBp17numberOfCodeUnitsBw_SS(i8* bitcast ([14 x i16]* @0 to i8*), i64 13)
LLVM ERROR: Unrecognized struct value
Traceback (most recent call last):
  File "/Users/glen/Downloads/emsdk_portable/emscripten/1.16.0/emcc", line 1540, in <module>
    shared.Building.llvm_opt(final, link_opts)
  File "/Users/glen/Downloads/emsdk_portable/emscripten/1.16.0/tools/shared.py", line 1267, in llvm_opt
    assert os.path.exists(target), 'Failed to run llvm optimizations: ' + output
AssertionError: Failed to run llvm optimizations:

Hopefully something's possible from here - Swift is a neat high-level language with a nice type system and no garbage collector, which makes me think it's a good fit for compiling to JS.

juj commented 10 years ago

Unfortunately it is not possible to link .dylib files to Emscripten. The reason for that is that the .dylibs already contain native machine code for x86/x64, and Emscripten cannot "go back" and get that to LLVM IR form again. What one would have to do is implement the standard library for Swift and compile that in. The unrecognized struct value errors sounds like something unrelated to this linking issue.

Perhaps it might be possible to stub in those standard library functions with your own implementation?

geelen commented 10 years ago

Interesting, I suspected as much, but had hoped dylibs might have llvm bitcode in there.

I'll take a look to see how much of the Swift standard library source is available to compile from, but I fear it won't be enough.

Appreciate the response, thanks!

On Saturday, June 14, 2014, juj notifications@github.com wrote:

Unfortunately it is not possible to link .dylib files to Emscripten. The reason for that is that the .dylibs already contain native machine code for x86/x64, and Emscripten cannot "go back" and get that to LLVM IR form again. What one would have to do is implement the standard library for Swift and compile that in. The unrecognized struct value errors sounds like something unrelated to this linking issue.

Perhaps it might be possible to stub in those standard library functions with your own implementation?

— Reply to this email directly or view it on GitHub https://github.com/kripken/emscripten/issues/2427#issuecomment-46086938.

iongion commented 10 years ago

What if we implement our own standard lib, according to http://practicalswift.com/2014/06/14/the-swift-standard-library-list-of-built-in-functions there are just 74 functions

JoshCheek commented 10 years ago

If the println command was omitted, would this work? (it didn't for me, I still got the same result, but I'm very ignorant) Then one could create a library and just call it from js without it depending on anything.

Also, an update, the commands in the first post seem deprecated, here is what I tried:

Versions

$ xcrun swiftc --version
Swift version 1.0 (swift-600.0.45.3.2)
Target: x86_64-apple-darwin13.2.0

$ emcc --version
emcc (Emscripten GCC-like replacement) 1.25.0 ()
Copyright (C) 2014 the Emscripten authors (see AUTHORS.txt)
This is free and open source software under the MIT license.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

The file to compile:

$ cat f.swift
var message = 1

The IR

$ xcrun swiftc -emit-ir f.swift
; ModuleID = '-'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.2.0"

%Si = type <{ i64 }>

@_Tv1f7messageSi = global %Si zeroinitializer, align 8

define internal void @top_level_code() {
entry:
  store i64 1, i64* getelementptr inbounds (%Si* @_Tv1f7messageSi, i32 0, i32 0), align 8
  ret void
}

define i32 @main(i32 %argc, i8** %argv) {
entry:
  %0 = call i8* @_TFSsa6C_ARGCVSs5Int32()
  %1 = bitcast i8* %0 to i32*
  store i32 %argc, i32* %1
  %2 = call i8* @_TFSsa6C_ARGVGVSs20UnsafeMutablePointerGS_VSs4Int8__()
  %3 = bitcast i8* %2 to i8***
  store i8** %argv, i8*** %3
  call void @top_level_code()
  ret i32 0
}

declare i8* @_TFSsa6C_ARGCVSs5Int32()

declare i8* @_TFSsa6C_ARGVGVSs20UnsafeMutablePointerGS_VSs4Int8__()

!llvm.module.flags = !{!0, !1, !2, !5, !6, !7, !8}

!0 = metadata !{i32 2, metadata !"Dwarf Version", i32 3}
!1 = metadata !{i32 1, metadata !"Debug Info Version", i32 1}
!2 = metadata !{i32 6, metadata !"Linker Options", metadata !3}
!3 = metadata !{metadata !4}
!4 = metadata !{metadata !"-lswiftCore"}
!5 = metadata !{i32 1, metadata !"Objective-C Version", i32 2}
!6 = metadata !{i32 1, metadata !"Objective-C Image Info Version", i32 0}
!7 = metadata !{i32 1, metadata !"Objective-C Image Info Section", metadata !"__DATA, __objc_imageinfo, regular, no_dead_strip"}
!8 = metadata !{i32 4, metadata !"Objective-C Garbage Collection", i32 0}

The SIL (IDK if it's useful, but can't hurt):

$ xcrun swiftc -emit-sil f.swift
sil_stage canonical

import Builtin
import Swift
import SwiftShims

var message: Int

// top_level_code
sil private @top_level_code : $@thin () -> () {
bb0:
  %0 = global_addr #message : $*Int               // user: %3
  %1 = integer_literal $Builtin.Word, 1           // user: %2
  %2 = struct $Int (%1 : $Builtin.Word)           // user: %3
  store %2 to %0 : $*Int                          // id: %3
  %4 = tuple ()                                   // user: %5
  return %4 : $()                                 // id: %5
}

// Swift.Int._convertFromBuiltinIntegerLiteral (Swift.Int.Type)(Builtin.Int2048) -> Swift.Int
sil public_external [transparent] @_TFSi33_convertFromBuiltinIntegerLiteralfMSiFBi2048_Si : $@thin (Builtin.Int2048, @thin Int.Type) -> Int {
bb0(%0 : $Builtin.Int2048, %1 : $@thin Int.Type):
  %2 = builtin_function_ref "s_to_s_checked_trunc_Int2048_Word" : $@thin (Builtin.Int2048) -> (Builtin.Word, Builtin.Int1) // user: %3
  %3 = apply %2(%0) : $@thin (Builtin.Int2048) -> (Builtin.Word, Builtin.Int1) // user: %4
  %4 = tuple_extract %3 : $(Builtin.Word, Builtin.Int1), 0 // user: %5
  %5 = struct $Int (%4 : $Builtin.Word)           // user: %6
  return %5 : $Int                                // id: %6
}

Trying to compile with emcc:

$ xcrun swiftc -emit-bc f.swift -o f.bc
$ emcc f.bc
WARNING: Linking two modules of different data layouts: '/Users/josh/.emscripten_cache/libc.bc' is 'e-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-p:32:32:32-v128:32:128-n32-S128' whereas '/Users/josh/deleteme/swift-play/f.bc' is 'e-m:o-i64:64-f80:128-n8:16:32:64-S128'
WARNING: Linking two modules of different target triples: /Users/josh/.emscripten_cache/libc.bc' is 'asmjs-unknown-emscripten' whereas '/Users/josh/deleteme/swift-play/f.bc' is 'x86_64-apple-darwin13.2.0'
Unknown specifier in datalayout string
UNREACHABLE executed at /Users/clb/emscripten-fastcomp/lib/IR/DataLayout.cpp:300!
0  opt                      0x00000001086d04ae llvm::sys::PrintStackTrace(__sFILE*) + 46
1  opt                      0x00000001086d07bb PrintStackTraceSignalHandler(void*) + 27
2  opt                      0x00000001086d0b4c SignalHandler(int) + 412
3  libsystem_platform.dylib 0x00007fff8b0e35aa _sigtramp + 26
4  libsystem_platform.dylib 0x00007fff6492d380 _sigtramp + 3649347056
5  opt                      0x00000001086d07eb raise + 27
6  opt                      0x00000001086d08a2 abort + 18
7  opt                      0x000000010865a7a6 llvm::llvm_unreachable_internal(char const*, char const*, unsigned int) + 198
8  opt                      0x0000000108416b74 llvm::DataLayout::parseSpecifier(llvm::StringRef) + 2804
9  opt                      0x0000000108415c57 llvm::DataLayout::init(llvm::StringRef) + 471
10 opt                      0x000000010749b47e llvm::DataLayout::DataLayout(llvm::StringRef) + 158
11 opt                      0x0000000107482ba5 llvm::DataLayout::DataLayout(llvm::StringRef) + 37
12 opt                      0x000000010747943c main + 3756
13 libdyld.dylib            0x00007fff8a2865fd start + 1
Stack dump:
0.  Program arguments: /Users/josh/code/emsdk_portable/clang/e1.25.0_64bit/opt /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/tmp93OFuV/a.out.bc -strip-debug -internalize -internalize-public-api-list=main,malloc,free -globaldce -pnacl-abi-simplify-preopt -pnacl-abi-simplify-postopt -enable-emscripten-cxx-exceptions -o /var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/tmp93OFuV/a.out.bc.opt.bc
Traceback (most recent call last):
  File "/Users/josh/code/emsdk_portable/emscripten/1.25.0/emcc", line 1224, in <module>
    shared.Building.llvm_opt(final, link_opts)
  File "/Users/josh/code/emsdk_portable/emscripten/1.25.0/tools/shared.py", line 1357, in llvm_opt
    assert os.path.exists(target), 'Failed to run llvm optimizations: ' + output
AssertionError: Failed to run llvm optimizations:
JoshCheek commented 10 years ago

If anyone can look at the above and give me an idea of what specifically it's missing, ie "it's trying to link stdlib.swift" and a link to somewhere that someone documents how they solved a similar problem, then I'll put a few hours into trying to get it. If there's any reasonable progress in that time, I'll report it here and probably keep at it.

Right now I'm just so ignorant that I don't even know what what the problem is or what to look for in terms of resolving it. But I have like 50 options I'm considering for my project, including things like Elm which I've already put a few days into playing with, so I need some guidance to not wander in circles for a months.

kripken commented 10 years ago

The targets need to match. As the IR shows, the swift frontend emits

target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.2.0"

Emscripten uses its own triple (asmjs-unknown-emscripten). You might be able to force the frontend to use it? If not, you might be able to just edit the LLVM IR file in text format to fix it (create one using emcc, to see the right values for those two lines). But, this is risky, as the triple affects stuff that is baked into the output in various places...

JoshCheek commented 10 years ago

So, I'm ignorant enough that I don't know how to interpret "the targets need to match" (I don't know what "target" means, or what a "triple" is -- kinda sounds like a fixed-length set of memory, like a "tuple" in a functional language). So it's unclear to me if it's something that I fucked up (e.g. did I compile it wrong, or with my own version of some binary, since emscripten uses its own set of binaries -- e.g. python2 was not in the PATH, and I couldn't find it in the emsdk_portable, so I just made a bash script named python2 that then invoked python and forwarded the args. Seemed reasonable since I had Python 2.7.5 installed, which the readme alleged was compatible https://github.com/kripken/emscripten/blob/06961a0ef6e3d8d92d5e36ff904262fefec62bec/tests/python/readme.md, and xcrun is some binary that I don't understand, which is just sort of generally available, but didn't come from emscripten) or is it some issue caused by Swift being overly zealous about an OSX environment, or is it some flag to find to tell it to chill the fuck out and forget about being execuatble and just compile as if some other program will load it and deal with all the integration stuff? I mean, you'll notice all I do in this script is declare a variable and assign it the value 1, so what is it trying to dynamically load? And does that have anything to do with the "targets" not matching?

I'll create the file using emcc and diff them and spend an hour poring over the differences, confused and trying and failing repeatedly, to figure out what I need to do, but I don't know how. The bc format si the only one I've found so far that emcc will even consider trying to compile.

The reality is that I have no particularly good model for figuring out how this stuff works. I once spent 8 hours getting a C program to compile and load a library I was interested in, that's about the extent of my experience. Code that operates at this level operates under a model that I am simply ignorant of, so while we're in this domain, it's reasonable to me like I've never programmed before.

I'm actually pretty good at guessing and cursing repeatedly trying and failing and guessing and cursing again, but I need some sort of model or context to iterate upon. And there's a sufficiently large amount of information available out there, that I need someone who's familiar with this domain to clue me in, and imply to me that this isn't a giant waste of my time (b/c, lets be honest, I could just suck it up and write my shit in JavaScript).

kripken commented 10 years ago

A target is what clang/LLVM is emitting code for. When you build normally, on OSX your target triple is something like darwin-intel-apple or some other combo that contains the information that you are building for intel hardware, to run on a darwin kernel on an apple userspace. This affects codegen in various ways, and the LLVM IR is not portable because of it.

Emscripten has its own target, and emcc tells clang and LLVM to use it, -triple=asmjs-unknown-emscripten or something like that.

JoshCheek commented 10 years ago

Hmm. Guess I thought LLVM was the target.

Anyway, asmjs-unknown-emscripten looks correct:

$ ag asmjs-unknown-emscripten | wc -l
     115

-triple isn't the correct flag, apparently, and it tells me the wrong help file:

$ xcrun swiftc -Xllvm "-triple=asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc
swift (LLVM option parsing): Unknown command line argument '-triple=asmjs-unknown-emscripten'.  Try: 'swift (LLVM option parsing) -help'
swift (LLVM option parsing): Did you mean '-spiller=asmjs-unknown-emscripten'?

$ swift '(LLVM option parsing)' -help
fish: Unknown command 'swift'

$ xcrun swift '(LLVM option parsing)' -help
<unknown>:0: error: no such file or directory: '(LLVM option parsing)'

$ xcrun swift 'LLVM option parsing' -help
<unknown>:0: error: no such file or directory: 'LLVM option parsing'

$ xcrun swift 'option parsing' -help
<unknown>:0: error: no such file or directory: 'option parsing'

llvm isn't a binary, but tab-complete suggestions include llvm-gcc and llvm-g++, their help screens don't mention a triple, but do talk about a --target, but that doesn't work either. Which is strange, because it's listed in the help output, so it seems like the error should be something about using it wrong, rather than "Unknown command line argument", so maybe this isn't the binary that it invokes for llvm:

$ xcrun swiftc -Xllvm "--target=asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc
swift (LLVM option parsing): Unknown command line argument '--target=asmjs-unknown-emscripten'.  Try: 'swift (LLVM option parsing) -help'
swift (LLVM option parsing): Did you mean '-stats=asmjs-unknown-emscripten'?

# same output for each of these potential variations:
$ xcrun swiftc -Xllvm "--target=asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc
$ xcrun swiftc -Xllvm "--target asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc
$ xcrun swiftc -Xllvm "-target asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc
$ xcrun swiftc -Xllvm "-target=asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc
$ xcrun swiftc -Xllvm "--target" -Xllvm "asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc

# for that last one, it says "swift (LLVM option parsing): Did you mean '-stats'?"
# so lets verify that llvm-gcc is the right binary by seeing if `-stats` is one of its options:
$ llvm-gcc --help | grep stats

# ...nope, what the fuck is llvm?
# searching implies its binary is named "clang" for some reason,
# but no dice here either:
$ clang --help | grep stats

# man pages have any ideas?
$ man llvm-gcc
No manual entry for llvm-gcc

$ man llvm-g++
No manual entry for llvm-g++

$ man clang # this one works!

# After looking through this man page, I try -arch, which doesn't do shit
# was going to try setting MACOSX_DEPLOYMENT_TARGET env var
# but then I realized there's some way to search all man pages for "-stats"
# some stupid searching (man man) eventually I figure out:
$ man -K stats

# go through this list, and `gcc-4.8` shows up, so maybe llvm is just gcc? *shrug* lets try it
# it turns out to be fruitless
# at this point, I'm considering trying to find in the emscripten code where they invoke it
# figure I'll try googling a bit, find some docs for `llc`, which isn't a binary on my system, but does have a `-mtriple` flag, so lets try that:
$ xcrun swiftc -Xllvm "-mtriple=asmjs-unknown-emscripten" -emit-bc f.swift -o f.bc

# run the gamut of possible ways that I'm supposed to pass this thing
# (why can't I find a fucking example of how to do this?)

# eventually, one of the options suggests "-spiller" as a correction possibility
$ man -K spiller  # ...just some thing about crypto

At this point, documenting in code would add another hour, but many searches, fancy -k and -K uses of the man flag. A google search for llvm -spiller and -stats takes me back to that llc page, look through it a second time, this time notice that they have two dashes before them, even though the suggestion when I got it wrong only has 1 dash. So, try the permutations of possible ways to pass an argument to -Xllvm with two dashes. None of them work.

Get pissed, internet search like the fourth time for an example of how to use -Xllvm flag, no results, cd to xcode and search for the string 'Xllvm', nothing. Try emsdk_portable, nothing.

Look one more time at llvm-gcc, llvm-g++, lldb, apropos llvm, clang, nothing.

Decide to go see if I can find where this happens in emsdk_portable, looks like the makefiles set a build_triple but never use it. Probably some makefile magic, and I don't feel like reading a book about makefiles just to find out how this gets persisted. Notice some of the files use LLVM_TARGET, I don't know Python, maybe that's an env var, so try compiling with that set env LLVM_TARGET=asmjs-unknown-emscripten xcrun swiftc -emit-bc f.swift -o f.bc, but it doesn't do anything (triple is still set to "x86_64-apple-darwin13.2.0"), remember the clang man pages had an env var that seemed like it had potential, so env MACOSX_DEPLOYMENT_TARGET=asmjs-unknown-emscripten xcrun swiftc -emit-bc f.swift -o f.bc. Nope.

So, I'm like 3 hours in, and literally no further than when I started. Looks like someone got Rust compiling to asm.js, maybe that's a better choice. Someone got Go compiling directly to JavaScript. Might be sufficient. Could go suffer through the Elm type system for another week or two (it was basically like this: hours of slamming myself into the wall, but I kept at it for like 3 times longer), or maybe write the thing in C, since that at least compiles, but some part of me thinks that's masochism. If I did it in Opal, I wouldn't have to learn a new language... but that's part of the appeal, and I'm not sure it'll be able to scale in directions I might want to go with my project.

Going to go for a drink and weigh the shittiness of C as a language against the fact that I'll almost certainly succeed.

endash commented 9 years ago

I was able to get very, very simple programs to build with the 6.3 beta at least. Unfortunately even something as simple as a string literal will not work, and regardless the resulting file has a bunch of missing symbols. Some of the symbols (swift_once) are defined in libswiftCore.dylib, so it's likely using Swift will be impossible until the standard library is open sourced.

guidobouman commented 9 years ago

So, luckily, this will help later on: https://developer.apple.com/swift/blog/?id=29

nmn commented 9 years ago

So I was trying to do the same thing, and I just stumbled upon this Issue. So glad that I won't waste my time again.

By the way did you try again with XCode 7 and the swiftc tool??

stepanhruda commented 9 years ago

Since Apple now supports LLVM Bitcode, would that enable including dylibs that contain it?

kripken commented 9 years ago

LLVM bitcode is not portable. Bitcode for the App Store would be specific to Apple's hardware and OS.

stepanhruda commented 9 years ago

Thanks, that makes sense. In case anyone is interested in more details as I was, LLVM FAQ: Can I compile C or C++ code to platform-independent LLVM bitcode?

geelen commented 8 years ago

It's been 18 months since I started this issue and I still really want this to happen. With Swift going open-source today let's hope that means it's now possible!

https://github.com/apple/swift :tada:

kripken commented 8 years ago

This will definitely make it a lot easier! :)

Ok, to move forward here, we need to do the following:

  1. Figure out how to use emscripten's llvm with swift's llvm. Does swift have a fork of llvm, or can it use stock? If the latter, then this should be easy, just make swift use emscripten's llvm. We just need to see which version of llvm it expects (the incoming branch is on trunk llvm from last week, hopefully that is good and swift doesn't need an older tag).
  2. Get swift to emit code with the asmjs-unknown-emscripten triple.
  3. Get swift to stop at the bitcode stage.
  4. Run emcc on that bitcode and see what happens.
  5. We would need similar bitcode of whatever runtime libraries swift requires, except for libc etc. which we already have.
Gaelan commented 8 years ago

Swift does have a fork of LLVM at apple/swift-llvm. Not sure what changes they have made.

nmn commented 8 years ago

the swift-cli has a bunch of handy options to output just the llvm byte code etc.

jcampbell05 commented 8 years ago

Let me know what I can help with would love to have something working!

kripken commented 8 years ago

Thanks for the info @Gaelan. Ok, if they have their own fork, then the best workflow would probably be:

  1. Upstream our target triple to LLVM
  2. When they merge in new LLVM, they'll get that
  3. Get Swift to emit bytecode to our target triple
  4. Run that bytecode through emcc

The first step is to upstream our triple, which is already partially there (due to nacl upstreamings), so it's a small patch and probably not controversial. What we need to send is basically this:

https://gist.github.com/kripken/0b7ba068faf21d5449a3

Anyone interested to help upstream that?

ianyh commented 8 years ago

It looks like Apple's llvm clone is pretty strict about changes being made upstream unless they are specifically swift-related. See: https://swift.org/contributing/#llvm-and-swift

jcampbell05 commented 8 years ago

Could open an issue in the Swift LLVM github to ask them for changes

ianyh commented 8 years ago

I think that reduces to asking them to merge the changes in themselves, which is against their policy. The patch would have to be submitted directly to llvm by someone here.

I have no familiarity with the llvm community, so I don't know how they would feel about, for example, introducing asmjs as an architecture. Anyone else have thoughts?

sberan commented 8 years ago

This is very exciting @kripken! In theory, should we be able to make these modifications locally and try things out using the swift LLVM clone? I modified the relevant files in the swift LLVM clone, according to your patch - here is the diff of the changes I made: https://gist.github.com/sberan/43bc5fbff78ea47658f0

Unfortunately I don't see a new target for asmjs in the swift recompiled LLVM:

./build/Ninja-DebugAssert/llvm-macosx-x86_64/bin/llc --version
LLVM (http://llvm.org/):
  LLVM version 3.8.0svn
  DEBUG build with assertions.
  Built Dec  6 2015 (19:49:40).
  Default target: x86_64-apple-macosx10.9
  Host CPU: haswell

  Registered Targets:
    aarch64    - AArch64 (little endian)
    aarch64_be - AArch64 (big endian)
    arm        - ARM
    arm64      - ARM64 (little endian)
    armeb      - ARM (big endian)
    thumb      - Thumb
    thumbeb    - Thumb (big endian)
    x86        - 32-bit X86: Pentium-Pro and above
    x86-64     - 64-bit X86: EM64T and AMD64

Maybe more work needs to be done here in order to register asmjs as a target? Forgive me if this is a noob question, I'm very new to llvm and emscripten, just very excited to try swift in the browser :smile:

I'd be happy to try and push these changes upstream, but I'd like to verify that they'll work first! :laughing:

ianyh commented 8 years ago

I'll try to give it a go when I get a chance!

kripken commented 8 years ago

I think you might not see a target because you can't build a full executable for js. But you should be able to build with -target asmjs-unknown-emscripten -emit-llvm and it should emit llvm for that specific target triple.

kripken commented 8 years ago

There is another possible path here. Upstream LLVM has a WebAssembly triple now. It is almost identical to our asm.js triple. It should be possible for swift to build to the wasm-wasm-wasm triple, then import that bitcode into emscripten which can compile it to asm.js.

(We would still need to make sure it's the same LLVM version, or close enough, to avoid e.g. debug info changes. But emscripten merges upstream every week or two now, so that should be easy, if someone tells me what is a good time to merge for Swift, i.e., when Swift merges.)

Note that the wasm triple is still experimental, so you need to build LLVM with it enabled, from the list of experimental targets.

jcampbell05 commented 8 years ago

@kripken - This is an overview of the release process for Swift https://swift.org/blog/swift-2-2-release-process/

naan commented 8 years ago

Swift uses llvm 3.8.0. They have their branch and not exactly sure their policy but once they finish swift 2.2, they'll probably merge their branch to upstream trunk.

3.8.0 already has wasm as kripken said, so I was able to build swift-llvm with wasm64 enabled and pass basic tests, but struggling to build swift with llvm-wasm and build swift stdlibs with with llvm-wasm + emscripten, main reason is that their build system is huge but not so flexible. (There're lots places that have hard-coded config like `if system==darwin then build for Mac, else build linux kind of thing) But I'm new for their build system so I may miss something.

I just wanted to make sure the process I'm doing is in the right course.

kripken commented 8 years ago

@naan:

  1. Try the wasm32 target, not 64. JS does not work well with 64-bit, and so the asm.js target is 32-bit.
  2. I would actually not use the llvm-wasm backend. It is not ready yet, e.g. stack support is not yet complete, and it is not yet tested on large codebases - likely many bugs to get through before it is stable.
  3. Instead, code built with the wasm32 target should be almost identical to code built with the asm.js target. So in theory, you can take a wasm32 bitcode file, and let emscripten's asm.js backend compile it.
  4. Need to make sure while doing all of this that the files are built not just with the right triple, but the right headers. emcc does that automatically, by replacing normal system headers with emscripten's portable headers.
naan commented 8 years ago

@kripken Thank you for the followup.

So, #4 is sounds a bit challenging with their build system for me... I need to research more about the build system itself first, but it might be easier to use emcc toolchains to build swift-stdlibs if it automatically does header swap? I guess emscripten-fastcomp is still llvm 3.7.0 base, but once it gets synced with 3.8.0, I assume I can use that to build swift-stdlibs with actual asmjs target.

kripken commented 8 years ago

If it helps, emscripten's incoming branch is already at 3.9.0 (svn).

Danappelxx commented 8 years ago

Any updates on this?

gvkhna commented 8 years ago

+1 On Thu, Mar 3, 2016 at 6:51 PM Dan Appel notifications@github.com wrote:

Any updates on this?

— Reply to this email directly or view it on GitHub https://github.com/kripken/emscripten/issues/2427#issuecomment-192071228 .

Sent from my iPhone

joekim commented 8 years ago

+1

DorianListens commented 8 years ago

I'm also super interested to hear about progress here!

ephemer commented 8 years ago

@naan I was peripherally involved in the SwiftAndroid port and we had to do the same things there. Basically we change the sysroot to the one provided by the Android NDK - this makes the swift build tool look at the necessary headers and binaries from that point and not from the host machine's toolchain. I haven't used emscripten before in a real project, but I imagine it works in much the same way.

There are three open pull requests / discussions that will be instrumental in getting Swift working with Emscripten:

[RFC] Port to Android #1442 - check this out for changing the sysroot Enable cross-compiling #1398 - this allows us to compile e.g. OSX --> Linux, not sure if it's needed but good to know that it's being worked on. Flag to statically link Swift standard library #730 - the first issue that came up in this thread was that libSwiftCore is dynamically linked by default. Reliably building a statically linked version will be vital to continuing here.

I won't have time to work on this for at least another month or so, but I'm interested to keep track and answer any questions if I can in the meantime.

naan commented 8 years ago

@ephemer

Thank you for the info! That's really good to know. I have been in another project for a while and didn't have much time looking into this since last month. I hope I can make time for this soon.

ramki1979 commented 8 years ago

Flag to statically link Swift standard library #730 is resolved & waiting for review.

sparkagain commented 8 years ago

Hello All, I tried using clang 3.9.0 to emit the llvm ir with -target wasm32-wasm32-wasm32, but end up getting warning for different data layout and target triple. The question is if llvm WebAssembly target is usable as is to generate llvm ir to be fed to emscripten ?

kripken commented 8 years ago

The warning is expected, but should not cause problems in practice. The important things are the same in those two triples. (Except perhaps for the size of long doubles, is that important in Swift?)

jcampbell05 commented 8 years ago

https://github.com/apple/swift/pull/1442 - Android port has been merged.

sparkagain commented 8 years ago

@kripken Thanks a lot. I do not think its important in swift. On a second thought I think it would be great if emscripten can read in bc files (not generated with asm-unknown-emscripten) and emit bc files compatible with asm-unknown-emscripten. Can you please tell me is this currently supported or how difficult it would be to implement this. One challenge could be to undo the existing target assumptions of the input bc file.

kripken commented 8 years ago

It's not possible in general, as LLVM IR is not portable. When you build for e.g. 64-bit ARM, you are baking in things that simply cannot be undone later.

ephemer commented 8 years ago

@kripken do you have a slack channel or something similar for Emscripten discussions? A lot of the bits I referenced above are falling into place and I'd be interested in figuring out what else is missing from the picture. Specifically I'm interested with how the c++ stdlib is currently dealt with as a dependency (to compare with how swift's stdlib might fit in), and to what extent the swift runtime might get in the way.

I see a lot of potential for swift + webassembly + WebGL via Emscripten!

kripken commented 8 years ago

No slack, but there is an irc channel.

The C++ stdlib is basically just libc++ compiled in when needed. The only "magic" it requires is unwinding for C++ exceptions, which we have a few special functions for (using JS exceptions).

ephemer commented 8 years ago

@kripken Thanks for the link to the IRC channel.

The bit about 'compiled in' is what I don't really understand yet. Do users depend on a static libc++ that gets compiled into the LLVM IR before emscripten has anything to do with it? Or do they link against a dylib that is somehow later provided automatically by emscripten?

kripken commented 8 years ago

The former. We take user input files, find out what system libs they need like libc and libc++, then link all those into one big bitcode file and optimize that. Doing so can really help with code size. Then we compile that into asm.js.

For special functionality that can't be done in bitcode (which turns into compiled asm.js), like stack unwinding, we call out into JS. So we link in necessary "JS libraries" as well, after the process described above.

Emscripten does have support for dylibs, so in theory one could use libc++ that way, however it's significantly less efficient in terms of code size and startup speed. However dylibs are still useful in other situations.

ephemer commented 8 years ago

@kripken thanks for the info.

So from what I can see, swiftc will have to be compiled with knowledge of the relevant triple (you mentioned that wasm32-wasm32-wasm32 is probably the most reasonable choice here, because it's already in upstream llvm), and referencing the correct headers, much like in https://github.com/apple/swift/pull/1442.

As in the android port, we'd need a swift stdlib (libswiftCore) compiled based on the emscripten sysroot. The issue I can see here is the swift build system currently expects to build and use complete (not intermediate llvm-ir) libraries. Someone smarter than me should be able to figure their way around that though. I know there is an option to produce llvm-ir as well, just not sure about producing only that.

For Linux, https://github.com/apple/swift/pull/1891 has been merged, the last of three pull requests to support compiling swift with -static-stdlib (libswiftCore). That said, I gather it was possible to emit just the llvm-ir already, so maybe this isn't a huge step forward after all.

On the Mac side (or maybe generally, I haven't looked into it yet) we do have https://github.com/apple/swift/pull/1398, which seems to enable cross-compilation based on an arbitrary sysroot and target. This is exactly what we need, maybe limited by the 'full compilation' issue mentioned above. See specifically https://github.com/apple/swift/pull/1398/commits/a4df003038b454d486bcb5fa8ef061e5ba446908 - I don't have time to look into this right now but maybe someone who does could give this process a go, putting in wasm32-wasm32 as the target and pointing to the emscripten sysroot at ...emsdk_portable/emscripten/1.35.0/system. We'd also need to point cross-compile-toolchain-bin to emscripten's clang. Just maybe we're close enough already...