1 module vayne.serializer;
2 
3 
4 import std.algorithm;
5 import std.range;
6 import std.traits;
7 
8 import vayne.compiler;
9 import vayne.op;
10 import vayne.source.source;
11 
12 
13 const(ubyte)[] serialize(CompiledCode code) {
14 	ByteStreamWriter writer;
15 
16 	with (writer) {
17 		put!uint(MagicString);
18 		put!uint(FormatVersion);
19 		put!uint(OpCodesVersion);
20 		put!uint(code.registerCount);
21 
22 		if (code.constants.length) {
23 			put!ubyte(Chunks.Constants);
24 			put!uint(cast(uint)code.constants.length);
25 			foreach (i, k; code.constants) {
26 				put!ubyte(k.type);
27 				put!string(k.value);
28 			}
29 		}
30 
31 		if (code.instrs.length) {
32 			put!ubyte(Chunks.Instructions);
33 			put!uint(cast(uint)code.instrs.length);
34 			foreach (instr; code.instrs) {
35 				put!ulong(instr.code0);
36 				put!ulong(instr.code1);
37 			}
38 		}
39 
40 		if (code.locs.length) {
41 			put!ubyte(Chunks.InstructionSourceLocations);
42 			put!uint(cast(uint)code.locs.length);
43 			foreach (loc; code.locs) {
44 				put!uint(loc.id);
45 				put!uint(loc.line);
46 				//writer.put!uint(loc.column);
47 			}
48 		}
49 
50 		if (code.sources.length) {
51 			put!ubyte(Chunks.SourceNames);
52 			put!uint(cast(uint)code.sources.length);
53 			foreach (source; code.sources)
54 				put!string(source);
55 		}
56 
57 		if (false && code.dependencies.length) {
58 			put!ubyte(Chunks.Dependencies);
59 			put!uint(cast(uint)code.dependencies.length);
60 			foreach (dependency; code.dependencies)
61 				put!string(dependency);
62 		}
63 	}
64 
65 	return writer.data;
66 }
67 
68 
69 CompiledCode unserialize(const(ubyte)[] bytes) {
70 	auto reader = ByteStreamReader(bytes);
71 	CompiledCode result;
72 
73 	with (reader) {
74 		auto magic = take!uint;
75 		auto format = take!uint;
76 		auto opcodes = take!uint;
77 		result.registerCount = take!uint;
78 
79 		assert(magic == MagicString);
80 		assert(format == FormatVersion);
81 		assert(opcodes == OpCodesVersion);
82 
83 		while (!eos) {
84 			auto chunk = take!ubyte;
85 			final switch (cast(Chunks)chunk) with (Chunks) {
86 			case Instructions:
87 				assert(result.instrs.empty);
88 				auto count = take!uint;
89 				result.instrs.reserve(count);
90 				foreach (i; 0..count) {
91 					auto code0 = take!ulong;
92 					auto code1 = take!ulong;
93 					result.instrs ~= Instr(code0, code1);
94 				}
95 				break;
96 			case Constants:
97 				assert(result.constants.empty);
98 				auto count = take!uint;
99 				result.constants.reserve(count);
100 				foreach (i; 0..count) {
101 					auto type = cast(ConstantType)take!ubyte;
102 					auto value = take!string;
103 					result.constants ~= CompiledCode.Constant(type, value);
104 				}
105 				break;
106 			case InstructionSourceLocations:
107 				assert(result.locs.empty);
108 				auto count = take!uint;
109 				result.locs.reserve(count);
110 				foreach (i; 0..count) {
111 					auto source = take!uint;
112 					auto line = take!uint;
113 					result.locs ~= SourceLoc(source, line, 0);
114 				}
115 				break;
116 			case SourceNames:
117 				assert(result.sources.empty);
118 				auto count = take!uint;
119 				result.sources.reserve(count);
120 				foreach (i; 0..count)
121 					result.sources ~= take!string;
122 				break;
123 			case Dependencies:
124 				assert(result.dependencies.empty);
125 				auto count = take!uint;
126 				result.dependencies.reserve(count);
127 				foreach (i; 0..count)
128 					result.dependencies ~= take!string;
129 				break;
130 			}
131 		}
132 	}
133 
134 	return result;
135 }
136 
137 
138 private:
139 
140 enum Chunks : ubyte {
141 	Instructions = 0,
142 	Constants,
143 	InstructionSourceLocations,
144 	SourceNames,
145 	Dependencies,
146 }
147 
148 
149 enum : uint { MagicString = 0xBB9D1BC5 };
150 enum : uint { FormatVersion = 1 };
151 
152 
153 
154 struct ByteStreamWriter {
155 	void put(T)(T x) if (isIntegral!T) {
156 		if (offset_ + T.sizeof > stream_.length)
157 			stream_.length += 16 * 1024;
158 		// TODO: always write as little-endian
159 		*cast(T*)(stream_.ptr + offset_) = x;
160 		length_ += T.sizeof;
161 		offset_ += T.sizeof;
162 	}
163 
164 	void put(T)(T x) if (is(Unqual!T == string)) {
165 		auto req = offset_ + uint.sizeof + x.length;
166 		if (req > stream_.length) {
167 			auto len = stream_.length + 16 * 1024;
168 			while (req >= len)
169 				len += 16 * 1024;
170 			stream_.length = len;
171 		}
172 
173 		put!uint(cast(uint)x.length);
174 
175 		(cast(char*)stream_.ptr)[offset_..offset_ + x.length] = x[];
176 		length_ += x.length;
177 		offset_ += x.length;
178 	}
179 
180 	@property size_t length() const {
181 		return length_;
182 	}
183 
184 	@property const(ubyte)[] data() const {
185 		return stream_[0..length_];
186 	}
187 
188 	private ubyte[] stream_;
189 	private size_t length_;
190 	private size_t offset_;
191 }
192 
193 
194 struct ByteStreamReader {
195 	this(const(ubyte)[] stream) {
196 		stream_ = stream;
197 	}
198 
199 	@property size_t length() const {
200 		return stream_.length;
201 	}
202 
203 	@property bool eos() const {
204 		return offset_ == stream_.length;
205 	}
206 
207 	T take(T)() if (isIntegral!T) {
208 		if (offset_ + T.sizeof > stream_.length)
209 			throw new Exception("reading past end of stream");
210 		// TODO: always read as little-endian
211 		scope(exit) offset_ += T.sizeof;
212 		return *cast(T*)(stream_.ptr + offset_);
213 	}
214 
215 	T take(T)() if (is(Unqual!T == string)) {
216 		if (offset_ + uint.sizeof > stream_.length)
217 			throw new Exception("reading past end of stream");
218 
219 		auto length = take!uint;
220 		auto result = ((cast(char*)stream_.ptr)[offset_..offset_ + length]).idup;
221 		offset_ += length;
222 		return result;
223 	}
224 
225 	private const(ubyte)[] stream_;
226 	private size_t offset_;
227 }