1 module zua.compiler.utils; 2 import zua.compiler.sourcemap; 3 import zua.vm.engine; 4 import std.uuid; 5 import std.bitmanip; 6 import std.variant; 7 import std.typecons; 8 9 alias FullWidth = ulong; 10 alias StackOffset = ulong; 11 alias CommonOperand = ulong; 12 alias OpcodeSize = uint; 13 14 /** An abstract VM statement */ 15 abstract class VmStat {} 16 17 /** A tag to allow for the creation of a source map */ 18 final class Indices : VmStat { 19 /** Start index in source file */ 20 size_t start; 21 22 /** End index in source file */ 23 size_t end; 24 25 /** Create a new Indices object */ 26 this(size_t start, size_t end) { 27 this.start = start; 28 this.end = end; 29 } 30 } 31 32 /** A label for a jump instruction */ 33 final class Label : VmStat { 34 /** The id of the label */ 35 UUID id; 36 37 /** Create a new label */ 38 this(UUID id) { 39 this.id = id; 40 } 41 } 42 43 /** An abstract instruction */ 44 abstract class Instruction : VmStat { 45 /** The instruction opcode */ 46 Opcode op; 47 } 48 49 /** An instruction with no operands */ 50 final class AtomicInstruction : Instruction { 51 52 /** Construct a new atomic instruction */ 53 this(Opcode op) { 54 this.op = op; 55 } 56 57 } 58 59 /** The value of an operand */ 60 union OperandValue { 61 /** The value of an operand */ 62 ulong i; 63 double d; /// ditto 64 65 /** Create a new OperandValue using an integer */ 66 this(ulong i) { 67 this.i = i; 68 } 69 70 /** Create a new OperandValue using a double */ 71 this(double d) { 72 this.d = d; 73 } 74 } 75 76 /** An instruction with one operand */ 77 final class MonadInstruction : Instruction { 78 /** The value of an operand */ 79 Algebraic!(OperandValue, UUID) value; 80 81 /** Construct a new monad instruction */ 82 this(Opcode op, OperandValue value) { 83 this.op = op; 84 this.value = value; 85 } 86 87 /** Construct a new monad instruction */ 88 this(Opcode op, ulong value) { 89 this.op = op; 90 this.value = OperandValue(value); 91 } 92 93 /** Construct a new monad instruction */ 94 this(Opcode op, double value) { 95 this.op = op; 96 this.value = OperandValue(value); 97 } 98 99 /** Construct a new monad instruction */ 100 this(Opcode op, UUID value) { 101 this.op = op; 102 this.value = value; 103 } 104 } 105 106 /** LDFUN instruction */ 107 final class LdFun : Instruction { 108 /** The index of the function to load */ 109 ulong index; 110 111 /** List of locals to close. Must be heap values, otherwise UB ensues */ 112 ulong[] upvalues; 113 114 /** Construct a new LDFUN instruction */ 115 this(ulong index, ulong[] upvalues) { 116 op = Opcode.LdFun; 117 this.index = index; 118 this.upvalues = upvalues; 119 } 120 } 121 122 /** A function */ 123 final class Function { 124 /** String data used in this function */ 125 string[] data; 126 127 /** Functions used in this function */ 128 Function[] functions; 129 130 /** The number of local variables to allocate in this function */ 131 ulong locals; 132 133 /** The number of upvalues to allocate in this function */ 134 ulong upvalues; 135 136 /** The actual body of this function */ 137 VmStat[] code; 138 } 139 140 private class BytecodeWriter { 141 142 private Indices indices; 143 144 private ubyte[] buffer; 145 private Tuple!(size_t, UUID)[] labelRefs; 146 private ulong[UUID] labels; 147 148 private SourceMap map; 149 150 private void write(ushort value) { 151 map.write(indices.start, indices.end, 2); 152 buffer ~= cast(ubyte)(value & 0xFF); 153 buffer ~= cast(ubyte)(value >> 8); 154 } 155 156 private void write(uint value) { 157 write(cast(ushort)(value & 0xFFFF)); 158 write(cast(ushort)(value >> 16)); 159 } 160 161 private void write(ulong value) { 162 write(cast(uint)(value & 0xFFFFFFFF)); 163 write(cast(uint)(value >> 32)); 164 } 165 166 private void write(string value) { 167 write(cast(ulong)value.length); 168 map.write(indices.start, indices.end, value.length); 169 buffer ~= cast(immutable(ubyte)[])value; 170 } 171 172 private void write(size_t len)(ubyte[len] value, size_t index) { 173 foreach (b; value) { 174 buffer[index] = b; 175 index++; 176 } 177 } 178 179 private void write(Instruction i) { 180 write(cast(OpcodeSize)i.op); 181 if (const MonadInstruction monad = cast(MonadInstruction)i) { 182 if (monad.value.peek!UUID) { 183 labelRefs ~= tuple(buffer.length, cast(UUID)monad.value.get!UUID); 184 write(0UL); 185 } 186 else { 187 OperandValue v = monad.value.get!OperandValue; 188 switch (monad.op) { 189 case Opcode.Introspect: 190 case Opcode.DropTuple: 191 case Opcode.UnpackRev: 192 case Opcode.Unpack: 193 case Opcode.Pack: 194 case Opcode.UnpackD: 195 case Opcode.DupN: 196 case Opcode.SetArray: 197 write(cast(StackOffset)v.i); 198 break; 199 case Opcode.Loop: 200 case Opcode.Jmp: 201 case Opcode.JmpT: 202 case Opcode.JmpF: 203 case Opcode.JmpNil: 204 write(cast(FullWidth)v.i); 205 break; 206 case Opcode.LdNum: 207 write(cast(ulong)v.i); 208 break; 209 default: 210 write(cast(CommonOperand)v.i); 211 break; 212 } 213 } 214 } 215 else if (const LdFun ld = cast(LdFun)i) { 216 write(ld.index); 217 write(cast(ulong)ld.upvalues.length); 218 foreach (uv; ld.upvalues) { 219 write(uv); 220 } 221 } 222 } 223 224 /** Write a Function */ 225 void write(Function func) { 226 const saveIndices = indices; 227 write(cast(ulong)func.data.length); 228 write(cast(ulong)func.functions.length); 229 write(cast(ulong)func.locals); 230 write(cast(ulong)func.upvalues); 231 const index = buffer.length; 232 write(0UL); // code size in bytes 233 foreach (stat; func.code) { 234 if (auto i = cast(Instruction)stat) { 235 write(i); 236 } 237 else if (auto l = cast(Label)stat) { 238 labels[l.id] = cast(ulong)buffer.length; 239 } 240 else if (auto l = cast(Indices)stat) { 241 indices = l; 242 } 243 } 244 ulong codeSize = cast(ulong)(buffer.length - index - 8); 245 write(nativeToLittleEndian(codeSize), index); 246 247 ulong strIndex = 0; 248 foreach (s; func.data) { 249 write(strIndex); // string offset in bytes 250 strIndex += 8 + s.length; 251 } 252 write(strIndex); // total string segment size 253 foreach (s; func.data) { 254 write(s); 255 } 256 257 ulong fnIndex = 0; 258 size_t offsetIndex = buffer.length; 259 foreach (f; func.functions) { 260 write(0UL); // function offset in bytes 261 } 262 write(0UL); // total function segment size 263 foreach (f; func.functions) { 264 const size_t began = buffer.length; 265 write(f); 266 const size_t fnSize = buffer.length - began; 267 write(nativeToLittleEndian(fnIndex), offsetIndex); 268 fnIndex += fnSize; 269 offsetIndex += 8; 270 } 271 write(nativeToLittleEndian(fnIndex), offsetIndex); 272 indices = cast(Indices)saveIndices; 273 } 274 275 void resolveLabels() { 276 foreach (l; labelRefs) { 277 const value = labels[l[1]]; 278 const vBytes = nativeToLittleEndian(value); 279 auto i = l[0]; 280 foreach (b; vBytes) { 281 buffer[i] = b; 282 i++; 283 } 284 } 285 } 286 287 /** Get the resulting code from this BytecodeWriter */ 288 const(ubyte)[] result() { 289 return buffer; 290 } 291 292 } 293 294 /** Convert a bytecode function into actual bytecode */ 295 immutable(ubyte)[] serialize(Function func, Indices toplevel, SourceMap map) { 296 BytecodeWriter writer = new BytecodeWriter; 297 writer.map = map; 298 writer.indices = toplevel; 299 writer.write(func); 300 writer.resolveLabels(); 301 return cast(immutable(ubyte)[])writer.result; 302 }