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 }