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 }