Dump.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. using Lua.Internal;
  2. using Lua.Runtime;
  3. using System.Buffers;
  4. using System.Buffers.Binary;
  5. using System.Diagnostics;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Runtime.CompilerServices;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. namespace Lua.CodeAnalysis.Compilation;
  11. [SuppressMessage("ReSharper", "MemberCanBePrivate.Local")]
  12. [StructLayout(LayoutKind.Sequential, Pack = 1)]
  13. unsafe struct Header
  14. {
  15. public static ReadOnlySpan<byte> LuaSignature => "\eLua"u8;
  16. public static ReadOnlySpan<byte> LuaTail => [0x19, 0x93, 0x0d, 0x0a, 0x1a, 0x0a];
  17. public fixed byte Signature[4];
  18. public byte Version, Format, Endianness, IntSize;
  19. public byte PointerSize, InstructionSize;
  20. public byte NumberSize, IntegralNumber;
  21. public fixed byte Tail[6];
  22. public const int Size = 18;
  23. public Header(bool isLittleEndian)
  24. {
  25. fixed (byte* signature = Signature)
  26. {
  27. LuaSignature.CopyTo(new(signature, 4));
  28. }
  29. Version = (Constants.VersionMajor << 4) | Constants.VersionMinor;
  30. Format = 0;
  31. Endianness = (byte)(isLittleEndian ? 1 : 0);
  32. IntSize = 4;
  33. PointerSize = (byte)sizeof(IntPtr);
  34. InstructionSize = 4;
  35. NumberSize = 8;
  36. IntegralNumber = 0;
  37. fixed (byte* tail = Tail)
  38. {
  39. LuaTail.CopyTo(new(tail, 6));
  40. }
  41. }
  42. public void Validate(ReadOnlySpan<char> name)
  43. {
  44. fixed (byte* signature = Signature)
  45. {
  46. if (!LuaSignature.SequenceEqual(new(signature, 4)))
  47. {
  48. throw new LuaUndumpException($"{name.ToString()}: is not a precompiled chunk");
  49. }
  50. }
  51. var major = Version >> 4;
  52. var minor = Version & 0xF;
  53. if (major != Constants.VersionMajor || minor != Constants.VersionMinor)
  54. {
  55. throw new LuaUndumpException($"{name.ToString()}: version mismatch in precompiled chunk {major}.{minor} != {Constants.VersionMajor}.{Constants.VersionMinor}");
  56. }
  57. if (IntSize != 4 || Format != 0 || IntegralNumber != 0 || PointerSize is not (4 or 8) || InstructionSize != 4 || NumberSize != 8)
  58. {
  59. goto ErrIncompatible;
  60. }
  61. fixed (byte* tail = Tail)
  62. {
  63. if (!LuaTail.SequenceEqual(new(tail, 6)))
  64. {
  65. goto ErrIncompatible;
  66. }
  67. }
  68. return;
  69. ErrIncompatible:
  70. throw new LuaUndumpException($"{name.ToString()}: incompatible precompiled chunk");
  71. }
  72. }
  73. unsafe ref struct DumpState(IBufferWriter<byte> writer, bool reversedEndian)
  74. {
  75. public readonly IBufferWriter<byte> Writer = writer;
  76. Span<byte> unWritten;
  77. void Write(ReadOnlySpan<byte> span)
  78. {
  79. var toWrite = span;
  80. var remaining = unWritten.Length;
  81. if (span.Length > remaining)
  82. {
  83. span[..remaining].CopyTo(unWritten);
  84. Writer.Advance(remaining);
  85. toWrite = span[remaining..];
  86. unWritten = Writer.GetSpan(toWrite.Length);
  87. }
  88. toWrite.CopyTo(unWritten);
  89. Writer.Advance(toWrite.Length);
  90. unWritten = unWritten[toWrite.Length..];
  91. }
  92. public bool IsReversedEndian => reversedEndian;
  93. void DumpHeader()
  94. {
  95. Header header = new(BitConverter.IsLittleEndian ^ IsReversedEndian);
  96. Write(new(&header, Header.Size));
  97. }
  98. public void Dump(Prototype prototype)
  99. {
  100. if (unWritten.Length == 0)
  101. {
  102. unWritten = Writer.GetSpan(Header.Size + 32);
  103. }
  104. DumpHeader();
  105. DumpFunction(prototype);
  106. }
  107. void DumpFunction(Prototype prototype)
  108. {
  109. WriteInt(prototype.LineDefined); // 4
  110. WriteInt(prototype.LastLineDefined); // 4
  111. WriteByte((byte)prototype.ParameterCount); // 1
  112. WriteByte((byte)prototype.MaxStackSize); // 1
  113. WriteByte((byte)(prototype.HasVariableArguments ? 1 : 0)); // 1
  114. WriteIntSpanWithLength(MemoryMarshal.Cast<Instruction, int>(prototype.Code)); // 4
  115. WriteConstants(prototype.Constants); // 4
  116. WritePrototypes(prototype.ChildPrototypes); // 4
  117. WriteUpValues(prototype.UpValues); // 4
  118. // Debug
  119. WriteString(prototype.ChunkName);
  120. WriteIntSpanWithLength(prototype.LineInfo);
  121. WriteLocalVariables(prototype.LocalVariables);
  122. WriteInt(prototype.UpValues.Length);
  123. foreach (var desc in prototype.UpValues)
  124. {
  125. WriteString(desc.Name);
  126. }
  127. }
  128. void WriteInt(int v)
  129. {
  130. if (reversedEndian)
  131. {
  132. v = BinaryPrimitives.ReverseEndianness(v);
  133. }
  134. Write(new(&v, sizeof(int)));
  135. }
  136. void WriteLong(long v)
  137. {
  138. if (reversedEndian)
  139. {
  140. v = BinaryPrimitives.ReverseEndianness(v);
  141. }
  142. Write(new(&v, sizeof(long)));
  143. }
  144. void WriteByte(byte v)
  145. {
  146. Write(new(&v, sizeof(byte)));
  147. }
  148. void WriteDouble(double v)
  149. {
  150. var l = BitConverter.DoubleToInt64Bits(v);
  151. WriteLong(l);
  152. }
  153. void WriteIntSpanWithLength(ReadOnlySpan<int> v)
  154. {
  155. WriteInt(v.Length);
  156. if (IsReversedEndian)
  157. {
  158. foreach (var i in v)
  159. {
  160. var reversed = BinaryPrimitives.ReverseEndianness(i);
  161. Write(new(&reversed, 4));
  162. }
  163. }
  164. else
  165. {
  166. Write(MemoryMarshal.Cast<int, byte>(v));
  167. }
  168. }
  169. void WriteBool(bool v)
  170. {
  171. WriteByte(v ? (byte)1 : (byte)0);
  172. }
  173. void WriteString(string v)
  174. {
  175. var bytes = Encoding.UTF8.GetBytes(v);
  176. var len = bytes.Length;
  177. if (bytes.Length != 0)
  178. {
  179. len++;
  180. }
  181. if (sizeof(IntPtr) == 8)
  182. {
  183. WriteLong(len);
  184. }
  185. else
  186. {
  187. WriteInt(len);
  188. }
  189. if (len != 0)
  190. {
  191. Write(bytes);
  192. WriteByte(0);
  193. }
  194. }
  195. void WriteConstants(ReadOnlySpan<LuaValue> constants)
  196. {
  197. WriteInt(constants.Length);
  198. foreach (var c in constants)
  199. {
  200. WriteByte((byte)c.Type);
  201. switch (c.Type)
  202. {
  203. case LuaValueType.Nil: break;
  204. case LuaValueType.Boolean:
  205. WriteBool(c.UnsafeReadDouble() != 0);
  206. break;
  207. case LuaValueType.Number:
  208. WriteDouble(c.UnsafeReadDouble());
  209. break;
  210. case LuaValueType.String:
  211. WriteString(c.UnsafeRead<string>());
  212. break;
  213. }
  214. }
  215. }
  216. void WritePrototypes(ReadOnlySpan<Prototype> prototypes)
  217. {
  218. WriteInt(prototypes.Length);
  219. foreach (var p in prototypes)
  220. {
  221. DumpFunction(p);
  222. }
  223. }
  224. void WriteLocalVariables(ReadOnlySpan<LocalVariable> localVariables)
  225. {
  226. WriteInt(localVariables.Length);
  227. foreach (var v in localVariables)
  228. {
  229. WriteString(v.Name);
  230. WriteInt(v.StartPc);
  231. WriteInt(v.EndPc);
  232. }
  233. }
  234. void WriteUpValues(ReadOnlySpan<UpValueDesc> upValues)
  235. {
  236. WriteInt(upValues.Length);
  237. foreach (var u in upValues)
  238. {
  239. WriteBool(u.IsLocal);
  240. WriteByte((byte)u.Index);
  241. }
  242. }
  243. }
  244. unsafe ref struct UndumpState(ReadOnlySpan<byte> span, ReadOnlySpan<char> name, StringInternPool internPool)
  245. {
  246. public ReadOnlySpan<byte> Unread = span;
  247. bool otherEndian;
  248. int pointerSize;
  249. readonly ReadOnlySpan<char> name = name;
  250. void Throw(string why)
  251. {
  252. throw new LuaUndumpException($"{name.ToString()}: {why} precompiled chunk");
  253. }
  254. void ThrowTooShort()
  255. {
  256. Throw("truncate");
  257. }
  258. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  259. internal void Read(Span<byte> dst)
  260. {
  261. if (Unread.Length < dst.Length)
  262. {
  263. ThrowTooShort();
  264. }
  265. Unread[..dst.Length].CopyTo(dst);
  266. Unread = Unread[dst.Length..];
  267. }
  268. byte ReadByte()
  269. {
  270. if (0 < Unread.Length)
  271. {
  272. var b = Unread[0];
  273. Unread = Unread[1..];
  274. return b;
  275. }
  276. ThrowTooShort();
  277. return 0;
  278. }
  279. bool ReadBool()
  280. {
  281. if (0 < Unread.Length)
  282. {
  283. var b = Unread[0];
  284. Unread = Unread[1..];
  285. return b != 0;
  286. }
  287. ThrowTooShort();
  288. return false;
  289. }
  290. int ReadInt()
  291. {
  292. var i = 0;
  293. Span<byte> span = new(&i, sizeof(int));
  294. Read(span);
  295. if (otherEndian)
  296. {
  297. i = BinaryPrimitives.ReverseEndianness(i);
  298. }
  299. return i;
  300. }
  301. long ReadLong()
  302. {
  303. long i = 0;
  304. Span<byte> span = new(&i, sizeof(long));
  305. Read(span);
  306. if (otherEndian)
  307. {
  308. i = BinaryPrimitives.ReverseEndianness(i);
  309. }
  310. return i;
  311. }
  312. double ReadDouble()
  313. {
  314. var i = ReadLong();
  315. return *(double*)&i;
  316. }
  317. public Prototype Undump()
  318. {
  319. Header h = default;
  320. Span<byte> span = new(&h, sizeof(Header));
  321. Read(span);
  322. h.Validate(name);
  323. otherEndian = BitConverter.IsLittleEndian ^ (h.Endianness == 1);
  324. pointerSize = h.PointerSize;
  325. return UndumpFunction();
  326. }
  327. Prototype UndumpFunction()
  328. {
  329. var lineDefined = ReadInt(); // 4
  330. var lastLineDefined = ReadInt(); // 4
  331. var parameterCount = ReadByte(); // 1
  332. var maxStackSize = ReadByte(); // 1
  333. var isVarArg = ReadByte() == 1; // 1
  334. var codeLength = ReadInt();
  335. var code = new Instruction[codeLength];
  336. ReadInToIntSpan(MemoryMarshal.Cast<Instruction, int>(code));
  337. var constants = ReadConstants();
  338. var prototypes = ReadPrototypes();
  339. var upValues = ReadUpValues();
  340. // Debug
  341. var source = ReadString();
  342. var lineInfoLength = ReadInt();
  343. var lineInfo = new int[lineInfoLength];
  344. ReadInToIntSpan(lineInfo.AsSpan());
  345. var localVariables = ReadLocalVariables();
  346. var upValueCount = ReadInt();
  347. Debug.Assert(upValueCount == upValues.Length, $"upvalue count mismatch: {upValueCount} != {upValues.Length}");
  348. foreach (ref var desc in upValues.AsSpan())
  349. {
  350. var name = ReadString();
  351. desc.Name = name;
  352. }
  353. return new(source, lineDefined, lastLineDefined, parameterCount, maxStackSize, isVarArg, constants, code, prototypes, lineInfo, localVariables, upValues);
  354. }
  355. void ReadInToIntSpan(Span<int> toWrite)
  356. {
  357. for (var i = 0; i < toWrite.Length; i++)
  358. {
  359. toWrite[i] = ReadInt();
  360. }
  361. }
  362. string ReadString()
  363. {
  364. var len = pointerSize == 4 ? ReadInt() : (int)ReadLong();
  365. if (len == 0)
  366. {
  367. return "";
  368. }
  369. len--;
  370. var arrayPooled = ArrayPool<byte>.Shared.Rent(len);
  371. char[]? charArrayPooled = null;
  372. try
  373. {
  374. var span = arrayPooled.AsSpan(0, len);
  375. Read(span);
  376. var l = ReadByte();
  377. Debug.Assert(l == 0);
  378. var chars = len <= 128 ? stackalloc char[len * 2] : (charArrayPooled = ArrayPool<char>.Shared.Rent(len * 2));
  379. var count = Encoding.UTF8.GetChars(span, chars);
  380. return internPool.Intern(chars[..count]);
  381. }
  382. finally
  383. {
  384. ArrayPool<byte>.Shared.Return(arrayPooled);
  385. if (charArrayPooled != null)
  386. {
  387. ArrayPool<char>.Shared.Return(charArrayPooled);
  388. }
  389. }
  390. }
  391. LuaValue[] ReadConstants()
  392. {
  393. var count = ReadInt();
  394. var constants = new LuaValue[count];
  395. for (var i = 0; i < count; i++)
  396. {
  397. var type = (LuaValueType)ReadByte();
  398. switch (type)
  399. {
  400. case LuaValueType.Nil: break;
  401. case LuaValueType.Boolean:
  402. constants[i] = ReadByte() == 1;
  403. break;
  404. case LuaValueType.Number:
  405. constants[i] = ReadDouble();
  406. break;
  407. case LuaValueType.String:
  408. constants[i] = ReadString();
  409. break;
  410. }
  411. }
  412. return constants;
  413. }
  414. Prototype[] ReadPrototypes()
  415. {
  416. var count = ReadInt();
  417. var prototypes = count != 0 ? new Prototype[count] : [];
  418. for (var i = 0; i < count; i++)
  419. {
  420. prototypes[i] = UndumpFunction();
  421. }
  422. return prototypes;
  423. }
  424. LocalVariable[] ReadLocalVariables()
  425. {
  426. var count = ReadInt();
  427. var localVariables = new LocalVariable[count];
  428. for (var i = 0; i < count; i++)
  429. {
  430. var name = ReadString();
  431. var startPc = ReadInt();
  432. var endPc = ReadInt();
  433. localVariables[i] = new() { Name = name, StartPc = startPc, EndPc = endPc };
  434. }
  435. return localVariables;
  436. }
  437. UpValueDesc[] ReadUpValues()
  438. {
  439. var count = ReadInt();
  440. Debug.Assert(count < 100, $" too many upvalues :{count}");
  441. var upValues = new UpValueDesc[count];
  442. for (var i = 0; i < count; i++)
  443. {
  444. var isLocal = ReadBool();
  445. var index = ReadByte();
  446. upValues[i] = new() { IsLocal = isLocal, Index = index };
  447. }
  448. return upValues;
  449. }
  450. }