1 module tagion.wasm.WasmBase; 2 3 import std.bitmanip : Endian, binpeek = peek, binread = read, binwrite = write; 4 import std.conv : emplace, to; 5 import std.exception : assumeUnique, assumeWontThrow; 6 import std.format; 7 import std.meta : AliasSeq; 8 import std.range.primitives : isInputRange; 9 import std.stdio; 10 import std.traits : ConstOf, EnumMembers, ForeachType, Unqual, getUDAs, isAssociativeArray, isFunctionPointer; 11 import std.typecons : Tuple; 12 import std.uni : toLower; 13 import tagion.wasm.WasmException; 14 15 import LEB128 = tagion.utils.LEB128; 16 17 enum VerboseMode { 18 NONE, 19 STANDARD 20 } 21 22 @safe struct Verbose { 23 VerboseMode mode; 24 string indent; 25 File fout; 26 enum INDENT = " "; 27 enum WIDTH = 16; 28 29 void opCall(Args...)(string fmt, lazy Args args) { 30 if (mode !is VerboseMode.NONE) { 31 fout.write(indent); 32 fout.writefln(fmt, args); 33 } 34 } 35 36 void print(Args...)(string fmt, lazy Args args) { 37 if (mode !is VerboseMode.NONE) { 38 fout.writef(fmt, args); 39 } 40 } 41 42 void println(Args...)(string fmt, lazy Args args) { 43 if (mode !is VerboseMode.NONE) { 44 fout.writefln(fmt, args); 45 } 46 } 47 48 void down() nothrow { 49 if (mode !is VerboseMode.NONE) { 50 indent ~= INDENT; 51 } 52 } 53 54 void up() nothrow { 55 if (mode !is VerboseMode.NONE) { 56 if (indent.length >= INDENT.length) { 57 indent.length -= INDENT.length; 58 } 59 } 60 } 61 62 void hex(const size_t index, const(ubyte[]) data) { 63 if (mode !is VerboseMode.NONE) { 64 size_t _index = index; 65 foreach (const i, d; data) { 66 if (i % WIDTH is 0) { 67 if (i !is 0) { 68 fout.writeln(""); 69 } 70 fout.writef("%s%06X", indent, _index); 71 } 72 fout.writef(" %02X", d); 73 _index++; 74 } 75 fout.writeln(""); 76 } 77 } 78 79 void ln() { 80 if (mode !is VerboseMode.NONE) { 81 fout.writeln(""); 82 } 83 } 84 85 } 86 87 static Verbose verbose; 88 89 static this() { 90 verbose.fout = stdout; 91 } 92 93 enum Section : ubyte { 94 CUSTOM = 0, 95 TYPE = 1, 96 IMPORT = 2, 97 FUNCTION = 3, 98 TABLE = 4, 99 MEMORY = 5, 100 GLOBAL = 6, 101 EXPORT = 7, 102 START = 8, 103 ELEMENT = 9, 104 CODE = 10, 105 DATA = 11, 106 } 107 108 enum IRType { 109 CODE, /// Simple instruction with no argument 110 BLOCK, /// Block instruction 111 // BLOCK_IF, /// Block for [IF] ELSE END 112 // BLOCK_ELSE, /// Block for IF [ELSE] END 113 BRANCH, /// Branch jump instruction 114 BRANCH_IF, /// Conditional branch jump instruction 115 BRANCH_TABLE, /// Branch table jump instruction 116 CALL, /// Subroutine call 117 CALL_INDIRECT, /// Indirect subroutine call 118 LOCAL, /// Local register storage instruction 119 GLOBAL, /// Global register storage instruction 120 MEMORY, /// Memory instruction 121 MEMOP, /// Memory management instruction 122 CONST, /// Constant argument 123 END, /// Block end instruction 124 PREFIX, /// Prefix for two byte extension 125 SYMBOL, /// This is extra instruction which does not have an equivalent wasm opcode 126 ILLEGAL, /// Illegal instructions 127 } 128 129 immutable illegalInstr = Instr("illegal", "illegal", 0, IRType.ILLEGAL); 130 131 struct Instr { 132 string name; /// Instruction name 133 string wast; /// Wast name 134 uint cost; 135 IRType irtype; 136 uint pops; // Number of pops from the stack 137 uint push; // Number of valus pushed 138 bool extend; // Extended 139 } 140 141 enum ubyte[] magic = [0x00, 0x61, 0x73, 0x6D]; 142 enum ubyte[] wasm_version = [0x01, 0x00, 0x00, 0x00]; 143 enum IR : ubyte { 144 // dfmt off 145 @Instr("unreachable", "unreachable", 1, IRType.CODE) UNREACHABLE = 0x00, /// unreachable 146 @Instr("nop", "nop", 1, IRType.CODE) NOP = 0x01, /// nop 147 @Instr("block", "block", 0, IRType.BLOCK) BLOCK = 0x02, /// block rt:blocktype (in:instr) * end 148 @Instr("loop", "loop", 0, IRType.BLOCK) LOOP = 0x03, /// loop rt:blocktype (in:instr) * end 149 @Instr("if", "if", 1, IRType.BLOCK, 1) IF = 0x04, /++ if rt:blocktype (in:instr) *rt in * else ? end 150 if rt:blocktype (in1:instr) *rt in * 1 else (in2:instr) * end 151 +/ 152 @Instr("else", "else", 0, IRType.END) ELSE = 0x05, /// else 153 @Instr("end", "end", 0, IRType.END) END = 0x0B, /// end 154 @Instr("br", "br", 1, IRType.BRANCH, 1) BR = 0x0C, /// br l:labelidx 155 @Instr("br_if", "br_if", 1, IRType.BRANCH_IF, 1) BR_IF = 0x0D, /// br_if l:labelidx 156 @Instr("br_table", "br_table", 1, IRType.BRANCH_TABLE, 1) BR_TABLE = 0x0E, /// br_table l:vec(labelidx) * lN:labelidx 157 @Instr("return", "return", 1, IRType.CODE, 1) RETURN = 0x0F, /// return 158 @Instr("call", "call", 1, IRType.CALL) CALL = 0x10, /// call x:funcidx 159 @Instr("call_indirect", "call_indirect", 1, IRType.CALL_INDIRECT, 1) CALL_INDIRECT = 0x11, /// call_indirect x:typeidx 0x00 160 @Instr("drop", "drop", 1, IRType.CODE, 1) DROP = 0x1A, /// drop 161 @Instr("select", "select", 1, IRType.CODE, 3, 1) SELECT = 0x1B, /// select 162 @Instr("local.get", "get_local", 1, IRType.LOCAL, 0, 1) LOCAL_GET = 0x20, /// local.get x:localidx 163 @Instr("local.set", "set_local", 1, IRType.LOCAL, 1) LOCAL_SET = 0x21, /// local.set x:localidx 164 @Instr("local.tee", "tee_local", 1, IRType.LOCAL, 1, 1) LOCAL_TEE = 0x22, /// local.tee x:localidx 165 @Instr("global.get", "get_global", 1, IRType.GLOBAL, 1, 0) GLOBAL_GET = 0x23, /// global.get x:globalidx 166 @Instr("global.set", "set_global", 1, IRType.GLOBAL, 0, 1) GLOBAL_SET = 0x24, /// global.set x:globalidx 167 168 @Instr("i32.load", "i32.load", 2, IRType.MEMORY, 1, 1) I32_LOAD = 0x28, /// i32.load m:memarg 169 @Instr("i64.load", "i64.load", 2, IRType.MEMORY, 1, 1) I64_LOAD = 0x29, /// i64.load m:memarg 170 @Instr("f32.load", "f32.load", 2, IRType.MEMORY, 1, 1) F32_LOAD = 0x2A, /// f32.load m:memarg 171 @Instr("f64.load", "f64.load", 2, IRType.MEMORY, 1, 1) F64_LOAD = 0x2B, /// f64.load m:memarg 172 @Instr("i32.load8_s", "i32.load8_s", 2, IRType.MEMORY, 1, 1) I32_LOAD8_S = 0x2C, /// i32.load8_s m:memarg 173 @Instr("i32.load8_u", "i32.load8_u", 2, IRType.MEMORY, 1, 1) I32_LOAD8_U = 0x2D, /// i32.load8_u m:memarg 174 @Instr("i32.load16_s", "i32.load16_s", 2, IRType.MEMORY, 1, 1) I32_LOAD16_S = 0x2E, /// i32.load16_s m:memarg 175 @Instr("i32.load16_u", "i32.load16_u", 2, IRType.MEMORY, 1, 1) I32_LOAD16_U = 0x2F, /// i32.load16_u m:memarg 176 @Instr("i64.load8_s", "i64.load8_s", 2, IRType.MEMORY, 1, 1) I64_LOAD8_S = 0x30, /// i64.load8_s m:memarg 177 @Instr("i64.load8_u", "i64.load8_u", 2, IRType.MEMORY, 1, 1) I64_LOAD8_U = 0x31, /// i64.load8_u m:memarg 178 @Instr("i64.load16_s", "i64.load16_s", 2, IRType.MEMORY, 1, 1) I64_LOAD16_S = 0x32, /// i64.load16_s m:memarg 179 @Instr("i64.load16_u", "i64.load16_u", 2, IRType.MEMORY, 1, 1) I64_LOAD16_U = 0x33, /// i64.load16_u m:memarg 180 @Instr("i64.load32_s", "i64.load32_s", 2, IRType.MEMORY, 1, 1) I64_LOAD32_S = 0x34, /// i64.load32_s m:memarg 181 @Instr("i64.load32_u", "i64.load32_u", 2, IRType.MEMORY, 1, 1) I64_LOAD32_U = 0x35, /// i64.load32_u m:memarg 182 @Instr("i32.store", "i32.store", 2, IRType.MEMORY, 2) I32_STORE = 0x36, /// i32.store m:memarg 183 @Instr("i64.store", "i64.store", 2, IRType.MEMORY, 2) I64_STORE = 0x37, /// i64.store m:memarg 184 @Instr("f32.store", "f32.store", 2, IRType.MEMORY, 2) F32_STORE = 0x38, /// f32.store m:memarg 185 @Instr("f64.store", "f64.store", 2, IRType.MEMORY, 2) F64_STORE = 0x39, /// f64.store m:memarg 186 @Instr("i32.store8", "i32.store8", 2, IRType.MEMORY, 2) I32_STORE8 = 0x3A, /// i32.store8 m:memarg 187 @Instr("i32.store16", "i32.store16", 2, IRType.MEMORY, 2) I32_STORE16 = 0x3B, /// i32.store16 m:memarg 188 @Instr("i64.store8", "i64.store8", 2, IRType.MEMORY, 2) I64_STORE8 = 0x3C, /// i64.store8 m:memarg 189 @Instr("i64.store16", "i64.store16", 2, IRType.MEMORY, 2) I64_STORE16 = 0x3D, /// i64.store16 m:memarg 190 @Instr("i64.store32", "i64.store32", 2, IRType.MEMORY, 2) I64_STORE32 = 0x3E, /// i64.store32 m:memarg 191 @Instr("memory.size", "memory_size", 7, IRType.MEMOP, 0, 2) MEMORY_SIZE = 0x3F, /// memory.size 0x00 192 @Instr("memory.grow", "grow_memory", 7, IRType.MEMOP, 1, 2) MEMORY_GROW = 0x40, /// memory.grow 0x00 193 194 @Instr("i32.const", "i32.const", 1, IRType.CONST, 0, 1) I32_CONST = 0x41, /// i32.const n:i32 195 @Instr("i64.const", "i64.const", 1, IRType.CONST, 0, 1) I64_CONST = 0x42, /// i64.const n:i64 196 @Instr("f32.const", "f32.const", 1, IRType.CONST, 0, 1) F32_CONST = 0x43, /// f32.const z:f32 197 @Instr("f64.const", "f64.const", 1, IRType.CONST, 0, 1) F64_CONST = 0x44, /// f64.const z:f64 198 199 @Instr("i32.eqz", "i32.eqz", 1, IRType.CODE, 1) I32_EQZ = 0x45, /// i32.eqz 200 @Instr("i32.eq", "i32.eq", 1, IRType.CODE, 2, 1) I32_EQ = 0x46, /// i32.eq 201 @Instr("i32.ne", "i32.ne", 1, IRType.CODE, 2, 1) I32_NE = 0x47, /// i32.ne 202 @Instr("i32.lt_s", "i32.lt_s", 1, IRType.CODE, 2, 1) I32_LT_S = 0x48, /// i32.lt_s 203 @Instr("i32.lt_u", "i32.lt_u", 1, IRType.CODE, 2, 1) I32_LT_U = 0x49, /// i32.lt_u 204 @Instr("i32.gt_s", "i32.gt_s", 1, IRType.CODE, 2, 1) I32_GT_S = 0x4A, /// i32.gt_s 205 @Instr("i32.gt_u", "i32.gt_u", 1, IRType.CODE, 2, 1) I32_GT_U = 0x4B, /// i32.gt_u 206 @Instr("i32.le_s", "i32.le_s", 1, IRType.CODE, 2, 1) I32_LE_S = 0x4C, /// i32.le_s 207 @Instr("i32.le_u", "i32.le_u", 1, IRType.CODE, 2, 1) I32_LE_U = 0x4D, /// i32.le_u 208 @Instr("i32.ge_s", "i32.ge_s", 1, IRType.CODE, 2, 1) I32_GE_S = 0x4E, /// i32.ge_s 209 @Instr("i32.ge_u", "i32.ge_u", 1, IRType.CODE, 2, 1) I32_GE_U = 0x4F, /// i32.ge_u 210 211 @Instr("i64.eqz", "i64.eqz", 1, IRType.CODE, 2, 1) I64_EQZ = 0x50, /// i64.eqz 212 @Instr("i64.eq", "i64.eq", 1, IRType.CODE, 2, 1) I64_EQ = 0x51, /// i64.eq 213 @Instr("i64.ne", "i64.ne", 1, IRType.CODE, 2, 1) I64_NE = 0x52, /// i64.ne 214 @Instr("i64.lt_s", "i64.lt_s", 1, IRType.CODE, 2, 1) I64_LT_S = 0x53, /// i64.lt_s 215 216 @Instr("i64.lt_u", "i64.lt_u", 1, IRType.CODE, 2, 1) I64_LT_U = 0x54, /// i64.lt_u 217 @Instr("i64.gt_s", "i64.gt_s", 1, IRType.CODE, 2, 1) I64_GT_S = 0x55, /// i64.gt_s 218 @Instr("i64.gt_u", "i64.gt_u", 1, IRType.CODE, 2, 1) I64_GT_U = 0x56, /// i64.gt_u 219 @Instr("i64.le_s", "i64.le_s", 1, IRType.CODE, 2, 1) I64_LE_S = 0x57, /// i64.le_s 220 @Instr("i64.le_u", "i64.le_u", 1, IRType.CODE, 2, 1) I64_LE_U = 0x58, /// i64.le_u 221 @Instr("i64.ge_s", "i64.ge_s", 1, IRType.CODE, 2, 1) I64_GE_S = 0x59, /// i64.ge_s 222 @Instr("i64.ge_u", "i64.ge_u", 1, IRType.CODE, 2, 1) I64_GE_U = 0x5A, /// i64.ge_u 223 224 @Instr("f32.eq", "f32.eq", 1, IRType.CODE, 2, 1) F32_EQ = 0x5B, /// f32.eq 225 @Instr("f32.ne", "f32.ne", 1, IRType.CODE, 2, 1) F32_NE = 0x5C, /// f32.ne 226 @Instr("f32.lt", "f32.lt", 1, IRType.CODE, 2, 1) F32_LT = 0x5D, /// f32.lt 227 @Instr("f32.gt", "f32.gt", 1, IRType.CODE, 2, 1) F32_GT = 0x5E, /// f32.gt 228 @Instr("f32.le", "f32.le", 1, IRType.CODE, 2, 1) F32_LE = 0x5F, /// f32.le 229 @Instr("f32.ge", "f32.ge", 1, IRType.CODE, 2, 1) F32_GE = 0x60, /// f32.ge 230 231 @Instr("f64.eq", "f64.eq", 1, IRType.CODE, 2, 1) F64_EQ = 0x61, /// f64.eq 232 @Instr("f64.ne", "f64.ne", 1, IRType.CODE, 2, 1) F64_NE = 0x62, /// f64.ne 233 @Instr("f64.lt", "f64.lt", 1, IRType.CODE, 2, 1) F64_LT = 0x63, /// f64.lt 234 @Instr("f64.gt", "f64.gt", 1, IRType.CODE, 2, 1) F64_GT = 0x64, /// f64.gt 235 @Instr("f64.le", "f64.le", 1, IRType.CODE, 2, 1) F64_LE = 0x65, /// f64.le 236 @Instr("f64.ge", "f64.ge", 1, IRType.CODE, 2, 1) F64_GE = 0x66, /// f64.ge 237 238 // instructions 239 @Instr("i32.clz", "i32.clz", 1, IRType.CODE, 1, 1) I32_CLZ = 0x67, /// i32.clz 240 @Instr("i32.ctz", "i32.ctz", 1, IRType.CODE, 1, 1) I32_CTZ = 0x68, /// i32.ctz 241 @Instr("i32.popcnt", "i32.popcnt", 1, IRType.CODE, 1, 1) I32_POPCNT = 0x69, /// i32.popcnt 242 @Instr("i32.add", "i32.add", 1, IRType.CODE, 2, 1) I32_ADD = 0x6A, /// i32.add 243 @Instr("i32.sub", "i32.sub", 1, IRType.CODE, 2, 1) I32_SUB = 0x6B, /// i32.sub 244 @Instr("i32.mul", "i32.mul", 1, IRType.CODE, 2, 1) I32_MUL = 0x6C, /// i32.mul 245 @Instr("i32.div_s", "i32.div_s", 1, IRType.CODE, 2, 1) I32_DIV_S = 0x6D, /// i32.div_s 246 @Instr("i32.div_u", "i32.div_u", 1, IRType.CODE, 2, 1) I32_DIV_U = 0x6E, /// i32.div_u 247 @Instr("i32.rem_s", "i32.rem_s", 1, IRType.CODE, 2, 1) I32_REM_S = 0x6F, /// i32.rem_s 248 @Instr("i32.rem_u", "i32.rem_u", 1, IRType.CODE, 2, 1) I32_REM_U = 0x70, /// i32.rem_u 249 @Instr("i32.and", "i32.and", 1, IRType.CODE, 2, 1) I32_AND = 0x71, /// i32.and 250 @Instr("i32.or", "i32.or", 1, IRType.CODE, 2, 1) I32_OR = 0x72, /// i32.or 251 @Instr("i32.xor", "i32.xor", 1, IRType.CODE, 2, 1) I32_XOR = 0x73, /// i32.xor 252 @Instr("i32.shl", "i32.shl", 1, IRType.CODE, 2, 1) I32_SHL = 0x74, /// i32.shl 253 @Instr("i32.shr_s", "i32.shr_s", 1, IRType.CODE, 2, 1) I32_SHR_S = 0x75, /// i32.shr_s 254 @Instr("i32.shr_u", "i32.shr_u", 1, IRType.CODE, 2, 1) I32_SHR_U = 0x76, /// i32.shr_u 255 @Instr("i32.rotl", "i32.rotl", 1, IRType.CODE, 2, 1) I32_ROTL = 0x77, /// i32.rotl 256 @Instr("i32.rotr", "i32.rotr", 1, IRType.CODE, 2, 1) I32_ROTR = 0x78, /// i32.rotr 257 258 @Instr("i64.clz", "i64.clz", 1, IRType.CODE, 1, 1) I64_CLZ = 0x79, /// i64.clz 259 @Instr("i64.ctz", "i64.ctz", 1, IRType.CODE, 1, 1) I64_CTZ = 0x7A, /// i64.ctz 260 @Instr("i64.popcnt", "i64.popcnt", 1, IRType.CODE, 1, 1) I64_POPCNT = 0x7B, /// i64.popcnt 261 @Instr("i64.add", "i64.add", 1, IRType.CODE, 2, 1) I64_ADD = 0x7C, /// i64.add 262 @Instr("i64.sub", "i64.sub", 1, IRType.CODE, 2, 1) I64_SUB = 0x7D, /// i64.sub 263 @Instr("i64.mul", "i64.mul", 1, IRType.CODE, 2, 1) I64_MUL = 0x7E, /// i64.mul 264 @Instr("i64.div_s", "i64.div_s", 1, IRType.CODE, 2, 1) I64_DIV_S = 0x7F, /// i64.div_s 265 @Instr("i64.div_u", "i64.div_u", 1, IRType.CODE, 2, 1) I64_DIV_U = 0x80, /// i64.div_u 266 @Instr("i64.rem_s", "i64.rem_s", 1, IRType.CODE, 2, 1) I64_REM_S = 0x81, /// i64.rem_s 267 @Instr("i64.rem_u", "i64.rem_u", 1, IRType.CODE, 2, 1) I64_REM_U = 0x82, /// i64.rem_u 268 @Instr("i64.and", "i64.and", 1, IRType.CODE, 2, 1) I64_AND = 0x83, /// i64.and 269 @Instr("i64.or", "i64.or", 1, IRType.CODE, 2, 1) I64_OR = 0x84, /// i64.or 270 @Instr("i64.xor", "i64.xor", 1, IRType.CODE, 2, 1) I64_XOR = 0x85, /// i64.xor 271 @Instr("i64.shl", "i64.shl", 1, IRType.CODE, 2, 1) I64_SHL = 0x86, /// i64.shl 272 @Instr("i64.shr_s", "i64.shr_s", 1, IRType.CODE, 2, 1) I64_SHR_S = 0x87, /// i64.shr_s 273 @Instr("i64.shr_u", "i64.shr_u", 1, IRType.CODE, 2, 1) I64_SHR_U = 0x88, /// i64.shr_u 274 @Instr("i64.rotl", "i64.rotl", 1, IRType.CODE, 2, 1) I64_ROTL = 0x89, /// i64.rotl 275 @Instr("i64.rotr", "i64.rotr", 1, IRType.CODE, 2, 1) I64_ROTR = 0x8A, /// i64.rotr 276 277 @Instr("f32.abs", "f32.abs", 1, IRType.CODE, 1, 1) F32_ABS = 0x8B, /// f32.abs 278 @Instr("f32.neg", "f32.neg", 1, IRType.CODE, 1, 1) F32_NEG = 0x8C, /// f32.neg 279 @Instr("f32.ceil", "f32.ceil", 1, IRType.CODE, 1, 1) F32_CEIL = 0x8D, /// f32.ceil 280 @Instr("f32.floor", "f32.floor", 1, IRType.CODE, 1, 1) F32_FLOOR = 0x8E, /// f32.floor 281 @Instr("f32.trunc", "f32.trunc", 1, IRType.CODE, 1, 1) F32_TRUNC = 0x8F, /// f32.trunc 282 @Instr("f32.nearest", "f32.nearest", 1, IRType.CODE, 1, 1) F32_NEAREST = 0x90, /// f32.nearest 283 @Instr("f32.sqrt", "f32.sqrt", 3, IRType.CODE, 1, 1) F32_SQRT = 0x91, /// f32.sqrt 284 @Instr("f32.add", "f32.add", 3, IRType.CODE, 2, 1) F32_ADD = 0x92, /// f32.add 285 @Instr("f32.sub", "f32.sub", 3, IRType.CODE, 2, 1) F32_SUB = 0x93, /// f32.sub 286 @Instr("f32.mul", "f32.mul", 3, IRType.CODE, 2, 1) F32_MUL = 0x94, /// f32.mul 287 @Instr("f32.div", "f32.div", 3, IRType.CODE, 2, 1) F32_DIV = 0x95, /// f32.div 288 @Instr("f32.min", "f32.min", 1, IRType.CODE, 2, 1) F32_MIN = 0x96, /// f32.min 289 @Instr("f32.max", "f32.max", 1, IRType.CODE, 2, 1) F32_MAX = 0x97, /// f32.max 290 @Instr("f32.copysign", "f32.copysign", 1, IRType.CODE, 2, 1) F32_COPYSIGN = 0x98, /// f32.copysign 291 292 @Instr("f64.abs", "f64.abs", 1, IRType.CODE, 1, 1) F64_ABS = 0x99, /// f64.abs 293 @Instr("f64.neg", "f64.neg", 1, IRType.CODE, 1, 1) F64_NEG = 0x9A, /// f64.neg 294 @Instr("f64.ceil", "f64.ceil", 1, IRType.CODE, 1, 1) F64_CEIL = 0x9B, /// f64.ceil 295 @Instr("f64.floor", "f64.floor", 1, IRType.CODE, 1, 1) F64_FLOOR = 0x9C, /// f64.floor 296 @Instr("f64.trunc", "f64.trunc", 1, IRType.CODE, 1, 1) F64_TRUNC = 0x9D, /// f64.trunc 297 @Instr("f64.nearest", "f64.nearest", 1, IRType.CODE, 1, 1) F64_NEAREST = 0x9E, /// f64.nearest 298 @Instr("f64.sqrt", "f64.sqrt", 3, IRType.CODE, 1, 1) F64_SQRT = 0x9F, /// f64.sqrt 299 @Instr("f64.add", "f64.add", 3, IRType.CODE, 2, 1) F64_ADD = 0xA0, /// f64.add 300 @Instr("f64.sub", "f64.sub", 3, IRType.CODE, 2, 1) F64_SUB = 0xA1, /// f64.sub 301 @Instr("f64.mul", "f64.mul", 3, IRType.CODE, 2, 1) F64_MUL = 0xA2, /// f64.mul 302 @Instr("f64.div", "f64.div", 3, IRType.CODE, 2, 1) F64_DIV = 0xA3, /// f64.div 303 @Instr("f64.min", "f64.min", 1, IRType.CODE, 2, 1) F64_MIN = 0xA4, /// f64.min 304 @Instr("f64.max", "f64.max", 1, IRType.CODE, 2, 1) F64_MAX = 0xA5, /// f64.max 305 @Instr("f64.copysign", "f64.copysign", 1, IRType.CODE, 2, 1) F64_COPYSIGN = 0xA6, /// f64.copysign 306 307 @Instr("i32.wrap_i64", "i32.wrap/i64", 1, IRType.CODE, 1, 1) I32_WRAP_I64 = 0xA7, /// i32.wrap_i64 308 @Instr("i32.trunc_f32_s", "i32.trunc_s/f32", 1, IRType.CODE, 1, 1) I32_TRUNC_F32_S = 0xA8, /// i32.trunc_f32_s 309 @Instr("i32.trunc_f32_u", "i32.trunc_u/f32", 1, IRType.CODE, 1, 1) I32_TRUNC_F32_U = 0xA9, /// i32.trunc_f32_u 310 @Instr("i32.trunc_f64_s", "i32.trunc_s/f64", 1, IRType.CODE, 1, 1) I32_TRUNC_F64_S = 0xAA, /// i32.trunc_f64_s 311 @Instr("i32.trunc_f64_u", "i32.trunc_u/f64", 1, IRType.CODE, 1, 1) I32_TRUNC_F64_U = 0xAB, /// i32.trunc_f64_u 312 @Instr("i64.extend_i32_s", "i64.extend_s/i32", 1, IRType.CODE, 1, 1) I64_EXTEND_I32_S = 0xAC, /// i64.extend_i32_s 313 @Instr("i64.extend_i32_u", "i64.extend_u/i32", 1, IRType.CODE, 1, 1) I64_EXTEND_I32_U = 0xAD, /// i64.extend_i32_u 314 @Instr("i32.extend8_s", "i32.extend8_s", 1, IRType.CODE, 1, 1) I32_EXTEND8_S = 0xC0, /// i32.extend8_s 315 @Instr("i32.extend16_s", "i32.extend16_s", 1, IRType.CODE, 1, 1) I32_EXTEND16_S = 0xC1, /// i32.extend16_s 316 @Instr("i64.extend8_s", "i64.extend8_s", 1, IRType.CODE, 1, 1) I64_EXTEND8_S = 0xC2, /// i64.extend8_s 317 @Instr("i64.extend16_s", "i64.extend16_s", 1, IRType.CODE, 1, 1) I64_EXTEND16_S = 0xC3, /// i64.extend16_s 318 @Instr("i64.extend32_s", "i64.extend32_s", 1, IRType.CODE, 1, 1) I64_EXTEND32_S = 0xC4, /// i64.extend32_s 319 @Instr("i64.trunc_f32_s", "i64.trunc_s/f32", 1, IRType.CODE, 1, 1) I64_TRUNC_F32_S = 0xAE, /// i64.trunc_f32_s 320 @Instr("i64.trunc_f32_u", "i64.trunc_u/f32", 1, IRType.CODE, 1, 1) I64_TRUNC_F32_U = 0xAF, /// i64.trunc_f32_u 321 @Instr("i64.trunc_f64_s", "i64.trunc_s/f64", 1, IRType.CODE, 1, 1) I64_TRUNC_F64_S = 0xB0, /// i64.trunc_f64_s 322 @Instr("i64.trunc_f64_u", "i64.trunc_u/f64", 1, IRType.CODE, 1, 1) I64_TRUNC_F64_U = 0xB1, /// i64.trunc_f64_u 323 @Instr("f32.convert_i32_s", "f32.convert_s/i32", 1, IRType.CODE, 1, 1) F32_CONVERT_I32_S = 0xB2, /// f32.convert_i32_s 324 @Instr("f32.convert_i32_u", "f32.convert_u/i32", 1, IRType.CODE, 1, 1) F32_CONVERT_I32_U = 0xB3, /// f32.convert_i32_u 325 @Instr("f32.convert_i64_s", "f32.convert_s/i64", 1, IRType.CODE, 1, 1) F32_CONVERT_I64_S = 0xB4, /// f32.convert_i64_s 326 @Instr("f32.convert_i64_u", "f32.convert_u/i64", 1, IRType.CODE, 1, 1) F32_CONVERT_I64_U = 0xB5, /// f32.convert_i64_u 327 @Instr("f32.demote_f64", "f32.demote/f64", 1, IRType.CODE, 1, 1) F32_DEMOTE_F64 = 0xB6, /// f32.demote_f64 328 @Instr("f64.convert_i32_s", "f64.convert_s/i32", 1, IRType.CODE, 1, 1) F64_CONVERT_I32_S = 0xB7, /// f64.convert_i32_s 329 @Instr("f64.convert_i32_u", "f64.convert_u/i32", 1, IRType.CODE, 1, 1) F64_CONVERT_I32_U = 0xB8, /// f64.convert_i32_u 330 @Instr("f64.convert_i64_s", "f64.convert_s/i64", 1, IRType.CODE, 1, 1) F64_CONVERT_I64_S = 0xB9, /// f64.convert_i64_s 331 @Instr("f64.convert_i64_u", "f64.convert_u/i64", 1, IRType.CODE, 1, 1) F64_CONVERT_I64_U = 0xBA, /// f64.convert_i64_u 332 @Instr("f64.promote_f32", "f64.promote/f32", 1, IRType.CODE, 1, 1) F64_PROMOTE_F32 = 0xBB, /// f64.promote_f32 333 @Instr("i32.reinterpret_f32", "i32.reinterpret/f32", 1, IRType.CODE, 1, 1) I32_REINTERPRET_F32 = 0xBC, /// i32.reinterpret_f32 334 @Instr("i64.reinterpret_f64", "i64.reinterpret/f64", 1, IRType.CODE, 1, 1) I64_REINTERPRET_F64 = 0xBD, /// i64.reinterpret_f64 335 @Instr("f32.reinterpret_i32", "f32.reinterpret/i32", 1, IRType.CODE, 1, 1) F32_REINTERPRET_I32 = 0xBE, /// f32.reinterpret_i32 336 @Instr("f64.reinterpret_i64", "f64.reinterpret/i64", 1, IRType.CODE, 1, 1) F64_REINTERPRET_I64 = 0xBF, /// f64.reinterpret_i64 337 @Instr("truct_sat", "truct_sat", 1, IRType.CODE, 1, 1, true) TRUNC_SAT = 0xFC, /// TYPE.truct_sat_TYPE_SIGN 338 // dfmt on 339 340 } 341 342 Instr getInstr(IR ir)() { 343 enum code = format!q{enum result = getUDAs!(%s, Instr)[0];}(ir.stringof); 344 mixin(code); 345 return result; 346 } 347 348 static unittest { 349 enum InstrUnreachable = Instr("unreachable", "unreachable", 1, IRType.CODE); 350 static assert(getInstr!(IR.UNREACHABLE) == InstrUnreachable); 351 enum ir = IR.UNREACHABLE; 352 static assert(getInstr!(ir) == InstrUnreachable); 353 } 354 355 shared static immutable(Instr[IR]) instrTable; 356 shared static immutable(IR[string]) irLookupTable; 357 shared static immutable(Instr[string]) instrWastLookup; 358 359 enum PseudoWastInstr { 360 invoke = "invoke", 361 if_else = "if_else", 362 call_import = "call_import", 363 local = "local", 364 label = "label", 365 tableswitch = "tableswitch", 366 table = "table", 367 case_ = "case", 368 memory_size = "memory_size", 369 } 370 371 protected immutable(Instr[IR]) generate_instrTable() { 372 Instr[IR] result; 373 with (IR) { 374 static foreach (E; EnumMembers!IR) { 375 { 376 enum code = format!q{result[%1$s]=getUDAs!(%1$s, Instr)[0];}(E.stringof); 377 mixin(code); 378 } 379 } 380 } 381 return assumeUnique(result); 382 } 383 384 shared static this() { 385 instrTable = generate_instrTable; 386 immutable(IR[string]) generateLookupTable() { 387 IR[string] result; 388 foreach (ir, ref instr; instrTable) { 389 result[instr.name] = ir; 390 } 391 return assumeUnique(result); 392 } 393 394 irLookupTable = generateLookupTable; 395 396 immutable(Instr[string]) generated_instrWastLookup() { 397 Instr[string] result; 398 static foreach (ir; EnumMembers!IR) { 399 { 400 enum instr = getInstr!ir; 401 result[instr.wast] = instr; 402 } 403 } 404 void setPseudo(const PseudoWastInstr pseudo, const IRType ir_type, const uint pushs = 0, const uint pops = 0) { 405 result[pseudo] = Instr("<" ~ pseudo ~ ">", pseudo, uint.max, ir_type, pops, pushs); 406 } 407 408 setPseudo(PseudoWastInstr.invoke, IRType.CALL, 0, 1); 409 setPseudo(PseudoWastInstr.if_else, IRType.BRANCH, 3, 1); 410 setPseudo(PseudoWastInstr.local, IRType.SYMBOL, 0, uint.max); 411 setPseudo(PseudoWastInstr.label, IRType.SYMBOL, 1, uint.max); 412 setPseudo(PseudoWastInstr.call_import, IRType.CALL); 413 setPseudo(PseudoWastInstr.tableswitch, IRType.SYMBOL, uint.max, uint.max); 414 setPseudo(PseudoWastInstr.table, IRType.SYMBOL, uint.max); 415 setPseudo(PseudoWastInstr.case_, IRType.SYMBOL, uint.max, 1); 416 417 result["i32.select"] = instrTable[IR.SELECT]; 418 result["i64.select"] = instrTable[IR.SELECT]; 419 result["f32.select"] = instrTable[IR.SELECT]; 420 result["f64.select"] = instrTable[IR.SELECT]; 421 422 return assumeUnique(result); 423 } 424 425 instrWastLookup = generated_instrWastLookup; 426 } 427 428 enum IR_TRUNC_SAT : ubyte { 429 @Instr("i32.trunc_sat_f32_s", "i32.trunc_sat_f32_s", 3, IRType.CODE, 1, 1) I32_F32_S, 430 @Instr("i32.trunc_sat_f32_u", "i32.trunc_sat_f32_u", 3, IRType.CODE, 1, 1) I32_F32_U, 431 @Instr("i32.trunc_sat_f64_s", "i32.trunc_sat_f64_s", 3, IRType.CODE, 1, 1) I32_F64_S, 432 @Instr("i32.trunc_sat_f64_u", "i32.trunc_sat_f64_u", 3, IRType.CODE, 1, 1) I32_F64_U, 433 @Instr("i64.trunc_sat_f32_s", "i64.trunc_sat_f32_s", 3, IRType.CODE, 1, 1) I64_F32_S, 434 @Instr("i64.trunc_sat_f32_u", "i64.trunc_sat_f32_u", 3, IRType.CODE, 1, 1) I64_F32_U, 435 @Instr("i64.trunc_sat_f64_s", "i64.trunc_sat_f64_s", 3, IRType.CODE, 1, 1) I64_F64_S, 436 @Instr("i64.trunc_sat_f64_u", "i64.trunc_sat_f64_u", 3, IRType.CODE, 1, 1) I64_F64_U, 437 } 438 439 version (none) { 440 shared static immutable(string[IR_TRUNC_SAT]) trunc_sat_mnemonic; 441 442 shared static this() { 443 with (IR_TRUNC_SAT) { 444 trunc_sat_mnemonic = [ 445 I32_F32_S: "i32.trunc_sat_f32_s", 446 I32_F32_U: "i32.trunc_sat_f32_u", 447 I32_F64_S: "i32.trunc_sat_f64_s", 448 I32_F64_U: "i32.trunc_sat_f64_u", 449 I64_F32_S: "i64.trunc_sat_f32_s", 450 I64_F32_U: "i64.trunc_sat_f32_u", 451 I64_F64_S: "i64.trunc_sat_f64_s", 452 I64_F64_U: "i64.trunc_sat_f64_u", 453 ]; 454 } 455 } 456 } 457 458 unittest { 459 size_t i; 460 foreach (E; EnumMembers!IR) { 461 assert(E in instrTable); 462 i++; 463 } 464 assert(i == instrTable.length); 465 } 466 467 enum Limits : ubyte { 468 INFINITE = 0x00, /// n:u32 ⇒ {min n, max ε} 469 RANGE = 0x01, /// n:u32 m:u32 ⇒ {min n, max m} 470 } 471 472 enum Mutable : ubyte { 473 CONST = 0x00, 474 VAR = 0x01, 475 } 476 477 enum Types : ubyte { 478 EMPTY = 0x40, /// Empty block 479 @("func") FUNC = 0x60, /// functype 480 @("funcref") FUNCREF = 0x70, /// funcref 481 @("i32") I32 = 0x7F, /// i32 valtype 482 @("i64") I64 = 0x7E, /// i64 valtype 483 @("f32") F32 = 0x7D, /// f32 valtype 484 @("f64") F64 = 0x7C, /// f64 valtype 485 } 486 487 template toWasmType(T) { 488 static if (is(T == int)) { 489 enum toWasmType = Types.I32; 490 } 491 else static if (is(T == long)) { 492 enum toWasmType = Types.I64; 493 } 494 else static if (is(T == float)) { 495 enum toWasmType = Types.F32; 496 } 497 else static if (is(T == double)) { 498 enum toWasmType = Types.F64; 499 } 500 else static if (isFunctionPointer!T) { 501 enum toWasmType = Types.FUNCREF; 502 } 503 else { 504 enum toWasmType = Types.EMPTY; 505 } 506 } 507 508 unittest { 509 static assert(toWasmType!int is Types.I32); 510 static assert(toWasmType!void is Types.EMPTY); 511 } 512 513 template toDType(Types t) { 514 static if (t is Types.I32) { 515 alias toDType = int; 516 } 517 else static if (t is Types.I64) { 518 alias toDType = long; 519 } 520 else static if (t is Types.F32) { 521 alias toDType = float; 522 } 523 else static if (t is Types.F64) { 524 alias toDType = double; 525 } 526 else static if (t is Types.FUNCREF) { 527 alias toDType = void*; 528 } 529 else { 530 alias toDType = void; 531 } 532 } 533 534 @safe static string typesName(const Types type) pure { 535 import std.conv : to; 536 import std.uni : toLower; 537 538 final switch (type) { 539 static foreach (E; EnumMembers!Types) { 540 case E: 541 return toLower(E.to!string); 542 } 543 } 544 } 545 546 @safe static Types getType(const string name) pure { 547 import std.traits; 548 549 switch (name) { 550 static foreach (E; EnumMembers!Types) { 551 static if (hasUDA!(E, string)) { 552 case getUDAs!(E, string)[0]: 553 return E; 554 } 555 } 556 default: 557 return Types.EMPTY; 558 } 559 } 560 561 @safe 562 unittest { 563 assert("f32".getType == Types.F32); 564 assert("empty".getType == Types.EMPTY); 565 assert("not-valid".getType == Types.EMPTY); 566 567 } 568 569 enum IndexType : ubyte { 570 @("func") FUNC = 0x00, /// func x:typeidx 571 @("table") TABLE = 0x01, /// func tt:tabletype 572 @("memory") MEMORY = 0x02, /// mem mt:memtype 573 @("global") GLOBAL = 0x03, /// global gt:globaltype 574 } 575 576 @safe static string indexName(const IndexType idx) pure { 577 import std.conv : to; 578 import std.uni : toLower; 579 580 final switch (idx) { 581 foreach (E; EnumMembers!IndexType) { 582 case E: 583 return toLower(E.to!string); 584 } 585 } 586 } 587 588 T decode(T)(immutable(ubyte[]) data, ref size_t index) pure { 589 size_t byte_size; 590 const leb128_index = LEB128.decode!T(data[index .. $]); 591 scope (exit) { 592 index += leb128_index.size; 593 } 594 return leb128_index.value; 595 } 596 597 alias u32 = decode!uint; 598 alias u64 = decode!ulong; 599 alias i32 = decode!int; 600 alias i64 = decode!long; 601 602 string secname(immutable Section s) @safe { 603 import std.exception : assumeUnique; 604 605 return assumeUnique(format("%s_sec", toLower(s.to!string))); 606 } 607 608 alias SectionsT(SectionType) = AliasSeq!( 609 SectionType.Custom, 610 SectionType.Type, 611 SectionType.Import, 612 SectionType.Function, 613 SectionType.Table, 614 SectionType.Memory, 615 SectionType.Global, 616 SectionType.Export, 617 SectionType.Start, 618 SectionType.Element, 619 SectionType.Code, 620 SectionType.Data, 621 ); 622 623 protected string GenerateInterfaceModule(T...)() { 624 import std.array : join; 625 626 string[] result; 627 static foreach (i, E; EnumMembers!Section) { 628 result ~= format(q{alias SecType_%s=T[Section.%s];}, i, E); 629 result ~= format(q{void %s(ref ConstOf!(SecType_%s) sec);}, secname(E), i); 630 } 631 return result.join("\n"); 632 } 633 634 interface InterfaceModuleT(T...) { 635 enum code = GenerateInterfaceModule!(T)(); 636 mixin(code); 637 } 638 639 version (none) bool isWasmModule(alias M)() @safe if (is(M == struct) || is(M == class)) { 640 import std.algorithm; 641 642 enum all_members = [__traits(allMembers, M)]; 643 return [EnumMembers!Section] 644 .map!(sec => sec.secname) 645 .all!(name => all_members.canFind(name)); 646 } 647 648 @safe struct WasmArg { 649 protected { 650 Types _type; 651 union { 652 @(Types.I32) int i32; 653 @(Types.I64) long i64; 654 @(Types.F32) float f32; 655 @(Types.F64) double f64; 656 } 657 } 658 659 static WasmArg undefine() pure nothrow { 660 WasmArg result; 661 result._type = Types.EMPTY; 662 return result; 663 } 664 665 void opAssign(T)(T x) nothrow { 666 alias BaseT = Unqual!T; 667 static if (is(BaseT == int) || is(BaseT == uint)) { 668 _type = Types.I32; 669 i32 = cast(int) x; 670 } 671 else static if (is(BaseT == long) || is(BaseT == ulong)) { 672 _type = Types.I64; 673 i64 = cast(long) x; 674 } 675 else static if (is(BaseT == float)) { 676 _type = Types.F32; 677 f32 = x; 678 } 679 else static if (is(BaseT == double)) { 680 _type = Types.F64; 681 f64 = x; 682 } 683 else static if (is(BaseT == WasmArg)) { 684 emplace!WasmArg(&this, x); 685 } 686 else { 687 static assert(0, format("Type %s is not supported by WasmArg", T.stringof)); 688 } 689 } 690 691 T get(T)() const { 692 alias BaseT = Unqual!T; 693 static if (is(BaseT == int) || is(BaseT == uint)) { 694 check(_type is Types.I32, format("Wrong to type %s execpted %s", _type, Types.I32)); 695 return cast(T) i32; 696 } 697 else static if (is(BaseT == long) || is(BaseT == ulong)) { 698 check(_type is Types.I64, format("Wrong to type %s execpted %s", _type, Types.I64)); 699 return cast(T) i64; 700 } 701 else static if (is(BaseT == float)) { 702 check(_type is Types.F32, format("Wrong to type %s execpted %s", _type, Types.F32)); 703 return f32; 704 } 705 else static if (is(BaseT == double)) { 706 check(_type is Types.F64, format("Wrong to type %s execpted %s", _type, Types.F64)); 707 return f64; 708 } 709 } 710 711 @property Types type() const pure nothrow { 712 return _type; 713 } 714 715 } 716 717 static assert(isInputRange!ExprRange); 718 @safe struct ExprRange { 719 immutable(ubyte[]) data; 720 721 protected { 722 size_t _index; 723 int _level; 724 IRElement current; 725 WasmException wasm_exception; 726 } 727 728 const(WasmException) exception() const pure nothrow @nogc { 729 return wasm_exception; 730 } 731 732 struct IRElement { 733 IR code; 734 int level; 735 private { 736 WasmArg _warg; 737 WasmArg[] _wargs; 738 const(Types)[] _types; 739 } 740 741 enum unreachable = IRElement(IR.UNREACHABLE); 742 743 const(WasmArg) warg() const pure nothrow { 744 return _warg; 745 } 746 747 const(WasmArg[]) wargs() const pure nothrow { 748 return _wargs; 749 } 750 751 const(Types[]) types() const pure nothrow { 752 return _types; 753 } 754 755 } 756 757 this(immutable(ubyte[]) data) pure { 758 this.data = data; 759 set_front(current, _index); 760 } 761 762 @safe protected void set_front(ref scope IRElement elm, ref size_t index) pure { 763 @trusted void set(ref WasmArg warg, const Types type) pure nothrow { 764 with (Types) { 765 switch (type) { 766 case I32: 767 warg = i32(data, index); 768 break; 769 case I64: 770 warg = i64(data, index); 771 break; 772 case F32: 773 warg = data.binpeek!(float, Endian.littleEndian)(&index); 774 break; 775 case F64: 776 warg = data.binpeek!(double, Endian.littleEndian)(&index); 777 break; 778 default: 779 assumeWontThrow({ 780 wasm_exception = new WasmException(format( 781 "Assembler argument type not vaild as an argument %s", type)); 782 }); 783 } 784 } 785 } 786 787 if (index < data.length) { 788 elm.code = cast(IR) data[index]; 789 elm._types = null; 790 const instr = instrTable.get(elm.code, illegalInstr); 791 index += IR.sizeof; 792 with (IRType) { 793 final switch (instr.irtype) { 794 case CODE: 795 break; 796 case PREFIX: 797 elm._warg = int(data[index]); // Extened insruction 798 index += ubyte.sizeof; 799 break; 800 case BLOCK: 801 elm._types = [cast(Types) data[index]]; 802 index += Types.sizeof; 803 _level++; 804 break; 805 case BRANCH: 806 case BRANCH_IF: 807 // branchidx 808 set(elm._warg, Types.I32); 809 _level++; 810 break; 811 case BRANCH_TABLE: 812 //size_t vec_size; 813 const len = u32(data, index) + 1; 814 elm._wargs = new WasmArg[len]; 815 foreach (ref a; elm._wargs) { 816 a = u32(data, index); 817 } 818 break; 819 case CALL: 820 // callidx 821 set(elm._warg, Types.I32); 822 break; 823 case CALL_INDIRECT: 824 // typeidx 825 set(elm._warg, Types.I32); 826 scope (exit) { 827 index += ubyte.sizeof; 828 } 829 if (!(data[index] == 0x00)) { 830 throw new WasmException("call_indirect should end with 0x00"); 831 } 832 break; 833 case LOCAL, GLOBAL: 834 // localidx globalidx 835 set(elm._warg, Types.I32); 836 break; 837 case MEMORY: 838 // offset 839 elm._wargs = new WasmArg[2]; 840 set(elm._wargs[0], Types.I32); 841 // align 842 set(elm._wargs[1], Types.I32); 843 break; 844 case MEMOP: 845 index++; 846 break; 847 case CONST: 848 with (IR) { 849 switch (elm.code) { 850 case I32_CONST: 851 set(elm._warg, Types.I32); 852 break; 853 case I64_CONST: 854 set(elm._warg, Types.I64); 855 break; 856 case F32_CONST: 857 set(elm._warg, Types.F32); 858 break; 859 case F64_CONST: 860 set(elm._warg, Types.F64); 861 break; 862 default: 863 throw new WasmException(format("Instruction %s is not a const", elm.code)); 864 } 865 } 866 break; 867 case END: 868 _level--; 869 break; 870 case ILLEGAL: 871 872 throw new WasmException(format("%s:Illegal opcode %02X", __FUNCTION__, elm.code)); 873 break; 874 case SYMBOL: 875 assert(0, "Is a symbol and it does not have an equivalent opcode"); 876 } 877 878 } 879 } 880 else { 881 if (index == data.length) { 882 index++; 883 } 884 elm.code = IR.UNREACHABLE; 885 } 886 } 887 888 pure nothrow { 889 const(size_t) index() const { 890 return _index; 891 } 892 893 const(IRElement) front() const { 894 return current; 895 } 896 897 bool empty() const { 898 return _index > data.length || (wasm_exception !is null); 899 } 900 901 } 902 void popFront() pure { 903 set_front(current, _index); 904 } 905 906 }