Skip to content

Commit 608fac6

Browse files
Fix WriteCompressedInt32 logic by using the same code as in System.Reflection.Metadata (#958)
* Fix WriteCompressedInt32 logic by using the same code as in System.Reflection.Metadata Note: the implementation in SRM uses methods that are not available in netfx 4.0. To make the code work with all platforms supported by Mono.Cecil, I extracted the required methods. * Cleanup whitespaces --------- Co-authored-by: Jb Evain <[email protected]>
1 parent 8e1ae7b commit 608fac6

File tree

2 files changed

+88
-10
lines changed

2 files changed

+88
-10
lines changed

Mono.Cecil.PE/ByteBuffer.cs

+72-10
Original file line numberDiff line numberDiff line change
@@ -252,19 +252,81 @@ public void WriteCompressedUInt32 (uint value)
252252

253253
public void WriteCompressedInt32 (int value)
254254
{
255-
if (value >= 0) {
256-
WriteCompressedUInt32 ((uint) (value << 1));
257-
return;
255+
// extracted from System.Reflection.Metadata
256+
unchecked {
257+
const int b6 = (1 << 6) - 1;
258+
const int b13 = (1 << 13) - 1;
259+
const int b28 = (1 << 28) - 1;
260+
261+
// 0xffffffff for negative value
262+
// 0x00000000 for non-negative
263+
264+
int signMask = value >> 31;
265+
266+
if ((value & ~b6) == (signMask & ~b6)) {
267+
int n = ((value & b6) << 1) | (signMask & 1);
268+
WriteByte ((byte)n);
269+
} else if ((value & ~b13) == (signMask & ~b13)) {
270+
int n = ((value & b13) << 1) | (signMask & 1);
271+
ushort val = (ushort)(0x8000 | n);
272+
WriteUInt16 (BitConverter.IsLittleEndian ? ReverseEndianness (val) : val);
273+
} else if ((value & ~b28) == (signMask & ~b28)) {
274+
int n = ((value & b28) << 1) | (signMask & 1);
275+
uint val = 0xc0000000 | (uint)n;
276+
WriteUInt32 (BitConverter.IsLittleEndian ? ReverseEndianness (val) : val);
277+
} else {
278+
throw new ArgumentOutOfRangeException ("value", "valid range is -2^28 to 2^28 -1");
279+
}
258280
}
281+
}
282+
283+
static uint ReverseEndianness (uint value)
284+
{
285+
// extracted from .net BCL
286+
287+
// This takes advantage of the fact that the JIT can detect
288+
// ROL32 / ROR32 patterns and output the correct intrinsic.
289+
//
290+
// Input: value = [ ww xx yy zz ]
291+
//
292+
// First line generates : [ ww xx yy zz ]
293+
// & [ 00 FF 00 FF ]
294+
// = [ 00 xx 00 zz ]
295+
// ROR32(8) = [ zz 00 xx 00 ]
296+
//
297+
// Second line generates: [ ww xx yy zz ]
298+
// & [ FF 00 FF 00 ]
299+
// = [ ww 00 yy 00 ]
300+
// ROL32(8) = [ 00 yy 00 ww ]
301+
//
302+
// (sum) = [ zz yy xx ww ]
303+
//
304+
// Testing shows that throughput increases if the AND
305+
// is performed before the ROL / ROR.
306+
307+
return RotateRight (value & 0x00FF00FFu, 8) // xx zz
308+
+ RotateLeft (value & 0xFF00FF00u, 8); // ww yy
309+
}
310+
311+
// extracted from .net BCL
312+
static uint RotateRight (uint value, int offset)
313+
=> (value >> offset) | (value << (32 - offset));
314+
315+
static uint RotateLeft (uint value, int offset)
316+
=> (value << offset) | (value >> (32 - offset));
317+
318+
static ushort ReverseEndianness (ushort value)
319+
{
320+
// extracted from .net BCL
259321

260-
if (value > -0x40)
261-
value = 0x40 + value;
262-
else if (value >= -0x2000)
263-
value = 0x2000 + value;
264-
else if (value >= -0x20000000)
265-
value = 0x20000000 + value;
322+
// Don't need to AND with 0xFF00 or 0x00FF since the final
323+
// cast back to ushort will clear out all bits above [ 15 .. 00 ].
324+
// This is normally implemented via "movzx eax, ax" on the return.
325+
// Alternatively, the compiler could elide the movzx instruction
326+
// entirely if it knows the caller is only going to access "ax"
327+
// instead of "eax" / "rax" when the function returns.
266328

267-
WriteCompressedUInt32 ((uint) ((value << 1) | 1));
329+
return (ushort)((value >> 8) + (value << 8));
268330
}
269331

270332
public void WriteBytes (byte [] bytes)
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Mono.Cecil.PE;
2+
using NUnit.Framework;
3+
4+
namespace Mono.Cecil.Tests {
5+
public class ByteBufferTests {
6+
[Test]
7+
public void TestLargeIntegerCompressed ()
8+
{
9+
var testee = new ByteBuffer ();
10+
testee.WriteCompressedInt32 (-9076);
11+
testee.position = 0;
12+
var result = testee.ReadCompressedInt32 ();
13+
Assert.AreEqual (-9076, result);
14+
}
15+
}
16+
}

0 commit comments

Comments
 (0)