emscripten article

This commit is contained in:
Wouter Groeneveld 2021-04-21 15:13:52 +02:00
parent 329fa30753
commit fd72ff935f
4 changed files with 124 additions and 0 deletions

View File

@ -5,6 +5,7 @@ date: 2021-04-15
tags:
- java
- C++
- go
- javascript
categories:
- programming

View File

@ -0,0 +1,123 @@
---
title: Stop limiting yourself to JS in browsers
subtitle: Did you know that almost anything compiles to JavaScript?
date: 2021-04-21
tags:
- go
- C++
- javascript
categories:
- programming
---
While sniffling through various documentations, trying my best in understanding the ramifications of LLVM's license model compared to the older GCC toolchain, I dare to say that I actually found something interesting instead of falling asleep in-between the countless and needless pro or contra Richard Stallman debates. Here's what I found:
[Emscripten](https://emscripten.org/). Instead of calling it a day, let's take a closer look. Emscripten is a complete compiler toolchain that targets WebAssembly (and JavaScript), and is able to compile any language that uses LLVM - to work in browsers, Node.js, or wasm runtimes. Hold on, what where how? Okay, let's rephrase that. It acts as a drop-in replacement for gcc, just like clang does for GNU's gcc itself. Instead of outputting native code, it spews out a `wasm` file - wrapped in js or html if you prefer. That means it's possible to compile C and C++ code... To... JS?
## C++ and JavaScript, sitting in a tree...
Most of the times, when I stumble on GitHub projects of that scale, they're in pre-alpha state and end up going nowhere. Yet, Emscripten is surprisingly robust and easy to use. It _really is_ a "drop-in" replacement. Open your `Makefile`, locate `CC=g++`, replace it with `CC=em++`, and call it a day. It interprets popular gcc flags, just like clang does with GNU/gcc. For instance, adding optimization flag `-O3` results in a longer compile time, but the binary size was halved, and the execution speed increased. Even "native" threading support (`-lpthread`) or C++14 constructs (`-std=c++14`) work.
An example might do wonders. For a [system C course](https://kuleuven-diepenbeek.github.io/osc-course/) at our faculty, we introduce students to the concept of unit testing using Google Test. GTest is C++ code. You first compile the library yourself, and then statically link to its main executer. Your Makefile might end up looking like this:
```make
compile:
$(CC) -std=c++11 -O3 -I$(GTEST_DIR)/include -c cpp-testing-main.cpp
$(CC) -std=c++11 -O3 -I$(GTEST_DIR)/include -c cpp-testing.cpp
link:
$(CC) -std=c++11 -O3 cpp-testing-main.o cpp-testing.o $(GTEST_DIR)/build/lib/libgtest.a $(GTEST_DIR)/build/lib/libgtest_main.a -lpthread -o mytests
```
Building the `.a` files does not require a lot of work, as GTest comes with the needed CMake script files. Simply calling upon Emscripten's C++ compiler in the above Makefile will not be enough, as it will compile, but not link successfully. For that, we also need to compile GTest with Emscripten. Luckily, the toolchain provides convenient wrappers that automagically replaces the necessary things after CMake is done generating:
```bash
emcmake cmake ../
make
```
That was surprisingly simple. I did hold my breath. It was not needed. Now we can proceed to link the em-native libs. Using `-o mytests.html` creates a simple document that bootstraps the needed JS and WebAssembly. Suddenly, I find myself executing students' Google Tests in the browser:
![](../emscripten.jpg "Powered by Emscripten: running a cmd-line C++ program from within Firefox.")
But that's not all, the real fun starts once you take your time to look at the [Emscripten documentation](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#embind) and realize its potential. For instance, Embind makes it possible to expose C++ classes and functions to native JS code. C++. C interop is usually "easy enough", but C++ interop is notoriously difficult to get right thanks to the name mangling after assembling. This complicated thing works:
```C++
#include <string>
#include <vector>
#include <emscripten/bind.h>
class Itm {
private:
std::string itm;
public:
Itm(std::string i) : itm(i) {}
inline std::string unwrap() { return itm; }
};
class Wonky {
private:
std::vector<std::unique_ptr<Itm> > items;
public:
Wonky() {}
inline void add(std::string s) {
items.push_back(std::unique_ptr<Itm>(new Itm(s)));
}
inline std::string print() {
std::string res;
for(auto& itm : items) {
res = res + " " + itm->unwrap();
}
return res;
}
};
using namespace emscripten;
// Binding code
EMSCRIPTEN_BINDINGS(wonky) {
class_<Wonky>("Wonky")
.constructor<>()
.function("add", &Wonky::add)
.function("print", &Wonky::print);
}
// compile with em++ -std=c++11 --bind -o wonky.js wonky.cpp
```
I made up this silliness just to see what happens. Using the thing:
```html
<!doctype html>
<html>
<script>
var Module = {
onRuntimeInitialized: function() {
const wonky = new Module.Wonky();
wonky.add("good");
wonky.add("day!");
console.log(wonky.print()); // prints "good day!" in console.
wonky.delete();
}
};
</script>
<script src="wonky.js"></script>
</html>
```
There. That begs the question: why write C++ when you can, in fact, write JS? Good point, C++ should die a painful death (Have you taken a tiny peek at the [C++17/20 ISO standards](https://www.iso.org/standard/79358.html)? No? Keep it that way. It's horrible.) God, I miss Go. Hey, what about using Go in the browser? Check - have a "go" at [fiddling with GopherJS](https://gopherjs.github.io/playground/) over at the playground.
## [insert language here] and JS
I'm not really sure where I'm going with this, except that Emscripten makes me excited since it provides countless of language transition opportunities. Using Emscripten, you can simply compile the compiler, as these are usually written in C. There's [Lua.Space](http://lua.space/webdev/why-we-rewrote-lua-in-js), there's a [Python implementation](https://www.beuc.net/python-emscripten/python/dir?ci=tip), there's so many good stuff. Here's a [list of languages that compile to JS](https://github.com/jashkenas/coffeescript/wiki/List-of-languages-that-compile-to-JS). In fact, the list tells us that there are 20 different attempts at bringing Python code to the JS ecosystem! That brings us to the next problem: which project is still active, and which is not?
Of course, Emscripten is not the best solution when it comes to simplicity. I'm not sure if anyone Go is a very simple language, that is designed to be easily parsaeble, meaning it's fairly easy to generate basic JavaScript code. The hard part are Goroutines and other features, but even those are supported! No more promises, generators, etc. Just write plain Go - or Lua, as it comes with a similar concept called Coroutines - code.
A few other highlights, extracted from the GitHub list:
- [Opal](https://opalrb.com/) - Ruby 💛 JavaScript
- [Rusthon](https://github.com/rusthon/Rusthon) - mixes many multiple languages and transpilers inside Markdown files
- [Haxe](https://haxe.org/) - a high-level language that compiles to multiple targets. Used before the advent of GopherJS to [Compile to Go->Hax->C#](https://tardisgo.github.io/)
- [Nim](https://nim-lang.org/) - another language that has built-in JS output support
- [ClojureScript](https://clojurescript.org/) - a compiler for Clojure that targets JS
- [WebSharper](https://www.websharper.com/) - F#/C#-to-JavaScript: Full-stack web programming for .NET
- [Kotlin/JS](https://kotlinlang.org/docs/js-overview.html) - Kotlin's built-in JS support
And we didn't even touch static typing transpilers yet, such as TypeScript, Dart, Elm, Roy, PureScript, ...
Next time you plan on writing a module aimed at the browser or your Node server, consider this: if you don't want to, you don't _have_ to stick with JavaScript...

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB