Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions src/coreclr/tools/Common/TypeSystem/IL/ILScalarSemantics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;

namespace Internal.IL
{
// Shared by concrete IL interpretation (type preinitialization) and symbolic interpretation (LLVM IR).
internal static class ILScalarSemantics
{
public static ILOpcode NormalizeBranch(ILOpcode opcode)
=> opcode >= ILOpcode.br && opcode <= ILOpcode.blt_un ? opcode - ILOpcode.br + ILOpcode.br_s : opcode;

public static ulong Mask(int bits) => bits == 64 ? ulong.MaxValue : (1UL << bits) - 1;

public static long Signed(ulong value, int bits)
=> unchecked((long)(value << (64 - bits))) >> (64 - bits);

public static bool IsDivisionSafe(ILOpcode opcode, int bits, ulong? left, ulong? right)
{
if (!right.HasValue || (right.Value & Mask(bits)) == 0)
return false;
return opcode is ILOpcode.div_un or ILOpcode.rem_un ||
(right.Value & Mask(bits)) != Mask(bits) ||
(left.HasValue && (left.Value & Mask(bits)) != (1UL << (bits - 1)));
}

public static bool TryBinary(ILOpcode opcode, int bits, ulong left, ulong right, out ulong result)
{
ulong mask = Mask(bits);
left &= mask;
right &= mask;
result = 0;
if (opcode is ILOpcode.div or ILOpcode.div_un or ILOpcode.rem or ILOpcode.rem_un &&
!IsDivisionSafe(opcode, bits, left, right))
return false;

unchecked
{
result = opcode switch
{
ILOpcode.add => left + right,
ILOpcode.sub => left - right,
ILOpcode.mul => left * right,
ILOpcode.and => left & right,
ILOpcode.or => left | right,
ILOpcode.xor => left ^ right,
ILOpcode.shl => left << ((int)right & (bits - 1)),
ILOpcode.shr => (ulong)(Signed(left, bits) >> ((int)right & (bits - 1))),
ILOpcode.shr_un => left >> ((int)right & (bits - 1)),
ILOpcode.div => (ulong)(Signed(left, bits) / Signed(right, bits)),
ILOpcode.rem => (ulong)(Signed(left, bits) % Signed(right, bits)),
ILOpcode.div_un => left / right,
ILOpcode.rem_un => left % right,
_ => throw new ArgumentOutOfRangeException(nameof(opcode)),
};
}
result &= mask;
return true;
}

public static string IntegerPredicate(ILOpcode opcode) => NormalizeBranch(opcode) switch
{
ILOpcode.ceq or ILOpcode.beq_s => "eq",
ILOpcode.bne_un_s => "ne",
ILOpcode.clt or ILOpcode.blt_s => "slt",
ILOpcode.cgt or ILOpcode.bgt_s => "sgt",
ILOpcode.ble_s => "sle",
ILOpcode.bge_s => "sge",
ILOpcode.clt_un or ILOpcode.blt_un_s => "ult",
ILOpcode.cgt_un or ILOpcode.bgt_un_s => "ugt",
ILOpcode.ble_un_s => "ule",
ILOpcode.bge_un_s => "uge",
_ => throw new ArgumentOutOfRangeException(nameof(opcode)),
};

// The .un comparisons are unordered for floating point, including NaNs.
public static string FloatPredicate(ILOpcode opcode) => NormalizeBranch(opcode) switch
{
ILOpcode.ceq or ILOpcode.beq_s => "oeq",
ILOpcode.bne_un_s => "une",
ILOpcode.clt or ILOpcode.blt_s => "olt",
ILOpcode.cgt or ILOpcode.bgt_s => "ogt",
ILOpcode.ble_s => "ole",
ILOpcode.bge_s => "oge",
ILOpcode.clt_un or ILOpcode.blt_un_s => "ult",
ILOpcode.cgt_un or ILOpcode.bgt_un_s => "ugt",
ILOpcode.ble_un_s => "ule",
ILOpcode.bge_un_s => "uge",
_ => throw new ArgumentOutOfRangeException(nameof(opcode)),
};

public static bool Compare(ILOpcode opcode, int bits, ulong left, ulong right)
{
left &= Mask(bits);
right &= Mask(bits);
return IntegerPredicate(opcode) switch
{
"eq" => left == right, "ne" => left != right,
"slt" => Signed(left, bits) < Signed(right, bits),
"sgt" => Signed(left, bits) > Signed(right, bits),
"sle" => Signed(left, bits) <= Signed(right, bits),
"sge" => Signed(left, bits) >= Signed(right, bits),
"ult" => left < right, "ugt" => left > right,
"ule" => left <= right, "uge" => left >= right,
_ => throw new InvalidOperationException(),
};
}

public static bool Compare(ILOpcode opcode, double left, double right) => FloatPredicate(opcode) switch
{
"oeq" => left == right, "une" => left != right,
"olt" => left < right, "ogt" => left > right,
"ole" => left <= right, "oge" => left >= right,
"ult" => !(left >= right), "ugt" => !(left <= right),
"ule" => !(left > right), "uge" => !(left < right),
_ => throw new InvalidOperationException(),
};
}
}
83 changes: 10 additions & 73 deletions src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/TypePreinit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1182,9 +1182,7 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
ThrowHelper.ThrowInvalidProgramException();
}

ILOpcode normalizedOpcode = opcode >= ILOpcode.br ?
opcode - ILOpcode.br + ILOpcode.br_s :
opcode;
ILOpcode normalizedOpcode = ILScalarSemantics.NormalizeBranch(opcode);

bool branchTaken;
if (normalizedOpcode == ILOpcode.brtrue_s || normalizedOpcode == ILOpcode.brfalse_s)
Expand Down Expand Up @@ -1213,54 +1211,15 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method

if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int32)
{
branchTaken = normalizedOpcode switch
{
ILOpcode.blt_s => value1.Value.AsInt32() < value2.Value.AsInt32(),
ILOpcode.blt_un_s => (uint)value1.Value.AsInt32() < (uint)value2.Value.AsInt32(),
ILOpcode.bgt_s => value1.Value.AsInt32() > value2.Value.AsInt32(),
ILOpcode.bgt_un_s => (uint)value1.Value.AsInt32() > (uint)value2.Value.AsInt32(),
ILOpcode.bge_s => value1.Value.AsInt32() >= value2.Value.AsInt32(),
ILOpcode.bge_un_s => (uint)value1.Value.AsInt32() >= (uint)value2.Value.AsInt32(),
ILOpcode.beq_s => value1.Value.AsInt32() == value2.Value.AsInt32(),
ILOpcode.bne_un_s => value1.Value.AsInt32() != value2.Value.AsInt32(),
ILOpcode.ble_s => value1.Value.AsInt32() <= value2.Value.AsInt32(),
ILOpcode.ble_un_s => (uint)value1.Value.AsInt32() <= (uint)value2.Value.AsInt32(),
_ => throw new NotImplementedException() // unreachable
};
branchTaken = ILScalarSemantics.Compare(normalizedOpcode, 32, (uint)value1.Value.AsInt32(), (uint)value2.Value.AsInt32());
}
else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
{
branchTaken = normalizedOpcode switch
{
ILOpcode.blt_s => value1.Value.AsInt64() < value2.Value.AsInt64(),
ILOpcode.blt_un_s => (ulong)value1.Value.AsInt64() < (ulong)value2.Value.AsInt64(),
ILOpcode.bgt_s => value1.Value.AsInt64() > value2.Value.AsInt64(),
ILOpcode.bgt_un_s => (ulong)value1.Value.AsInt64() > (ulong)value2.Value.AsInt64(),
ILOpcode.bge_s => value1.Value.AsInt64() >= value2.Value.AsInt64(),
ILOpcode.bge_un_s => (ulong)value1.Value.AsInt64() >= (ulong)value2.Value.AsInt64(),
ILOpcode.beq_s => value1.Value.AsInt64() == value2.Value.AsInt64(),
ILOpcode.bne_un_s => value1.Value.AsInt64() != value2.Value.AsInt64(),
ILOpcode.ble_s => value1.Value.AsInt64() <= value2.Value.AsInt64(),
ILOpcode.ble_un_s => (ulong)value1.Value.AsInt64() <= (ulong)value2.Value.AsInt64(),
_ => throw new NotImplementedException() // unreachable
};
branchTaken = ILScalarSemantics.Compare(normalizedOpcode, 64, (ulong)value1.Value.AsInt64(), (ulong)value2.Value.AsInt64());
}
else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
{
branchTaken = normalizedOpcode switch
{
ILOpcode.blt_s => value1.Value.AsDouble() < value2.Value.AsDouble(),
ILOpcode.blt_un_s => !(value1.Value.AsDouble() >= value2.Value.AsDouble()),
ILOpcode.bgt_s => value1.Value.AsDouble() > value2.Value.AsDouble(),
ILOpcode.bgt_un_s => !(value1.Value.AsDouble() <= value2.Value.AsDouble()),
ILOpcode.bge_s => value1.Value.AsDouble() >= value2.Value.AsDouble(),
ILOpcode.bge_un_s => !(value1.Value.AsDouble() < value2.Value.AsDouble()),
ILOpcode.beq_s => value1.Value.AsDouble() == value2.Value.AsDouble(),
ILOpcode.bne_un_s => value1.Value.AsDouble() != value2.Value.AsDouble(),
ILOpcode.ble_s => value1.Value.AsDouble() <= value2.Value.AsDouble(),
ILOpcode.ble_un_s => !(value1.Value.AsDouble() > value2.Value.AsDouble()),
_ => throw new NotImplementedException() // unreachable
};
branchTaken = ILScalarSemantics.Compare(normalizedOpcode, value1.Value.AsDouble(), value2.Value.AsDouble());
}
else
{
Expand Down Expand Up @@ -1459,21 +1418,10 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
&& value1.Value.AsInt32() == int.MinValue && value2.Value.AsInt32() == -1)
return Status.Fail(methodIL.OwningMethod, opcode, "Overflow");

int result = opcode switch
{
ILOpcode.or => value1.Value.AsInt32() | value2.Value.AsInt32(),
ILOpcode.shl => value1.Value.AsInt32() << value2.Value.AsInt32(),
ILOpcode.add => value1.Value.AsInt32() + value2.Value.AsInt32(),
ILOpcode.sub => value1.Value.AsInt32() - value2.Value.AsInt32(),
ILOpcode.and => value1.Value.AsInt32() & value2.Value.AsInt32(),
ILOpcode.mul => value1.Value.AsInt32() * value2.Value.AsInt32(),
ILOpcode.div => value1.Value.AsInt32() / value2.Value.AsInt32(),
ILOpcode.div_un => (int)((uint)value1.Value.AsInt32() / (uint)value2.Value.AsInt32()),
ILOpcode.rem => value1.Value.AsInt32() % value2.Value.AsInt32(),
ILOpcode.rem_un => (int)((uint)value1.Value.AsInt32() % (uint)value2.Value.AsInt32()),
_ => throw new NotImplementedException(), // unreachable
};
if (!ILScalarSemantics.TryBinary(opcode, 32, (uint)value1.Value.AsInt32(), (uint)value2.Value.AsInt32(), out ulong resultBits))
return Status.Fail(methodIL.OwningMethod, opcode);

int result = unchecked((int)resultBits);
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int32, ValueTypeValue.FromInt32(result));
}
else if (value1.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64 && value2.ValueKind.WithNormalizedNativeInt(context) == StackValueKind.Int64)
Expand All @@ -1485,21 +1433,10 @@ private Status TryScanMethod(MethodIL methodIL, Value[] parameters, Stack<Method
&& value1.Value.AsInt64() == long.MinValue && value2.Value.AsInt64() == -1)
return Status.Fail(methodIL.OwningMethod, opcode, "Overflow");

long result = opcode switch
{
ILOpcode.or => value1.Value.AsInt64() | value2.Value.AsInt64(),
ILOpcode.shl => value1.Value.AsInt64() << (int)value2.Value.AsInt64(),
ILOpcode.add => value1.Value.AsInt64() + value2.Value.AsInt64(),
ILOpcode.sub => value1.Value.AsInt64() - value2.Value.AsInt64(),
ILOpcode.and => value1.Value.AsInt64() & value2.Value.AsInt64(),
ILOpcode.mul => value1.Value.AsInt64() * value2.Value.AsInt64(),
ILOpcode.div => value1.Value.AsInt64() / value2.Value.AsInt64(),
ILOpcode.div_un => (long)((ulong)value1.Value.AsInt64() / (ulong)value2.Value.AsInt64()),
ILOpcode.rem => value1.Value.AsInt64() % value2.Value.AsInt64(),
ILOpcode.rem_un => (long)((ulong)value1.Value.AsInt64() % (ulong)value2.Value.AsInt64()),
_ => throw new NotImplementedException(), // unreachable
};
if (!ILScalarSemantics.TryBinary(opcode, 64, (ulong)value1.Value.AsInt64(), (ulong)value2.Value.AsInt64(), out ulong resultBits))
return Status.Fail(methodIL.OwningMethod, opcode);

long result = unchecked((long)resultBits);
stack.Push(isNint ? StackValueKind.NativeInt : StackValueKind.Int64, ValueTypeValue.FromInt64(result));
}
else if (value1.ValueKind == StackValueKind.Float && value2.ValueKind == StackValueKind.Float)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@
<Compile Include="..\..\Common\TypeSystem\IL\ILReader.cs">
<Link>IL\ILReader.cs</Link>
</Compile>
<Compile Include="..\..\Common\TypeSystem\IL\ILScalarSemantics.cs" Link="IL\ILScalarSemantics.cs" />
<Compile Include="..\..\Common\TypeSystem\IL\StackValueKind.cs">
<Link>IL\StackValueKind.cs</Link>
</Compile>
Expand Down
Loading
Loading