*This is part 3 in a series on running the [Clean][] compiler in [WebAssembly][], with the proof of concept in the [Clean Sandbox][]. In this part I discuss how all the individual components are integrated. See the [introduction][] for a high-level overview. In the previous part, I discussed [the compilation pipeline][pipeline].* [[toc]] # Overview We have to put all our different elements together in JavaScript. In particular we have the following elements: - The C tools (compiler backend, bytecode generator, bytecode linker, and bytecode prelinker) - The WebAssembly interpreters in which the Clean tools run (compiler frontend, `make`-like program) - The WebAssembly interpreter for the compiled program itself. # Linking the compiler frontend and backend We have already seen how [the `make` tool communicates with the C tools and with the compiler][pipeline]. It is a little trickier to set up the communication between the compiler frontend and the backend. This is because the `make` tool was written for this application specifically, while the compiler wasn't. In Clean, the foreign function interface works through a special ABC instruction, `ccall`. For example, if we have a C function `int c_add(int, int)`, we can define `add` below. `II:I` here stands for the type: two integer arguments and an integer return type. ```clean add :: !Int !Int -> Int add _ _ = code { ccall c_add "II:I" } ``` This instruction is not supported by the interpreter, because there is no way to implement it generically in that context. When interpretation reaches a `ccall` (or any other unimplemented instruction), the WebAssembly interpreter calls a JavaScript function that can try to handle it. This function can read and modify the state of the ABC machine, and return to it after it has handled the instruction. This allows us to link the compiler frontend to the backend in JavaScript: ```js var clean_compiler; function create_clean_compiler () { return ABCInterpreter.instantiate({ interpreter_imports: { handle_illegal_instr: (pc, instr, asp, bsp, csp, hp, hp_free) => { instr = ABCInterpreter.instructions[instr]; if (instr == 'ccall') return i_ccall.bind(c_compiler)(clean_compiler, pc, asp, bsp); else return 0; }, /* other options .. */ } }).then(abc => { clean_compiler = abc; }); } ``` With `i_ccall` we can link a WebAssembly interpreter to an Emscripten module, which contains the global C functions: ```js function i_ccall (abc, pc, asp, bsp) { const fun = '_' + abc.get_clean_string(abc.memory_array[pc/4+2]-8); /* e.g. c_add; emscripten adds an underscore */ var type = abc.get_clean_string (abc.memory_array[pc/4+4]-8); /* e.g. II:I */ if (!(fun in this)) throw 'ccall: unknown function '+fun; /* parse type; get arguments from the Clean heap and stack .. */ const result = this[fun].apply(null, args); /* copy the result to the WebAssembly interpreter .. */ return pc+24; /* return the address of the next instruction */ } ``` # Clean file I/O We have a similar problem with Clean programs doing file I/O. The compiler frontend uses file I/O, but for this it needs ABC instructions that are not implemented in the WebAssembly interpreter. We extend `handle_illegal_instr` to also catch these instructions, and use Emscripten's `FS` library to implement them. For example, `writeFS` writes a string to a file. `handle_illegal_instr` becomes: ```js handle_illegal_instr: (pc, instr, asp, bsp, csp, hp, hp_free) => { instr = ABCInterpreter.instructions[instr]; if (instr == 'ccall') return i_ccall.bind(c_compiler)(clean_compiler, pc, asp, bsp); else if (instr == 'writeFS') return i_writeFS.bind(c_compiler)(clean_compiler, pc, asp, bsp); else return 0; }, ``` And again `i_witeFS` is defined to link an arbitrary WebAssembly interpreter and Emscripten module together: ```js function i_writeFS (abc, pc, asp, bsp) { const i = abc.memory_array[bsp/4+2]; /* get arguments from the stack */ const s_ptr = abc.memory_array[asp/4]; const size = abc.memory_array[s_ptr/4+2]; const s = new Uint8Array(abc.memory_array.buffer, s_ptr+16, size); this.FS.write(abc.files[i].stream, s, 0, size); abc.interpreter.instance.exports.set_asp(asp-8); /* pop the string from the stack */ return pc+8; /* return the address of the next instruction */ } ``` In reality we need about 10 instructions for file I/O, but the others are similar. # Running the generated bytecode We can now run a Clean program from our editor through the [compilation pipeline][pipeline] and generate a prelinked bytecode file. This is similar to a native executable, but for the WebAssembly interpreter. What remains is actually running the bytecode. We do this by simply creating a new instance of the ABC interpreter. The `instantiate` function uses `fetch()` to get supporting WebAssembly and bytecode files. In this case, our bytecode comes from the local file system, so we monkey-patch `fetch()` to supply the bytecode from the Emscripten file system (the case that the path is `null`): ```js function run (path) { const opts = { fetch: (p) => { return p !== null ? fetch(p) : new Promise(resolve => { const pbc = c_bcprelink.FS.readFile(path); resolve({ ok: true, arrayBuffer: () => pbc.buffer }); }); }, }; return ABCInterpreter.instantiate(opts).then(abc => { abc.interpreter.instance.exports.set_pc(abc.start); var r = 0; try { r = abc.interpreter.instance.exports.interpret(); } catch (e) { sandbox.stderr(e+'\n'); r = -1; } /* flush output buffer */ if (sandbox.stdout_buffer.length>0) sandbox.stdout('\n'); if (r!=0) sandbox.stderr('failed with return code '+r); }); } ``` # Wrapping up That's it! There are many other small tricks here and there, but these four posts covered the architecture and most interesting implementation details. You can try out the [Clean Sandbox][] to see it live, or check out the [source code](https://gitlab.com/camilstaps/clean-sandbox). [Clean]: http://clean.cs.ru.nl/ [Clean Sandbox]: https://camilstaps.gitlab.io/clean-sandbox/ [WebAssembly]: https://webassembly.org/ [introduction]: 2021-06-23-compiling-clean-in-the-browser-with-webassembly-part-1-introduction.html [pipeline]: 2021-06-23-compiling-clean-in-the-browser-with-webassembly-part-2-the-pipeline.html [integration]: 2021-06-23-compiling-clean-in-the-browser-with-webassembly-part-3-putting-it-all-together.html