-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
221 changes: 221 additions & 0 deletions
221
src/IKVM.ByteCode.Tests/Writing/InstructionEncoderTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
using System.Buffers; | ||
|
||
using FluentAssertions; | ||
|
||
using IKVM.ByteCode.Buffers; | ||
using IKVM.ByteCode.Writing; | ||
|
||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
|
||
namespace IKVM.ByteCode.Tests.Writing | ||
{ | ||
|
||
[TestClass] | ||
public class InstructionEncoderTests | ||
{ | ||
|
||
[TestMethod] | ||
public void CanMarkShortLabel() | ||
{ | ||
var blob = new BlobBuilder(); | ||
var code = new InstructionEncoder(blob); | ||
var label = code.DefineLabel(); | ||
|
||
var branchOffset = code.Offset; | ||
code.Branch(OpCode._goto, label); | ||
|
||
var labelOffset = code.Offset; | ||
code.MarkLabel(label); | ||
code.OpCode(OpCode._return); | ||
|
||
var b = new SequenceReader<byte>(new ReadOnlySequence<byte>(blob.ToArray())); | ||
|
||
// start with instruction | ||
b.TryRead(out byte opcode).Should().BeTrue(); | ||
opcode.Should().Be((byte)OpCode._goto); | ||
|
||
// argument of branch instruction should be offset to label | ||
b.TryReadBigEndian(out short argument).Should().BeTrue(); | ||
argument.Should().Be(checked((short)(labelOffset - branchOffset))); | ||
|
||
// last value should be return instruction | ||
b.TryRead(out byte returnInstruction).Should().BeTrue(); | ||
returnInstruction.Should().Be((byte)OpCode._return); | ||
} | ||
|
||
[TestMethod] | ||
public void CanMarkLongLabel() | ||
{ | ||
var blob = new BlobBuilder(); | ||
var code = new InstructionEncoder(blob); | ||
var label = code.DefineLabel(); | ||
|
||
var branchOffset = code.Offset; | ||
code.Branch(OpCode._goto_w, label); | ||
|
||
var labelOffset = code.Offset; | ||
code.MarkLabel(label); | ||
code.OpCode(OpCode._return); | ||
|
||
var b = new SequenceReader<byte>(new ReadOnlySequence<byte>(blob.ToArray())); | ||
|
||
// start with instruction | ||
b.TryRead(out byte opcode).Should().BeTrue(); | ||
opcode.Should().Be((byte)OpCode._goto_w); | ||
|
||
// argument of branch instruction should be offset to label | ||
b.TryReadBigEndian(out int argument).Should().BeTrue(); | ||
argument.Should().Be(labelOffset - branchOffset); | ||
|
||
// last value should be return instruction | ||
b.TryRead(out byte returnInstruction).Should().BeTrue(); | ||
returnInstruction.Should().Be((byte)OpCode._return); | ||
} | ||
|
||
[TestMethod] | ||
public void CanRepeatLabel() | ||
{ | ||
var blob = new BlobBuilder(); | ||
new InstructionEncoder(blob) | ||
.DefineLabel(out var label) | ||
.Label(label, 4) | ||
.Label(label, 4) | ||
.Label(label, 4) | ||
.Label(label, 4) | ||
.MarkLabel(label); | ||
|
||
var b = new SequenceReader<byte>(new ReadOnlySequence<byte>(blob.ToArray())); | ||
|
||
b.TryReadBigEndian(out int l1).Should().BeTrue(); | ||
b.TryReadBigEndian(out int l2).Should().BeTrue(); | ||
b.TryReadBigEndian(out int l3).Should().BeTrue(); | ||
b.TryReadBigEndian(out int l4).Should().BeTrue(); | ||
|
||
// labels values should be progrssively lower as we approach the end | ||
l1.Should().Be(16); | ||
l2.Should().Be(12); | ||
l3.Should().Be(8); | ||
l4.Should().Be(4); | ||
} | ||
|
||
[TestMethod] | ||
public void CanEncodeTableSwitch() | ||
{ | ||
var blob = new BlobBuilder(); | ||
var code = new InstructionEncoder(blob); | ||
|
||
var defaultLabel = code.DefineLabel(); | ||
var case1Label = code.DefineLabel(); | ||
var case2Label = code.DefineLabel(); | ||
|
||
// tableswitch | ||
var tableOffset = code.Offset; | ||
code.TableSwitch(defaultLabel, 0, t => t.Case(case1Label).Case(case2Label)); | ||
|
||
// return code, all labels arrive here | ||
var labelOffset = code.Offset; | ||
code.MarkLabel(defaultLabel); | ||
code.MarkLabel(case1Label); | ||
code.MarkLabel(case2Label); | ||
code.OpCode(OpCode._return); | ||
|
||
var b = new SequenceReader<byte>(new ReadOnlySequence<byte>(blob.ToArray())); | ||
|
||
// start with instruction | ||
b.TryRead(out byte opcode).Should().BeTrue(); | ||
opcode.Should().Be((byte)OpCode._tableswitch); | ||
|
||
// advance past padding | ||
b.TryRead(out byte _).Should().BeTrue(); | ||
b.TryRead(out byte _).Should().BeTrue(); | ||
b.TryRead(out byte _).Should().BeTrue(); | ||
|
||
// default value should be 32 bit signed integer which is offset from instruction to label | ||
b.TryReadBigEndian(out int defaultValue).Should().BeTrue(); | ||
defaultValue.Should().Be(labelOffset - tableOffset); | ||
|
||
// low should be starting value: 0 | ||
b.TryReadBigEndian(out int low).Should().BeTrue(); | ||
low.Should().Be(0); | ||
|
||
// high should be ending value: 1 | ||
b.TryReadBigEndian(out int high).Should().BeTrue(); | ||
high.Should().Be(1); | ||
|
||
// first case should be distance from instruction to return | ||
b.TryReadBigEndian(out int case1Offset).Should().BeTrue(); | ||
case1Offset.Should().Be(labelOffset - tableOffset); | ||
|
||
// second case should be distance from instruction to return | ||
b.TryReadBigEndian(out int case2Offset).Should().BeTrue(); | ||
case2Offset.Should().Be(labelOffset - tableOffset); | ||
|
||
// last value should be return instruction | ||
b.TryRead(out byte returnInstruction).Should().BeTrue(); | ||
returnInstruction.Should().Be((byte)OpCode._return); | ||
} | ||
|
||
[TestMethod] | ||
public void CanEncodeLookupSwitch() | ||
{ | ||
var blob = new BlobBuilder(); | ||
var code = new InstructionEncoder(blob); | ||
|
||
var defaultLabel = code.DefineLabel(); | ||
var case1Label = code.DefineLabel(); | ||
var case2Label = code.DefineLabel(); | ||
|
||
// tableswitch | ||
var lookupOffset = code.Offset; | ||
code.LookupSwitch(defaultLabel, e => e.Case(1, case1Label).Case(2, case2Label)); | ||
|
||
// return code, all labels arrive here | ||
var labelOffset = code.Offset; | ||
code.MarkLabel(defaultLabel); | ||
code.MarkLabel(case1Label); | ||
code.MarkLabel(case2Label); | ||
code.OpCode(OpCode._return); | ||
|
||
var b = new SequenceReader<byte>(new ReadOnlySequence<byte>(blob.ToArray())); | ||
|
||
// start with instruction | ||
b.TryRead(out byte opcode).Should().BeTrue(); | ||
opcode.Should().Be((byte)OpCode._lookupswitch); | ||
|
||
// advance past padding | ||
b.TryRead(out byte _).Should().BeTrue(); | ||
b.TryRead(out byte _).Should().BeTrue(); | ||
b.TryRead(out byte _).Should().BeTrue(); | ||
|
||
// default value should be 32 bit signed integer which is offset from instruction to label | ||
b.TryReadBigEndian(out int defaultValue).Should().BeTrue(); | ||
defaultValue.Should().Be(labelOffset - lookupOffset); | ||
|
||
// npairs, count of pairs | ||
b.TryReadBigEndian(out int npairs).Should().BeTrue(); | ||
npairs.Should().Be(2); | ||
|
||
// first case key | ||
b.TryReadBigEndian(out int case1Key).Should().BeTrue(); | ||
case1Key.Should().Be(1); | ||
|
||
// first case offset should be distance from instruction to return | ||
b.TryReadBigEndian(out int case1Offset).Should().BeTrue(); | ||
case1Offset.Should().Be(labelOffset - lookupOffset); | ||
|
||
// second case key | ||
b.TryReadBigEndian(out int case2Key).Should().BeTrue(); | ||
case2Key.Should().Be(2); | ||
|
||
// second case offset should be distance from instruction to return | ||
b.TryReadBigEndian(out int case2Offset).Should().BeTrue(); | ||
case2Offset.Should().Be(labelOffset - lookupOffset); | ||
|
||
// last value should be return instruction | ||
b.TryRead(out byte returnInstruction).Should().BeTrue(); | ||
returnInstruction.Should().Be((byte)OpCode._return); | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace IKVM.ByteCode.Writing | ||
{ | ||
|
||
public delegate void EncoderAction<T>(in T encoder) where T : struct; | ||
|
||
} |
64 changes: 64 additions & 0 deletions
64
src/IKVM.ByteCode/Writing/LookupSwitchInstructionEncoder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using System; | ||
using System.Buffers.Binary; | ||
|
||
using IKVM.ByteCode.Buffers; | ||
|
||
namespace IKVM.ByteCode.Writing | ||
{ | ||
|
||
/// <summary> | ||
/// Encodes a lookupswitch instruction. | ||
/// </summary> | ||
public struct LookupSwitchInstructionEncoder | ||
{ | ||
|
||
readonly InstructionEncoder _encoder; | ||
readonly int _offset; | ||
readonly Blob _npairsBlob; | ||
int _npairs; | ||
long _lastKey = long.MinValue; | ||
|
||
/// <summary> | ||
/// Initializes a new instance. | ||
/// </summary> | ||
/// <param name="encoder"></param> | ||
/// <param name="defaultLabel"></param> | ||
/// <param name="low"></param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
public LookupSwitchInstructionEncoder(InstructionEncoder encoder, LabelHandle defaultLabel) | ||
{ | ||
_encoder = encoder ?? throw new ArgumentNullException(nameof(encoder)); | ||
|
||
// header of table switch is instruction, alignment, then default | ||
_offset = _encoder.Offset; | ||
_encoder.OpCode(OpCode._lookupswitch); | ||
_encoder.Align(4); | ||
_encoder.Label(defaultLabel, 4, _offset); | ||
|
||
// reserve space for high value and start at low | ||
_npairs = 0; | ||
_npairsBlob = encoder.ReserveBytes(4); | ||
BinaryPrimitives.WriteInt32BigEndian(_npairsBlob.GetBytes(), _npairs); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a new case to the lookup switch. | ||
/// </summary> | ||
/// <param name="key"></param> | ||
/// <param name="label"></param> | ||
/// <returns></returns> | ||
public LookupSwitchInstructionEncoder Case(int key, LabelHandle label) | ||
{ | ||
if (key < _lastKey) | ||
throw new ArgumentOutOfRangeException("Key must be greater than previos key.", nameof(key)); | ||
|
||
_lastKey = key; | ||
_encoder.WriteInt32(key); | ||
_encoder.Label(label, 4, _offset); | ||
BinaryPrimitives.WriteInt32BigEndian(_npairsBlob.GetBytes(), ++_npairs); | ||
return this; | ||
} | ||
|
||
} | ||
|
||
} |
72 changes: 72 additions & 0 deletions
72
src/IKVM.ByteCode/Writing/TableSwitchInstructionEncoder.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
using System; | ||
using System.Buffers.Binary; | ||
|
||
using IKVM.ByteCode.Buffers; | ||
|
||
namespace IKVM.ByteCode.Writing | ||
{ | ||
|
||
/// <summary> | ||
/// Encodes multiple table switch arguments. | ||
/// </summary> | ||
public class TableSwitchInstructionEncoder | ||
{ | ||
|
||
readonly InstructionEncoder _encoder; | ||
readonly int _offset; | ||
readonly int _low; | ||
readonly Blob _highBlob; | ||
int _high; | ||
|
||
/// <summary> | ||
/// Initializes a new instance. | ||
/// </summary> | ||
/// <param name="encoder"></param> | ||
/// <param name="defaultLabel"></param> | ||
/// <param name="low"></param> | ||
/// <exception cref="ArgumentNullException"></exception> | ||
public TableSwitchInstructionEncoder(InstructionEncoder encoder, LabelHandle defaultLabel, int low) | ||
{ | ||
_encoder = encoder ?? throw new ArgumentNullException(nameof(encoder)); | ||
|
||
// header of table switch is instruction, alignment, then default | ||
_offset = _encoder.Offset; | ||
_encoder.OpCode(OpCode._tableswitch); | ||
_encoder.Align(4); | ||
_encoder.Label(defaultLabel, 4, _offset); | ||
|
||
// write low value | ||
_low = low; | ||
encoder.WriteInt32(_low); | ||
|
||
// reserve space for high value and start at low | ||
_high = low - 1; | ||
_highBlob = encoder.ReserveBytes(4); | ||
BinaryPrimitives.WriteInt32BigEndian(_highBlob.GetBytes(), _high); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a new case to the table switch. | ||
/// </summary> | ||
/// <param name="label"></param> | ||
/// <returns></returns> | ||
public TableSwitchInstructionEncoder Case(LabelHandle label) | ||
{ | ||
_encoder.Label(label, 4, _offset); | ||
BinaryPrimitives.WriteInt32BigEndian(_highBlob.GetBytes(), ++_high); | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Validates whether we have written correct values. | ||
/// </summary> | ||
/// <exception cref="InvalidOperationException"></exception> | ||
public void Validate() | ||
{ | ||
if (_high < _low) | ||
throw new InvalidOperationException("TableSwitch requires at least one item."); | ||
} | ||
|
||
} | ||
|
||
} |