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 }