1 module tagion.wasm.WasmBetterC;
2 
3 import std.algorithm;
4 import std.array;
5 import std.array;
6 import std.conv : to;
7 import std.format;
8 import std.range;
9 import std.range.primitives : isOutputRange;
10 import std.stdio;
11 import std.traits : ConstOf, EnumMembers, ForeachType, PointerTarget;
12 import std.typecons : Tuple;
13 import std.typecons;
14 import std.uni : toLower;
15 import tagion.basic.tagionexceptions;
16 import tagion.hibon.Document;
17 import tagion.wasm.WasmBase;
18 import tagion.wasm.WasmException;
19 import tagion.wasm.WasmReader;
20 import tagion.wasm.WastAssert;
21 
22 @safe class WasmBetterCException : WasmException {
23     this(string msg, string file = __FILE__, size_t line = __LINE__) pure nothrow {
24         super(msg, file, line);
25     }
26 }
27 
28 alias check = Check!WasmBetterCException;
29 
30 @safe WasmBetterC!(Output) wasmBetterC(Output)(WasmReader wasmreader, Output output) {
31     return new WasmBetterC!(Output)(wasmreader, output);
32 }
33 
34 @safe class WasmBetterC(Output) : WasmReader.InterfaceModule {
35     alias Sections = WasmReader.Sections;
36     //alias ExprRange=WasmReader.WasmRange.WasmSection.ExprRange;
37     //alias WasmArg=WasmReader.WasmRange.WasmSection.WasmArg;
38     alias ImportType = WasmReader.WasmRange.WasmSection.ImportType;
39     alias ExportType = WasmReader.WasmRange.WasmSection.ExportType;
40     alias FuncType = WasmReader.WasmRange.WasmSection.FuncType;
41     alias TypeIndex = WasmReader.WasmRange.WasmSection.TypeIndex;
42     alias CodeType = WasmReader.WasmRange.WasmSection.CodeType;
43 
44     alias Limit = WasmReader.Limit;
45     alias GlobalDesc = WasmReader.WasmRange.WasmSection.ImportType.ImportDesc.GlobalDesc;
46 
47     protected {
48         Output output;
49         WasmReader wasmstream;
50         string indent;
51         string spacer;
52     }
53 
54     string module_name;
55     string[] imports;
56     string[] attributes;
57     this(WasmReader wasmstream, Output output, string spacer = "    ") {
58         this.output = output;
59         this.wasmstream = wasmstream;
60         this.spacer = spacer;
61     }
62 
63     static string limitToString(ref const Limit limit) {
64         immutable to_range = (limit.lim is Limits.INFINITE) ? "" : format(" %d", limit.to);
65         return format("%d%s", limit.from, to_range);
66     }
67 
68     static string globalToString(ref const GlobalDesc globaldesc) {
69         with (Mutable) {
70             final switch (globaldesc.mut) {
71             case CONST:
72                 return format("%s", typesName(globaldesc.type));
73             case VAR:
74                 return format("(mut %s)", typesName(globaldesc.type));
75             }
76         }
77         assert(0);
78     }
79 
80     static string offsetAlignToString(const(WasmArg[]) wargs)
81     in {
82         assert(wargs.length == 2);
83     }
84     do {
85         string result;
86         const _offset = wargs[1].get!uint;
87         const _align = wargs[0].get!uint;
88 
89         if (_offset > 0) {
90             result ~= format(" offset=%d", _offset);
91         }
92         if (_align > 0) {
93             result ~= format(" align=%d", _align);
94         }
95         return result;
96     }
97 
98     void produceAsserts(const Document doc, const string indent) {
99         const sec_assert = SectionAssert(doc);
100         void innerAssert(const Assert _assert, const string indent) {
101             Context ctx;
102             auto code_type = CodeType(_assert.invoke);
103             auto invoke_expr = code_type[];
104             output.writefln("%s// expr   %(%02X %)", indent, _assert.invoke);
105             output.writefln("%s// result %(%02X %)", indent, _assert.result);
106             if (_assert.method is Assert.Method.Trap) {
107                 void assert_block(const string _indent) {
108                     block(invoke_expr, ctx, _indent ~ spacer);
109                     output.writefln("%s}", _indent);
110                 }
111 
112                 output.writefln("%swasm.assert_trap((() {", indent);
113                 assert_block(indent ~ spacer);
114                 output.writefln("%s)());", indent);
115 
116             }
117             else if (_assert.result.length != 0) {
118                 block(invoke_expr, ctx, indent, true);
119                 auto result_type = CodeType(_assert.result);
120                 auto result_expr = result_type[];
121                 block(result_expr, ctx, indent, true);
122                 output.writef("%sassert(%s == %s", indent, ctx.pop, ctx.pop);
123                 if (_assert.message.length) {
124                     output.writef(`, "%s"`, _assert.message);
125                 }
126                 output.writeln(");");
127             }
128         }
129 
130         foreach (_assert; sec_assert.asserts) {
131             output.writefln("%s{ // %s", indent, _assert.name);
132             innerAssert(_assert, indent ~ spacer);
133             output.writefln("%s}", indent);
134 
135         }
136     }
137 
138     enum max_linewidth = 120;
139     alias Custom = Sections[Section.CUSTOM];
140     void custom_sec(ref scope const(Custom) _custom) {
141         import tagion.hibon.HiBONJSON;
142 
143         //output.writef(`%s(custom "%s" "`, indent, _custom.name);
144         enum {
145             SPACE = 32,
146             DEL = 127
147         }
148         if (_custom.doc.isInorder) {
149             switch (_custom.name) {
150             case "assert":
151                 output.writeln("@safe");
152                 output.writefln("%sunittest { // %s", indent, _custom.name);
153                 produceAsserts(_custom.doc, indent ~ spacer);
154                 output.writefln("%s}", indent);
155                 break;
156             default:
157                 output.writefln("/* %s", _custom.name);
158                 output.writefln("%s", _custom.doc.toPretty);
159                 output.writeln("*/");
160             }
161         }
162         else {
163             uint linewidth;
164             output.writefln(`/* Custom "%s"`, _custom.name);
165             foreach (d; _custom.bytes) {
166                 if ((d > SPACE) && (d < DEL)) {
167                     output.writef(`%c`, char(d));
168                     linewidth += 1;
169                 }
170                 else {
171                     output.writef(`\x%02X`, d);
172                     linewidth += 3;
173                 }
174                 if (linewidth >= max_linewidth) {
175                     output.writeln;
176                     linewidth = 0;
177                 }
178             }
179             output.writeln(`*/`);
180         }
181     }
182 
183     alias Type = Sections[Section.TYPE];
184     void type_sec(ref const(Type) _type) {
185         version (none)
186             foreach (i, t; _type[].enumerate) {
187             output.writef("%s(type (;%d;) (%s", indent, i, typesName(t.type));
188             if (t.params.length) {
189                 output.write(" (param");
190                 foreach (p; t.params) {
191                     output.writef(" %s", typesName(p));
192                 }
193                 output.write(")");
194             }
195             if (t.results.length) {
196                 output.write(" (result");
197                 foreach (r; t.results) {
198                     output.writef(" %s", typesName(r));
199                 }
200                 output.write(")");
201             }
202             output.writeln("))");
203         }
204     }
205 
206     alias Import = Sections[Section.IMPORT];
207     void import_sec(ref const(Import) _import) {
208         //        auto _import=*mod[Section.IMPORT];//.import_sec;
209         static string importdesc(ref const ImportType imp, const size_t index) {
210             const desc = imp.importdesc.desc;
211             with (IndexType) {
212                 final switch (desc) {
213                 case FUNC:
214                     const _funcdesc = imp.importdesc.get!FUNC;
215                     return format("(%s (;%d;) (func %d))", indexName(desc),
216                             index, _funcdesc.funcidx);
217                 case TABLE:
218                     const _tabledesc = imp.importdesc.get!TABLE;
219                     return format("(%s (;%d;) %s %s)", indexName(desc), index,
220                             limitToString(_tabledesc.limit), typesName(_tabledesc.type));
221                 case MEMORY:
222                     const _memorydesc = imp.importdesc.get!MEMORY;
223                     return format("(%s(;%d;)  %s %s)", indexName(desc), index,
224                             limitToString(_memorydesc.limit));
225                 case GLOBAL:
226                     const _globaldesc = imp.importdesc.get!GLOBAL;
227                     return format("(%s (;%d;) %s)", indexName(desc), index,
228                             globalToString(_globaldesc));
229                 }
230             }
231         }
232 
233         foreach (i, imp; _import[].enumerate) {
234             //            output.writefln("imp=%s", imp);
235             output.writefln(`%s(import "%s" "%s" %s)`, indent, imp.mod,
236                     imp.name, importdesc(imp, i));
237         }
238     }
239 
240     alias Function = Sections[Section.FUNCTION];
241     protected Function _function;
242     @trusted void function_sec(ref const(Function) _function) {
243         // Empty
244         // The function headers are printed in the code section
245         this._function = cast(Function) _function;
246     }
247 
248     alias Table = Sections[Section.TABLE];
249     void table_sec(ref const(Table) _table) {
250         foreach (i, t; _table[].enumerate) {
251             output.writefln("%s(table (;%d;) %s %s)", indent, i,
252                     limitToString(t.limit), typesName(t.type));
253         }
254     }
255 
256     alias Memory = Sections[Section.MEMORY];
257     void memory_sec(ref const(Memory) _memory) {
258         foreach (i, m; _memory[].enumerate) {
259             output.writefln("%s(memory (;%d;) %s)", indent, i, limitToString(m.limit));
260         }
261     }
262 
263     alias Global = Sections[Section.GLOBAL];
264     void global_sec(ref const(Global) _global) {
265         Context ctx;
266         foreach (i, g; _global[].enumerate) {
267             output.writefln("%s(global (;%d;) %s (", indent, i, globalToString(g.global));
268             auto expr = g[];
269             block(expr, ctx, indent ~ spacer);
270             output.writefln("%s))", indent);
271         }
272     }
273 
274     alias Export = Sections[Section.EXPORT];
275     private Export _export;
276     void export_sec(ref const(Export) _export) {
277         this._export = _export.dup;
278     }
279 
280     ExportType getExport(const int idx) const { //pure nothrow {
281         const found = _export[].find!(exp => exp.idx == idx);
282         if (found.empty) {
283             return ExportType.init;
284         }
285         return found.front;
286     }
287 
288     alias Start = Sections[Section.START];
289     void start_sec(ref const(Start) _start) {
290         output.writefln("%s(start %d),", indent, _start.idx);
291     }
292 
293     alias Element = Sections[Section.ELEMENT];
294     void element_sec(ref const(Element) _element) {
295         Context ctx;
296         foreach (i, e; _element[].enumerate) {
297             output.writefln("%s(elem (;%d;) (", indent, i);
298             auto expr = e[];
299             const local_indent = indent ~ spacer;
300             block(expr, ctx, local_indent ~ spacer);
301             output.writef("%s) func", local_indent);
302             foreach (f; e.funcs) {
303                 output.writef(" %d", f);
304             }
305             output.writeln(")");
306         }
307     }
308 
309     static string dType(const Types type) {
310         with (Types) {
311             final switch (type) {
312 
313             case EMPTY:
314                 return "void";
315             case I32:
316                 return "int";
317             case I64:
318                 return "long";
319             case F32:
320                 return "float";
321             case F64:
322                 return "double";
323             case FUNC:
324                 return "_function_";
325             case FUNCREF:
326                 return "void*";
327 
328             }
329         }
330         assert(0);
331     }
332 
333     static string return_type(const(Types[]) types) {
334         return (types.length == 1) ? dType(types[0]) : "void";
335     }
336 
337     string function_name(const int index) {
338         const exp = getExport(index);
339         if (exp == ExportType.init) {
340             return format("func_%d", index);
341         }
342         return exp.name;
343     }
344 
345     static string param_name(const size_t index) {
346         return format("param_%d", index);
347     }
348 
349     static string function_params(const(Types[]) types) {
350         return format("%-(%s, %)", types.enumerate.map!(type => format("%s %s", dType(type.value),
351                 param_name(type.index))));
352     }
353 
354     private void output_function(const(TypeIndex) func, const(CodeType) code_type) {
355         Context ctx;
356         auto expr = code_type[];
357         const function_header = wasmstream.get!(Section.TYPE)[func.idx];
358         const x = return_type(function_header.results);
359         output.writefln("%s%s %s (%s) {",
360                 indent,
361                 return_type(function_header.results),
362                 function_name(func.idx),
363                 function_params(function_header.params));
364         ctx.locals = iota(function_header.params.length).map!(idx => param_name(idx)).array;
365         const local_indent = indent ~ spacer;
366         if (!code_type.locals.empty) {
367             output.writef("%s(local", local_indent);
368             foreach (l; code_type.locals) {
369                 foreach (i; 0 .. l.count) {
370                     output.writef(" %s", typesName(l.type));
371                 }
372             }
373             output.writeln(")");
374         }
375 
376         block(expr, ctx, local_indent);
377         output.writefln("%s}\n", indent);
378     }
379 
380     alias Code = Sections[Section.CODE];
381     @trusted void code_sec(ref const(Code) _code) {
382         foreach (f, c; lockstep(_function[], _code[], StoppingPolicy.requireSameLength)) {
383             output_function(f, c);
384         }
385     }
386 
387     alias Data = Sections[Section.DATA];
388     void data_sec(ref const(Data) _data) {
389         Context ctx;
390         foreach (d; _data[]) {
391             output.writefln("%s(data (", indent);
392             auto expr = d[];
393             const local_indent = indent ~ spacer;
394             block(expr, ctx, local_indent ~ spacer);
395             output.writefln(`%s) "%s")`, local_indent, d.base);
396         }
397     }
398 
399     struct Context {
400         string[] locals;
401         string[] stack;
402         string peek() const pure nothrow @nogc {
403             return stack[$ - 1];
404         }
405 
406         string pop() pure nothrow {
407             scope (exit) {
408                 stack.length--;
409             }
410             return peek;
411         }
412 
413         string[] pops(const size_t amount) pure nothrow {
414             try {
415                 scope (success) {
416                     stack.length -= amount;
417                 }
418                 return stack[$ - amount .. $];
419             }
420             catch (Exception e) {
421                 return ["Error Stack underflow", e.msg];
422             }
423             assert(0);
424         }
425 
426         void push(string value) pure nothrow {
427             stack ~= value;
428         }
429 
430         bool empty() const pure nothrow @nogc {
431             return stack.empty;
432         }
433 
434         void perform(const IR ir, const uint number_of_args) {
435             switch (number_of_args) {
436             case 1:
437                 push(format(instr_fmt[ir], pop));
438                 return;
439             case 2:
440                 push(format(instr_fmt[ir], pop, pop));
441                 return;
442             default:
443                 check(0, format("Format argument %s not supported for %s", number_of_args, instrTable[ir].name));
444             }
445 
446         }
447 
448         void push(const IR ir, const uint local_idx) pure nothrow {
449             push(locals[local_idx]);
450         }
451     }
452 
453     private const(ExprRange.IRElement) block(
454             ref ExprRange expr,
455             ref Context ctx,
456             const(string) indent,
457             const bool no_return = false) {
458         string block_comment;
459         uint block_count;
460         uint count;
461         uint calls;
462         static string block_result_type()(const Types t) {
463             with (Types) {
464                 switch (t) {
465                 case I32, I64, F32, F64, FUNCREF:
466                     return format(" (result %s)", typesName(t));
467                 case EMPTY:
468                     return null;
469                 default:
470                     check(0, format("Block Illegal result type %s for a block", t));
471                 }
472             }
473             assert(0);
474         }
475 
476         string result_name() {
477             return format("result_%d", calls);
478         }
479 
480         const(ExprRange.IRElement) innerBlock(ref ExprRange expr, const(string) indent, const uint level) {
481             while (!expr.empty) {
482                 const elm = expr.front;
483                 const instr = instrTable[elm.code];
484                 expr.popFront;
485 
486                 with (IRType) {
487                     final switch (instr.irtype) {
488                     case CODE:
489                         output.writefln("%s// %s", indent, instr.name);
490                         ctx.perform(elm.code, instr.pops);
491                         break;
492                     case PREFIX:
493                         output.writefln("%s%s", indent, instr.name);
494                         break;
495                     case BLOCK:
496                         block_comment = format(";; block %d", block_count);
497                         block_count++;
498                         output.writefln("%s%s%s %s", indent, instr.name,
499                                 block_result_type(elm.types[0]), block_comment);
500                         const end_elm = innerBlock(expr, indent ~ spacer, level + 1);
501                         const end_instr = instrTable[end_elm.code];
502                         output.writefln("%s%s", indent, end_instr.name);
503                         //return end_elm;
504 
505                         // const end_elm=block_elm(elm);
506                         if (end_elm.code is IR.ELSE) {
507                             const endif_elm = innerBlock(expr, indent ~ spacer, level + 1);
508                             const endif_instr = instrTable[endif_elm.code];
509                             output.writefln("%s%s %s count=%d", indent,
510                                     endif_instr.name, block_comment, count);
511                         }
512                         break;
513                     case BRANCH:
514                     case BRANCH_IF:
515                         output.writefln("%s%s %s", indent, instr.name, elm.warg.get!uint);
516                         break;
517                     case BRANCH_TABLE:
518                         static string branch_table(const(WasmArg[]) args) {
519                             string result;
520                             foreach (a; args) {
521                                 result ~= format(" %d", a.get!uint);
522                             }
523                             return result;
524                         }
525 
526                         output.writefln("%s%s %s", indent, instr.name, branch_table(elm.wargs));
527                         break;
528                     case CALL:
529                         scope (exit) {
530                             calls++;
531                         }
532                         output.writefln("%s// %s %s", indent, instr.name, elm.warg.get!uint);
533                         const func_idx = elm.warg.get!uint;
534                         const function_header = wasmstream.get!(Section.TYPE)[func_idx];
535                         const function_call = format("%s(%-(%s,%))", function_name(func_idx), ctx.pops(function_header
536                                 .params.length));
537                         string set_result;
538                         if (function_header.results.length) {
539                             set_result = format("const %s=", result_name);
540                             ctx.push(result_name);
541                         }
542                         output.writefln("%s%s%s;", indent, set_result, function_call);
543                         break;
544                     case CALL_INDIRECT:
545                         output.writefln("%s%s (type %d)", indent, instr.name, elm.warg.get!uint);
546                         break;
547                     case LOCAL:
548                         output.writefln("%s// %s %d", indent, instr.name, elm.warg.get!uint);
549                         ctx.push(elm.code, elm.warg.get!uint);
550                         break;
551                     case GLOBAL:
552                         output.writefln("%s%s %d", indent, instr.name, elm.warg.get!uint);
553                         break;
554                     case MEMORY:
555                         output.writefln("%s%s%s", indent, instr.name, offsetAlignToString(elm.wargs));
556                         break;
557                     case MEMOP:
558                         output.writefln("%s%s", indent, instr.name);
559                         break;
560                     case CONST:
561                         static string toText(const WasmArg a) {
562                             with (Types) {
563                                 switch (a.type) {
564                                 case I32:
565                                     return format("(%d)", a.get!int);
566                                 case I64:
567                                     return format("(%dL)", a.get!long);
568                                 case F32:
569                                     const x = a.get!float;
570                                     return format("(%a /* %s */)", x, x);
571                                 case F64:
572                                     const x = a.get!double;
573                                     return format("(%a /* %s */)", x, x);
574                                 default:
575                                     assert(0);
576                                 }
577                             }
578                             assert(0);
579                         }
580 
581                         const value = toText(elm.warg);
582                         output.writefln("%s// %s %s", indent, instr.name, value);
583                         ctx.push(value);
584                         break;
585                     case END:
586                         return elm;
587                     case ILLEGAL:
588                         output.writefln("Error: Illegal instruction %02X", elm.code);
589                         return elm;
590                     case SYMBOL:
591                         assert(0, "Symbol opcode and it does not have an equivalent opcode");
592                     }
593                 }
594             }
595             return ExprRange.IRElement(IR.END, level);
596         }
597 
598         scope (exit) {
599             if (!no_return && (ctx.stack.length > 0)) {
600                 output.writefln("%sreturn %s;", indent, ctx.pop);
601             }
602             check(no_return || (ctx.stack.length == 0), format("Stack size is %d but the stack should be empty on return", ctx
603                     .stack
604                     .length));
605         }
606         return innerBlock(expr, indent, 0);
607     }
608 
609     Output serialize() {
610         output.writefln("module %s;", module_name);
611         output.writeln;
612         imports.each!(imp => output.writefln("import %s;", imp));
613         attributes.each!(attr => output.writefln("%s:", attr));
614         //indent = spacer;
615         scope (exit) {
616             output.writeln("// end");
617         }
618         wasmstream(this);
619         return output;
620     }
621 
622 }
623 
624 immutable string[IR] instr_fmt;
625 
626 shared static this() {
627     instr_fmt = [
628         IR.LOCAL_GET: q{%1$s},
629         IR.LOCAL_SET: q{%2$s=$1$s;},
630         IR.I32_CLZ: q{wasm.clz(%s)},
631         IR.I32_CTZ: q{wasm.ctz(%s)},
632         IR.I32_POPCNT: q{wasm.popcnt(%s)},
633         IR.I32_ADD: q{(%2$s + %1$s)},
634         IR.I32_SUB: q{(%2$s - %1$s)},
635         IR.I32_MUL: q{(%2$s * %1$s)},
636         IR.I32_DIV_S: q{wasm.div(%2$s, %1$s)},
637         IR.I32_DIV_U: q{wasm.div(uint(%2$s), uint(%1$s))},
638         IR.I32_REM_S: q{wasm.rem(%2$s, %1$s)},
639         IR.I32_REM_U: q{wasm.rem(uint(%2$s), uint(%1$s))},
640         IR.I32_AND: q{(%2$s & %1$s)},
641         IR.I32_OR: q{(%2$s | %1$s)},
642         IR.I32_XOR: q{(%2$s ^ %1$s)},
643         IR.I32_SHL: q{(%2$s << %1$s)},
644         IR.I32_SHR_S: q{(%2$s >> %1$s)},
645         IR.I32_SHR_U: q{(%2$s >>> %1$s)},
646         IR.I32_ROTL: q{wasm.rotl(%1$s, %2$s)},
647         IR.I32_ROTR: q{wasm.rotr(%1$s, %2$s)},
648         IR.I32_EQZ: q{(%1$s == 0)},
649         IR.I32_EQ: q{(%2$s == %1$s)},
650         IR.I32_NE: q{(%2$s != %1$s)},
651         IR.I32_LT_S: q{(%2$s < %1$s)},
652         IR.I32_LT_U: q{(uint(%2$s) < uint(%1$s))},
653         IR.I32_LE_S: q{(%2$s <= %1$s)},
654         IR.I32_LE_U: q{(uint(%2$s) <= uint(%1$s))},
655         IR.I32_GT_S: q{(%2$s > %1$s)},
656         IR.I32_GT_U: q{(uint(%2$s) > uint(%1$s))},
657         IR.I32_GE_S: q{(%2$s >= %1$s)},
658         IR.I32_GE_U: q{(uint(%2$s) >= uint(%1$s))},
659 
660     ];
661 }
662 
663 version (none) unittest {
664     import std.exception : assumeUnique;
665     import std.file;
666     import std.stdio;
667 
668     //      import std.file : fread=read, fwrite=write;
669 
670     @trusted static immutable(ubyte[]) fread(R)(R name, size_t upTo = size_t.max) {
671         import std.file : _read = read;
672 
673         auto data = cast(ubyte[]) _read(name, upTo);
674         // writefln("read data=%s", data);
675         return assumeUnique(data);
676     }
677 
678     //    string filename="../tests/wasm/func_1.wasm";
679     //    string filename="../tests/wasm/global_1.wasm";
680     //    string filename="../tests/wasm/imports_1.wasm";
681     //    string filename="../tests/wasm/table_copy_2.wasm";
682     //    string filename="../tests/wasm/memory_2.wasm";
683     //    string filename="../tests/wasm/start_4.wasm";
684     //    string filename="../tests/wasm/address_1.wasm";
685     string filename = "../tests/wasm/data_4.wasm";
686     immutable code = fread(filename);
687     auto wasm = WasmReader(code);
688     //    auto dasm=Wdisasm(wasm);
689     Wast(wasm, stdout).serialize();
690     //    auto output=Wast
691 
692 }