1 module tagion.wasm.WasmWat;
2 
3 import std.conv : to;
4 import std.format;
5 import std.range : StoppingPolicy, enumerate, lockstep;
6 import std.range.primitives : isOutputRange;
7 import std.stdio;
8 import std.traits : ConstOf, EnumMembers, ForeachType, PointerTarget;
9 import std.typecons : Tuple;
10 import std.uni : toLower;
11 import tagion.basic.tagionexceptions;
12 import tagion.wasm.WasmBase;
13 import tagion.wasm.WasmException;
14 import tagion.wasm.WasmReader;
15 
16 @safe class WatException : WasmException {
17     this(string msg, string file = __FILE__, size_t line = __LINE__) pure nothrow {
18         super(msg, file, line);
19     }
20 }
21 
22 alias check = Check!WatException;
23 
24 @safe WatT!(Output) wat(Output)(WasmReader wasmreader, Output output) {
25     return new WatT!(Output)(wasmreader, output);
26 }
27 
28 @safe class WatT(Output) : WasmReader.InterfaceModule {
29     alias Sections = WasmReader.Sections;
30     //alias ExprRange=WasmReader.WasmRange.WasmSection.ExprRange;
31     //alias WasmArg=WasmReader.WasmRange.WasmSection.WasmArg;
32     alias ImportType = WasmReader.WasmRange.WasmSection.ImportType;
33     alias Limit = WasmReader.Limit;
34     alias GlobalDesc = WasmReader.WasmRange.WasmSection.ImportType.ImportDesc.GlobalDesc;
35 
36     protected {
37         Output output;
38         WasmReader wasmstream;
39         string indent;
40         string spacer;
41     }
42 
43     this(WasmReader wasmstream, Output output, string spacer = "  ") {
44         this.output = output;
45         this.wasmstream = wasmstream;
46         this.spacer = spacer;
47     }
48 
49     static string limitToString(ref const Limit limit) {
50         immutable to_range = (limit.lim is Limits.INFINITE) ? "" : format(" %d", limit.to);
51         return format("%d%s", limit.from, to_range);
52     }
53 
54     static string globalToString(ref const GlobalDesc globaldesc) {
55         with (Mutable) {
56             final switch (globaldesc.mut) {
57             case CONST:
58                 return format("%s", typesName(globaldesc.type));
59             case VAR:
60                 return format("(mut %s)", typesName(globaldesc.type));
61             }
62         }
63         assert(0);
64     }
65 
66     static string offsetAlignToString(const(WasmArg[]) wargs)
67     in {
68         assert(wargs.length == 2);
69     }
70     do {
71         string result;
72         const _offset = wargs[1].get!uint;
73         const _align = wargs[0].get!uint;
74 
75         if (_offset > 0) {
76             result ~= format(" offset=%d", _offset);
77         }
78         if (_align > 0) {
79             result ~= format(" align=%d", _align);
80         }
81         return result;
82     }
83 
84     alias Custom = Sections[Section.CUSTOM];
85     void custom_sec(ref scope const(Custom) _custom) {
86         output.writef(`%s(custom "%s" `, indent, _custom.name);
87         enum {
88             SPACE = 32,
89             DEL = 127
90         }
91         import tagion.hibon.Document;
92         import tagion.hibon.HiBONJSON;
93         import LEB128 = tagion.utils.LEB128;
94         import std.algorithm;
95 
96         if (_custom.doc.isInorder) {
97             output.writefln("\n%s", _custom.doc.toPretty);
98             output.writefln(`)`);
99         }
100         else {
101             output.write(`"`);
102             foreach (d; _custom.bytes) {
103                 if ((d > SPACE) && (d < DEL)) {
104                     output.writef(`%c`, char(d));
105                 }
106                 else {
107                     output.writef(`\x%02X`, d);
108                 }
109             }
110             output.writefln(`")`);
111         }
112     }
113 
114     alias Type = Sections[Section.TYPE];
115     void type_sec(ref const(Type) _type) {
116         //        auto _type=*mod[Section.TYPE]; //type_sec;
117         foreach (i, t; _type[].enumerate) {
118             output.writef("%s(type (;%d;) (%s", indent, i, typesName(t.type));
119             if (t.params.length) {
120                 output.write(" (param");
121                 foreach (p; t.params) {
122                     output.writef(" %s", typesName(p));
123                 }
124                 output.write(")");
125             }
126             if (t.results.length) {
127                 output.write(" (result");
128                 foreach (r; t.results) {
129                     output.writef(" %s", typesName(r));
130                 }
131                 output.write(")");
132             }
133             output.writeln("))");
134         }
135     }
136 
137     alias Import = Sections[Section.IMPORT];
138     void import_sec(ref const(Import) _import) {
139         //        auto _import=*mod[Section.IMPORT];//.import_sec;
140         static string importdesc(ref const ImportType imp, const size_t index) {
141             const desc = imp.importdesc.desc;
142             with (IndexType) {
143                 final switch (desc) {
144                 case FUNC:
145                     const _funcdesc = imp.importdesc.get!FUNC;
146                     return format("(%s (;%d;) (func %d))", indexName(desc),
147                             index, _funcdesc.funcidx);
148                 case TABLE:
149                     const _tabledesc = imp.importdesc.get!TABLE;
150                     return format("(%s (;%d;) %s %s)", indexName(desc), index,
151                             limitToString(_tabledesc.limit), typesName(_tabledesc.type));
152                 case MEMORY:
153                     const _memorydesc = imp.importdesc.get!MEMORY;
154                     return format("(%s(;%d;)  %s %s)", indexName(desc), index,
155                             limitToString(_memorydesc.limit));
156                 case GLOBAL:
157                     const _globaldesc = imp.importdesc.get!GLOBAL;
158                     return format("(%s (;%d;) %s)", indexName(desc), index,
159                             globalToString(_globaldesc));
160                 }
161             }
162         }
163 
164         foreach (i, imp; _import[].enumerate) {
165             //            output.writefln("imp=%s", imp);
166             output.writefln(`%s(import "%s" "%s" %s)`, indent, imp.mod,
167                     imp.name, importdesc(imp, i));
168         }
169     }
170 
171     alias Function = Sections[Section.FUNCTION];
172     protected Function _function;
173     @trusted void function_sec(ref const(Function) _function) {
174         // Empty
175         // The functions headers are printed in the code section
176         this._function = cast(Function) _function;
177     }
178 
179     alias Table = Sections[Section.TABLE];
180     void table_sec(ref const(Table) _table) {
181         //        auto _table=*mod[Section.TABLE];
182         foreach (i, t; _table[].enumerate) {
183             output.writefln("%s(table (;%d;) %s %s)", indent, i,
184                     limitToString(t.limit), typesName(t.type));
185         }
186     }
187 
188     alias Memory = Sections[Section.MEMORY];
189     void memory_sec(ref const(Memory) _memory) {
190         //        auto _memory=*mod[Section.MEMORY];
191         foreach (i, m; _memory[].enumerate) {
192             output.writefln("%s(memory (;%d;) %s)", indent, i, limitToString(m.limit));
193         }
194     }
195 
196     alias Global = Sections[Section.GLOBAL];
197     void global_sec(ref const(Global) _global) {
198         //        auto _global=*mod[Section.GLOBAL];
199         foreach (i, g; _global[].enumerate) {
200             output.writefln("%s(global (;%d;) %s (", indent, i, globalToString(g.global));
201             auto expr = g[];
202             block(expr, indent ~ spacer);
203             output.writefln("%s))", indent);
204         }
205     }
206 
207     alias Export = Sections[Section.EXPORT];
208     void export_sec(ref const(Export) _export) {
209         //        auto _export=*mod[Section.EXPORT];
210         foreach (exp; _export[]) {
211             output.writefln(`%s(export "%s" (%s %d))`, indent, exp.name,
212                     indexName(exp.desc), exp.idx);
213         }
214 
215     }
216 
217     alias Start = Sections[Section.START];
218     void start_sec(ref const(Start) _start) {
219         output.writefln("%s(start %d),", indent, _start.idx);
220     }
221 
222     alias Element = Sections[Section.ELEMENT];
223     void element_sec(ref const(Element) _element) {
224         //        auto _element=*mod[Section.ELEMENT];
225         foreach (i, e; _element[].enumerate) {
226             output.writefln("%s(elem (;%d;) (", indent, i);
227             auto expr = e[];
228             const local_indent = indent ~ spacer;
229             block(expr, local_indent ~ spacer);
230             output.writef("%s) func", local_indent);
231             foreach (f; e.funcs) {
232                 output.writef(" %d", f);
233             }
234             output.writeln(")");
235         }
236     }
237 
238     alias Code = Sections[Section.CODE];
239     @trusted void code_sec(ref const(Code) _code) {
240         check(_function !is null, "Fuction section missing");
241         check(_code !is null, "Code section missing");
242         foreach (f, c; lockstep(_function[], _code[], StoppingPolicy.requireSameLength)) {
243             auto expr = c[];
244             output.writefln("%s(func (type %d)", indent, f.idx);
245             const local_indent = indent ~ spacer;
246             if (!c.locals.empty) {
247                 output.writef("%s(local", local_indent);
248                 foreach (l; c.locals) {
249                     foreach (i; 0 .. l.count) {
250                         output.writef(" %s", typesName(l.type));
251                     }
252                 }
253                 output.writeln(")");
254             }
255 
256             block(expr, local_indent);
257             output.writefln("%s)", indent);
258         }
259     }
260 
261     alias Data = Sections[Section.DATA];
262     void data_sec(ref const(Data) _data) {
263         //        auto _data=*mod[Section.DATA];
264         foreach (d; _data[]) {
265             output.writefln("%s(data (", indent);
266             auto expr = d[];
267             const local_indent = indent ~ spacer;
268             block(expr, local_indent ~ spacer);
269             output.writefln(`%s) "%s")`, local_indent, d.base);
270         }
271     }
272 
273     private const(ExprRange.IRElement) block(ref ExprRange expr,
274             const(string) indent, const uint level = 0) {
275         //        immutable indent=base_indent~spacer;
276         string block_comment;
277         uint block_count;
278         uint count;
279         static string block_result_type()(const Types t) {
280             with (Types) {
281                 switch (t) {
282                 case I32, I64, F32, F64, FUNCREF:
283                     return format(" (result %s)", typesName(t));
284                 case EMPTY:
285                     return null;
286                 default:
287                     check(0, format("Block Illegal result type %s for a block", t));
288                 }
289             }
290             assert(0);
291         }
292 
293         while (!expr.empty) {
294             const elm = expr.front;
295             const instr = instrTable.get(elm.code, illegalInstr);
296             expr.popFront;
297             with (IRType) {
298                 final switch (instr.irtype) {
299                 case CODE:
300                     output.writefln("%s%s", indent, instr.name);
301                     break;
302                 case PREFIX:
303                     output.writefln("%s%s", indent, instr.name);
304                     break;
305                 case BLOCK:
306                     block_comment = format(";; block %d", block_count);
307                     block_count++;
308                     output.writefln("%s%s%s %s", indent, instr.name,
309                             block_result_type(elm.types[0]), block_comment);
310                     const end_elm = block(expr, indent ~ spacer, level + 1);
311                     const end_instr = instrTable[end_elm.code];
312                     output.writefln("%s%s", indent, end_instr.name);
313                     //return end_elm;
314 
315                     // const end_elm=block_elm(elm);
316                     if (end_elm.code is IR.ELSE) {
317                         const endif_elm = block(expr, indent ~ spacer, level + 1);
318                         const endif_instr = instrTable[endif_elm.code];
319                         output.writefln("%s%s %s count=%d", indent,
320                                 endif_instr.name, block_comment, count);
321                     }
322                     break;
323                 case BRANCH:
324                 case BRANCH_IF:
325                     output.writefln("%s%s %s", indent, instr.name, elm.warg.get!uint);
326                     break;
327                 case BRANCH_TABLE:
328                     static string branch_table(const(WasmArg[]) args) {
329                         string result;
330                         foreach (a; args) {
331                             result ~= format(" %d", a.get!uint);
332                         }
333                         return result;
334                     }
335 
336                     output.writefln("%s%s %s", indent, instr.name, branch_table(elm.wargs));
337                     break;
338                 case CALL:
339                     output.writefln("%s%s %s", indent, instr.name, elm.warg.get!uint);
340                     break;
341                 case CALL_INDIRECT:
342                     output.writefln("%s%s (type %d)", indent, instr.name, elm.warg.get!uint);
343                     break;
344                 case LOCAL:
345                     output.writefln("%s%s %d", indent, instr.name, elm.warg.get!uint);
346                     break;
347                 case GLOBAL:
348                     output.writefln("%s%s %d", indent, instr.name, elm.warg.get!uint);
349                     break;
350                 case MEMORY:
351                     output.writefln("%s%s%s", indent, instr.name, offsetAlignToString(elm.wargs));
352                     break;
353                 case MEMOP:
354                     output.writefln("%s%s", indent, instr.name);
355                     break;
356                 case CONST:
357                     static string toText(const WasmArg a) {
358                         with (Types) {
359                             switch (a.type) {
360                             case I32:
361                                 return a.get!int
362                                     .to!string;
363                             case I64:
364                                 return a.get!long
365                                     .to!string;
366                             case F32:
367                                 const x = a.get!float;
368                                 return format("%a (;=%s;)", x, x);
369                             case F64:
370                                 const x = a.get!double;
371                                 return format("%a (;=%s;)", x, x);
372                             default:
373                                 assert(0);
374                             }
375                         }
376                         assert(0);
377                     }
378 
379                     output.writefln("%s%s %s", indent, instr.name, toText(elm.warg));
380                     break;
381                 case END:
382                     return elm;
383                 case ILLEGAL:
384                     throw new WatException(format("Illegal instruction %02X", elm.code));
385                 case SYMBOL:
386                     assert(0, "Symbol opcode and it does not have an equivalent opcode");
387                 }
388             }
389         }
390         return ExprRange.IRElement(IR.END, level);
391     }
392 
393     Output serialize() {
394         output.writeln("(module");
395         indent = spacer;
396         scope (exit) {
397             output.writeln(")");
398         }
399         wasmstream(this);
400         return output;
401     }
402 }
403 
404 version (none) unittest {
405     import std.exception : assumeUnique;
406     import std.file;
407     import std.stdio;
408 
409     //      import std.file : fread=read, fwrite=write;
410 
411     @trusted static immutable(ubyte[]) fread(R)(R name, size_t upTo = size_t.max) {
412         import std.file : _read = read;
413 
414         auto data = cast(ubyte[]) _read(name, upTo);
415         // writefln("read data=%s", data);
416         return assumeUnique(data);
417     }
418 
419     //    string filename="../tests/wasm/func_1.wasm";
420     //    string filename="../tests/wasm/global_1.wasm";
421     //    string filename="../tests/wasm/imports_1.wasm";
422     //    string filename="../tests/wasm/table_copy_2.wasm";
423     //    string filename="../tests/wasm/memory_2.wasm";
424     //    string filename="../tests/wasm/start_4.wasm";
425     //    string filename="../tests/wasm/address_1.wasm";
426     string filename = "../tests/wasm/data_4.wasm";
427     immutable code = fread(filename);
428     auto wasm = WasmReader(code);
429     //    auto dasm=Wdisasm(wasm);
430     Wat(wasm, stdout).serialize();
431     //    auto output=Wat
432 
433 }