Add wasm to wat backend#4
Conversation
|
Doubts:
|
|
Excellent, great job! Let's brainstorm if this can be simplified. I think WASM is a file format, just like ELF or Mach-O. So you read the sections and you store them in some internal data structure, as you do. All the data structures are really simple, such as Then in WASM->Mach-O/arm, backend, I simply implement a different writer. |
|
To be specific, this code: case 0x10: { // call func
uint32_t index = read_unsinged_num(offset);
codes[i].instructions.push_back(
std::make_unique<CallInst>(cur_byte, index));
break;
}Is very slow, because case 0x10: { // call func
uint32_t index = read_unsinged_num(offset);
self().visit_CallInst(cur_byte, index);
break;
}You can still create a subclass visitor where you implement void visit_CallInst(uint32_t cur_byte, uint32_t index) {
codes[i].instructions.push_back(
std::make_unique<CallInst>(cur_byte, index));
}If you wanted to create an intermediate representation. But you can also just simply print out WAT right away in the visitor. With the CRTP pattern, there is no overhead of this approach. |
doubt: do we need to implement the visitor just for
doubt: do we need to write
|
|
Only for Instruction. No asdl. What I am imagining is that you load So the WASM->WAT would create the function signature, as it does now, using the I think this design is equivalent to the Mach-O (and ELF) reader/writer: you have to read the format, you load all the metadata in memory (Mach-O has info about the sections, symbols etc.; WASM has info about |
|
I tried using a visitor like pattern, although I am not sure if this is the way I was supposed to do. Please possibly review and please share feedback. If in case, this code differs majorly from what actually we needed, please do let me know. |
|
Rather than: void visit_ControlInstruction(std::string &result, std::string &indent,
uint32_t &offset) {
uint8_t cur_byte = wasm_bytes[offset++];
switch (cur_byte) {
case 0x0F: { // return
result += indent + "return";
break;
}
case 0x10: { // call function
uint32_t func_index = read_unsinged_num(offset);
result += indent + "call " + std::to_string(func_index);
break;
}
default: {
std::cout << "Control Instruction (" << std::hex << cur_byte
<< std::dec;
std::cout << ") Not yet supported" << std::endl;
break;
}
}
return;
}I would have: class WATVisitor : public ASR::BaseWASMVisitor<WATVisitor>
{
std::string src;
...
void visit_Return() {
src += indent + "return";
}
void visit_FunctionCall(uint32_t func_index) {
src += indent + "call " + std::to_string(func_index);
}
void visit_LocalSet(uint32_t local_index) {
src += indent + "local.set " + std::to_string(local_index);
}
} |
|
The decored of just the instructions (everything else is loaded ahead of time) would at the core contain just: template <class Derived>
class BaseWASMVisitor
{
private:
Derived& self() { return static_cast<Derived&>(*this); }
public:
void decode_function_instructions(uint32_t offset, Vec<uint8_t> wasm_bytes) {
uint8_t cur_byte = wasm_bytes[offset++];
while (cur_byte != 0x0B) {
switch (cur_byte) {
...
case 0x21: { // local.set
uint32_t index = read_unsinged_num(offset);
self().visit_LocalSet(index);
break;
}
case 0x6A: { // i32.add
self().visit_I32Add();
break;
}
case 0x6B: { // i32.sub
self().visit_I32Sub();
break;
}
case 0x6C: { // i32.mul
self().visit_I32Mul();
break;
}
case 0x6D: { // i32.div_s
self().visit_I32Div();
break;
}
case 0x10: { // call func
uint32_t index = read_unsinged_num(offset);
self().visit_FunctionCall(index);
break;
}
case 0x0F:{
self().visit_Return();
break;
}
default: {
std::cout << "Error: Instruction " << std::to_string(cur_byte)
<< " not supported" << std::endl;
exit(1);
}
}
}
void visit_Return() { throw LFortran::LFortranException("visit_Return() not implemented"); }
void visit_FunctionCall(uint32_t /*func_index*/) { throw LFortran::LFortranException("visit_FunctionCall() not implemented"); }
}; |
wasm_to_wat.cpp
Outdated
| result += ")"; | ||
|
|
||
| std::string inst_indent = "\n "; | ||
| visit_Instructions(result, inst_indent, codes[i].insts_start_index); |
There was a problem hiding this comment.
| visit_Instructions(result, inst_indent, codes[i].insts_start_index); | |
| { | |
| v = WASMVisitor(); | |
| v.decode_function_instructions(codes[i].insts_start_index, wasm_bytes); | |
| result += v.src; | |
| } |
|
I don't think we need to, but if we ever wanted an intermediate representation (IR), we can do it like this, by creating a separate visitor: class IRVisitor : public ASR::BaseWASMVisitor<IRVisitor>
{
std::string src;
...
void visit_Return() {
codes.push_back(ReturnInst());
}
void visit_FunctionCall(uint32_t func_index) {
codes.push_back(FunctionCall(index))
}
void visit_LocalSet(uint32_t local_index) {
codes.push_back(LocalSet(index))
}
} |
|
Most of the code in the commit Define BaseWASMVisitor Class and WATVisitor Child Class has been added using this script. If there are any repetitive changes the same script, hopefully, can be utilized to regenerate the needed/necessary code. Note: after using the script, we need to update table index names as: Mark table indices as src and des in visit_TableCopy(). Please possibly review and please possibly share feedback. |
|
Awesome, this looks good. Now, why don't we extract Let's put the Python script in, make it reasonably "maintainable", and we can run it as part of |
Got it. On it. |
|
Few instructions in wasm_instructions.txt are commented out (starting with Also, I collected out necessary variables/struct/functions in a new file called wasm_utils.h. Please possibly review and please possibly share feedback. |
|
I think this looks excellent! I think we can probably merge it as is. |
|
I added you in, so you should be able to merge it. |
|
How did you create the |
By (slightly careful) copy-pasting instructions from pages 136-147 of https://webassembly.github.io/spec/core/_download/WebAssembly.pdf. After that the parameters were modified to possibly add their types. This is mostly done with |
Yes, I received an invitation over mail. I accepted it. Thank you so much for the access. |
|
Excellent, very cool. Thanks for creating the file, that's a great simple reference and we generate things from it. |
This adds a
wasm_to_watbackend. As this is the first version, it might not be the best/optimal in terms of performance or code size.Notes:
std::vectorinstead of our customVec.lfortran wasmbackend.WAT_DEBUGand aDEUBUGmacros provided at the top. They were useful for me to to debug the decoding ofwasm. In the final version (that we will add toLFortran), probably we may/should remove them.wat_test.f90andtest2.wasmfiles are provided. Detailed steps for testing/executing thewasm_to_watare below.Steps to test/execute the
wasm_to_watbackendLFortranto generate thewasmfile forwat_test.f90. We can use the following command for the same (assuming that we havelfortranbinary in/home/user/binand/home/user/binis included in thePATHvariable. (more details here))a.outtotest2.wasm.wasm_to_wat.cppa.outOutput:
(module (func $0 (param i32) (result i32) (local i32) local.get 0 local.get 0 i32.mul local.set 1 local.get 1 return ) (func $1 (param i32 i32) (result i32) (local i32) local.get 0 local.get 1 i32.add local.set 2 local.get 2 return ) (func $2 (param i32) (result i32) (local i32 i32) i32.const 3 local.set 2 local.get 2 local.get 0 call 0 i32.mul local.set 1 local.get 1 return ) (func $3 (param i32 i32) (result i32) (local i32) local.get 0 local.get 1 i32.add local.set 2 local.get 2 return ) (export "a_sqr" (func $0)) (export "add" (func $1)) (export "computecirclearea" (func $2)) (export "my_add" (func $3)) )For testing the
watprinted on the console:watin theWATbox on https://webassembly.github.io/wabt/demo/wat2wasm/JSbox to export and test our functions that were defined inwat_test.f90JS LOGbox and is as follows3 3 16 75 Success!Please possibly review and please share feedback.