1 module tagion.wasm.WasmExpr;
2 
3 import std.bitmanip : nativeToLittleEndian;
4 import std.format;
5 import std.outbuffer;
6 import std.traits : ForeachType, Unqual, isArray, isIntegral;
7 import tagion.wasm.WasmBase;
8 import tagion.utils.LEB128;
9 
10 @safe
11 struct WasmExpr {
12     protected OutBuffer bout;
13     @disable this();
14     this(OutBuffer bout) pure nothrow {
15         this.bout = bout;
16     }
17 
18     ref WasmExpr opCall(Args...)(const IR ir, Args args) {
19         immutable instr = instrTable.get(ir, illegalInstr);
20         bout.write(cast(ubyte) ir);
21         immutable irtype = instr.irtype;
22         with (IRType) {
23             final switch (irtype) {
24             case PREFIX:
25             case CODE:
26                 assert(Args.length == 0,
27                         format("Instruction %s should have no arguments", instr.name));
28                 // No args
29                 break;
30             case BLOCK, BRANCH, BRANCH_IF, CALL, LOCAL, GLOBAL:
31                 assert(Args.length == 1,
32                         format("Instruction %s only one argument expected", instr.name));
33                 static if (Args.length == 1) {
34                     assert(isIntegral!(Args[0]), format("Args idx must be an integer for %s not %s",
35                             instr.name, Args[0].stringof));
36                     static if (isIntegral!(Args[0])) {
37                         bout.write(encode(args[0]));
38                     }
39                 }
40                 break;
41             case BRANCH_TABLE:
42                 scope uint[] table;
43                 static foreach (i, a; args) {
44                     {
45                         enum OK = is(Args[i] : const(uint)) || is(Args[i] : const(uint[]));
46                         assert(OK, format("Argument %d must be integer or uint[] of integer not %s",
47                                 i, Args[i].stringof));
48                         static if (OK) {
49                             table ~= a;
50                         }
51                     }
52                 }
53                 check(table.length >= 2, format("Too few arguments for %s instruction", instr.name));
54                 bout.write(encode(table.length - 1));
55                 foreach (t; table) {
56                     bout.write(encode(t));
57                 }
58                 break;
59             case CALL_INDIRECT:
60                 assert(Args.length == 1, format("Instruction %s one argument", instr.name));
61                 static if (Args.length == 1) {
62                     assert(isIntegral!(Args[0]),
63                     format("The funcidx must be an integer for %s", instr.name));
64                     static if (isIntegral!(Args[0])) {
65                         bout.write(encode(args[0]));
66                         bout.write(cast(ubyte)(0x00));
67                     }
68                 }
69                 break;
70             case MEMORY:
71                 assert(Args.length == 2, format("Instruction %s two arguments", instr.name));
72                 static if (Args.length == 2) {
73                     assert(isIntegral!(Args[0]),
74                     format("The funcidx must be an integer for %s", instr.name));
75                     assert(isIntegral!(Args[1]),
76                     format("The funcidx must be an integer for %s", instr.name));
77                     static if (isIntegral!(Args[0]) && isIntegral!(Args[1])) {
78                         bout.write(encode(args[0]));
79                         bout.write(encode(args[1]));
80                     }
81                 }
82                 break;
83             case MEMOP:
84                 assert(Args.length == 0,
85                         format("Instruction %s should have no arguments", instr.name));
86                 bout.write(cast(ubyte)(0x00));
87                 break;
88             case CONST:
89                 assert(Args.length == 1, format("Instruction %s one argument", instr.name));
90                 static if (Args.length == 1) {
91                     alias BaseArg0 = Unqual!(Args[0]);
92                     with (IR) {
93                         switch (ir) {
94                         case I32_CONST:
95                             assert(is(BaseArg0 == int) || is(BaseArg0 == uint),
96                                     format("Bad type %s for the %s instruction",
97                                     BaseArg0.stringof, instr.name));
98                             static if (is(BaseArg0 == int) || is(BaseArg0 == uint)) {
99                                 bout.write(encode(args[0]));
100                             }
101                             break;
102                         case I64_CONST:
103                             assert(isIntegral!(BaseArg0), format("Bad type %s for the %s instruction",
104                                     BaseArg0.stringof, instr.name));
105                             static if (isIntegral!(BaseArg0)) {
106                                 bout.write(encode(args[0]));
107                             }
108                             break;
109                         case F32_CONST:
110                             assert(is(BaseArg0 : float), format("Bad type %s for the %s instruction",
111                                     Args[0].stringof, instr.name));
112                             static if (is(BaseArg0 : float)) {
113                                 float x = args[0];
114                                 bout.write(nativeToLittleEndian(x));
115                             }
116                             break;
117                         case F64_CONST:
118                             assert(is(BaseArg0 : double), format("Bad type %s for the %s instruction",
119                                     Args[0].stringof, instr.name));
120                             static if (is(BaseArg0 : double)) {
121                                 double x = args[0];
122                                 bout.write(nativeToLittleEndian(x));
123                             }
124                             break;
125                         default:
126                             assert(0, format("Bad const instruction %s", instr.name));
127                         }
128                     }
129                 }
130                 break;
131             case END:
132                 assert(Args.length == 0,
133                         format("Instruction %s should have no arguments", instr.name));
134                 break;
135             case ILLEGAL:
136                 assert(0, format("Illegal opcode %02X", ir));
137                 break;
138             case SYMBOL:
139                 assert(0, "Symbol opcode and it does not have an equivalent opcode");
140             }
141         }
142         return this;
143     }
144 
145     immutable(ubyte[]) serialize() const pure nothrow {
146         return bout.toBytes.idup;
147     }
148 }