diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs index 1cf2787e4b6..38923ad3e55 100644 --- a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs +++ b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter.cs @@ -3999,6 +3999,17 @@ private void ImportRefAnyVal(int token) private void ImportCkFinite() { + StackEntry value = _stack.Pop(); + if (value.Type == GetWellKnownType(WellKnownType.Single)) + { + ThrowCkFinite(value.ValueForStackKind(value.Kind, _builder, false), 32, ref CkFinite32Function); + } + else + { + ThrowCkFinite(value.ValueForStackKind(value.Kind, _builder, false), 64, ref CkFinite64Function); + } + + _stack.Push(value); } private void ImportMkRefAny(int token) @@ -4201,39 +4212,67 @@ private void ThrowIfNull(LLVMValueRef entry) builder.BuildCondBr(builder.BuildICmp(LLVMIntPredicate.LLVMIntEQ, NullRefFunction.GetParam(1), LLVMValueRef.CreateConstPointerNull(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0)), "nullCheck"), throwBlock, retBlock); builder.PositionAtEnd(throwBlock); - MetadataType nullRefType = _compilation.NodeFactory.TypeSystemContext.SystemModule.GetType("System", "NullReferenceException"); + + ThrowException(builder, "ThrowHelpers", "ThrowNullReferenceException", NullRefFunction); + + builder.PositionAtEnd(retBlock); + builder.BuildRetVoid(); + } + + LLVMBasicBlockRef nextInstrBlock = default; + CallOrInvoke(false, _builder, GetCurrentTryRegion(), NullRefFunction, new List { GetShadowStack(), entry }, ref nextInstrBlock); + } + + private void ThrowCkFinite(LLVMValueRef value, int size, ref LLVMValueRef llvmCheckFunction) + { + if (llvmCheckFunction.Handle == IntPtr.Zero) + { + llvmCheckFunction = Module.AddFunction("corert.throwckfinite" + size, LLVMTypeRef.CreateFunction(LLVMTypeRef.Void, new LLVMTypeRef[] { LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), size == 32 ? LLVMTypeRef.Float : LLVMTypeRef.Double }, false)); + LLVMValueRef exponentMask; + LLVMTypeRef intTypeRef; + var builder = Context.CreateBuilder(); + var block = llvmCheckFunction.AppendBasicBlock("Block"); + builder.PositionAtEnd(block); - var arguments = new StackEntry[] { new LoadExpressionEntry(StackValueKind.ValueType, "eeType", GetEETypePointerForTypeDesc(nullRefType, true), GetEETypePtrTypeDesc()) }; + if (size == 32) + { + intTypeRef = LLVMTypeRef.Int32; + exponentMask = LLVMValueRef.CreateConstInt(intTypeRef, 0x7F800000, false); + } + else + { + intTypeRef = LLVMTypeRef.Int64; + exponentMask = LLVMValueRef.CreateConstInt(intTypeRef, 0x7FF0000000000000, false); + } - MetadataType helperType = _compilation.TypeSystemContext.SystemModule.GetKnownType("System.Runtime", RuntimeExport); - MethodDesc helperMethod = helperType.GetKnownMethod("RhNewObject", null); - var resultAddress = builder.BuildIntCast(builder.BuildAlloca(LLVMTypeRef.Int32, "resultAddress"), LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), "castResultAddress"); - HandleDirectCall(helperMethod, helperMethod.Signature, arguments, null, default(LLVMValueRef), 0, NullRefFunction.GetParam(0), builder, true, resultAddress, helperMethod); + var valRef = builder.BuildBitCast(llvmCheckFunction.GetParam(1), intTypeRef); + LLVMValueRef exponentBits = builder.BuildAnd(valRef, exponentMask, "and"); + LLVMValueRef isFinite = builder.BuildICmp(LLVMIntPredicate.LLVMIntEQ, exponentBits, exponentMask, "isfinite"); - var exceptionEntry = new LoadExpressionEntry(GetStackValueKind(nullRefType), "RhNewObject_return", resultAddress, nullRefType); + LLVMBasicBlockRef throwBlock = llvmCheckFunction.AppendBasicBlock("Throw"); + LLVMBasicBlockRef afterIf = llvmCheckFunction.AppendBasicBlock("AfterIf"); + builder.BuildCondBr(isFinite, throwBlock, afterIf); - var ctorDef = nullRefType.GetDefaultConstructor(); + builder.PositionAtEnd(throwBlock); - HandleDirectCall(ctorDef, ctorDef.Signature, new StackEntry[] { exceptionEntry }, null, default(LLVMValueRef), 0, NullRefFunction.GetParam(0), builder, false, default(LLVMValueRef), ctorDef); + ThrowException(builder, "ThrowHelpers", "ThrowOverflowException", llvmCheckFunction); - EnsureRhpThrowEx(); - LLVMValueRef[] args = new LLVMValueRef[] { exceptionEntry.ValueAsType(LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0), builder) }; - builder.BuildCall(RhpThrowEx, args, ""); - builder.BuildUnreachable(); - builder.PositionAtEnd(retBlock); + afterIf.MoveAfter(llvmCheckFunction.LastBasicBlock); + builder.PositionAtEnd(afterIf); builder.BuildRetVoid(); } LLVMBasicBlockRef nextInstrBlock = default; - CallOrInvoke(false, _builder, GetCurrentTryRegion(), NullRefFunction, new List { GetShadowStack(), entry }, ref nextInstrBlock); + CallOrInvoke(false, _builder, GetCurrentTryRegion(), llvmCheckFunction, new List { GetShadowStack(), value }, ref nextInstrBlock); } - void EnsureRhpThrowEx() + private void ThrowException(LLVMBuilderRef builder, string helperClass, string helperMethodName, LLVMValueRef throwingFunction) { - if (RhpThrowEx.Handle.Equals(IntPtr.Zero)) - { - RhpThrowEx = Module.AddFunction("RhpThrowEx", LLVMTypeRef.CreateFunction(LLVMTypeRef.Void, new LLVMTypeRef[] { LLVMTypeRef.CreatePointer(LLVMTypeRef.Int8, 0) }, false)); - } + MetadataType helperType = _compilation.TypeSystemContext.SystemModule.GetKnownType("Internal.Runtime.CompilerHelpers", helperClass); + MethodDesc helperMethod = helperType.GetKnownMethod(helperMethodName, null); + LLVMValueRef fn = LLVMFunctionForMethod(helperMethod, helperMethod, null, false, null, null, out bool hasHiddenParam, out LLVMValueRef dictPtrPtrStore, out LLVMValueRef fatFunctionPtr); + builder.BuildCall(fn, new LLVMValueRef[] {throwingFunction.GetParam(0) }, string.Empty); + builder.BuildUnreachable(); } private LLVMValueRef GetInstanceFieldAddress(StackEntry objectEntry, FieldDesc field) diff --git a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter_Statics.cs b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter_Statics.cs index 400b022ca3c..ceb0b775e4b 100644 --- a/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter_Statics.cs +++ b/src/ILCompiler.WebAssembly/src/CodeGen/ILToWebAssemblyImporter_Statics.cs @@ -117,6 +117,8 @@ public static void CompileMethod(WebAssemblyCodegenCompilation compilation, WebA static LLVMValueRef LlvmCatchFunclet = default(LLVMValueRef); static LLVMValueRef LlvmFinallyFunclet = default(LLVMValueRef); static LLVMValueRef NullRefFunction = default(LLVMValueRef); + static LLVMValueRef CkFinite32Function = default(LLVMValueRef); + static LLVMValueRef CkFinite64Function = default(LLVMValueRef); public static LLVMValueRef GxxPersonality = default(LLVMValueRef); public static LLVMTypeRef GxxPersonalityType = default(LLVMTypeRef); diff --git a/tests/src/Simple/HelloWasm/CkFinite.il b/tests/src/Simple/HelloWasm/CkFinite.il new file mode 100644 index 00000000000..6342e78e240 --- /dev/null +++ b/tests/src/Simple/HelloWasm/CkFinite.il @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +.assembly extern mscorlib +{ + .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) + .ver 4:0:0:0 +} + +.assembly CkFinite { } + +.class public abstract sealed CkFinite.CkFiniteTest { + + .method public static bool CkFinite32(float32) { + .maxstack 5 + try_start: + ldarg 0 + ckfinite + pop //remove the value from the stack + leave try_end + try_end: + ldc.i4 0x00000001 + ret + handler_start: + pop //remove the exception ref from the stack + leave done + handler_end: + done: + ldc.i4 0x00000000 + ret + .try try_start to try_end catch [mscorlib]System.OverflowException handler handler_start to handler_end + } + + .method public static bool CkFinite64(float64) { + .maxstack 5 + try_start: + ldarg 0 + ckfinite + pop //remove the value from the stack + leave try_end + try_end: + ldc.i4 0x00000001 + ret + handler_start: + pop //remove the exception ref from the stack + leave done + handler_end: + done: + ldc.i4 0x00000000 + ret + .try try_start to try_end catch [mscorlib]System.OverflowException handler handler_start to handler_end + } +} diff --git a/tests/src/Simple/HelloWasm/CkFinite.ilproj b/tests/src/Simple/HelloWasm/CkFinite.ilproj new file mode 100644 index 00000000000..7972352de7a --- /dev/null +++ b/tests/src/Simple/HelloWasm/CkFinite.ilproj @@ -0,0 +1,20 @@ + + + netstandard2.0 + Library + portable + $(MSBuildProjectDirectory)\bin\$(Configuration)\$(Platform)\ + $(MSBuildProjectDirectory)\obj\$(Configuration)\$(Platform)\ + + + + + + + + $(MicrosoftNETCoreAppPackageVersion) + + + + + diff --git a/tests/src/Simple/HelloWasm/HelloWasm.csproj b/tests/src/Simple/HelloWasm/HelloWasm.csproj index 546efa4c2a1..39c049d5941 100644 --- a/tests/src/Simple/HelloWasm/HelloWasm.csproj +++ b/tests/src/Simple/HelloWasm/HelloWasm.csproj @@ -4,7 +4,8 @@ - + + diff --git a/tests/src/Simple/HelloWasm/Program.cs b/tests/src/Simple/HelloWasm/Program.cs index 7f53c9b7ea6..f5a39845a66 100644 --- a/tests/src/Simple/HelloWasm/Program.cs +++ b/tests/src/Simple/HelloWasm/Program.cs @@ -10,6 +10,7 @@ #if TARGET_WINDOWS using CpObj; +using CkFinite; #endif internal static class Program { @@ -276,7 +277,7 @@ private static unsafe int Main(string[] args) TestTryFinally(); - + #if TARGET_WINDOWS StartTest("RVA static field test"); int rvaFieldValue = ILHelpers.ILHelpersTest.StaticInitedInt; @@ -344,6 +345,10 @@ private static unsafe int Main(string[] args) TestThrowIfNull(); +#if TARGET_WINDOWS + TestCkFinite(); +#endif + // This test should remain last to get other results before stopping the debugger PrintLine("Debugger.Break() test: Ok if debugger is open and breaks."); System.Diagnostics.Debugger.Break(); @@ -1671,6 +1676,43 @@ static void TestThrowIfNull() EndTest(success); } +#if TARGET_WINDOWS + private static void TestCkFinite() + { + // includes tests from https://github.com/dotnet/coreclr/blob/9b0a9fd623/tests/src/JIT/IL_Conformance/Old/Base/ckfinite.il4 + StartTest("CkFiniteTests"); + if (!CkFiniteTest.CkFinite32(0) || !CkFiniteTest.CkFinite32(1) || + !CkFiniteTest.CkFinite32(100) || !CkFiniteTest.CkFinite32(-100) || + !CkFinite32(0x7F7FFFC0) || CkFinite32(0xFF800000) || // use converter function to get the float equivalent of this bits + CkFinite32(0x7FC00000) && !CkFinite32(0xFF7FFFFF) || + CkFinite32(0x7F800000)) + { + FailTest("one or more 32 bit tests failed"); + return; + } + + if (!CkFiniteTest.CkFinite64(0) || !CkFiniteTest.CkFinite64(1) || + !CkFiniteTest.CkFinite64(100) || !CkFiniteTest.CkFinite64(-100) || + CkFinite64(0x7FF0000000000000) || CkFinite64(0xFFF0000000000000) || + CkFinite64(0x7FF8000000000000) || !CkFinite64(0xFFEFFFFFFFFFFFFF)) + { + FailTest("one or more 64 bit tests failed."); + return; + } + PassTest(); + } + + private static unsafe bool CkFinite32(uint value) + { + return CkFiniteTest.CkFinite32 (* (float*)(&value)); + } + + private static unsafe bool CkFinite64(ulong value) + { + return CkFiniteTest.CkFinite64(*(double*)(&value)); + } +#endif + static ushort ReadUInt16() { // something with MSB set @@ -1692,6 +1734,7 @@ public class ClassForNre public int F; } + public class ClassWithFloat { public static float F;