Giter Site home page Giter Site logo

Comments (3)

MillKaDe avatar MillKaDe commented on July 1, 2024

Some more info:

Both SharpLab and ILSpy decompile Cmp2 and Cmp4 to something like this:

// renamed 'reference' to 'refT_a'
// renamed 'val' to 'valT_0'
public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  T valT_0 = default (T);
  if (valT_0 == null) {
    valT_0 = refT_a;
    refT_a = ref valT_0;
  }
  return refT_a.Equals (A[i]);
}

Both SharpLab and ILSpy show the follwing IL code (ILSpy adds the C# code as comments before the corresponding IL code):

.method private hidebysig 
  instance bool CmpVal4 (
    !T a,
    uint32 i
  ) cil managed 
{
  // Method begins at RVA 0x2b48
  // Header size: 12
  // Code size: 60 (0x3c)
  .maxstack 3
  .locals init (
    [0] !T,
    [1] bool
  )
  
  // {
  IL_0000: nop
  // ref T reference = ref a;
  IL_0001: ldarga.s a
  // (no C# code)
  IL_0003: ldloca.s 0
  // T val = default(T);
  IL_0005: initobj !T
  // if (val == null)
  IL_000b: ldloc.0
  // (no C# code)
  IL_000c: box !T
  IL_0011: brtrue.s IL_001b
  
  // val = reference;
  IL_0013: ldobj !T
  IL_0018: stloc.0
  // reference = ref val;
  IL_0019: ldloca.s 0
  
  // return reference.Equals(Nodes[i].Value);

  IL_001b: ldarg.0
  IL_001c: ldfld !0[] class C`1<!T>::A
  IL_0021: ldarg.2
  IL_0022: ldelem !T
  IL_0027: constrained. !T
  IL_002d: callvirt instance bool class [System.Runtime]System.IEquatable`1<!T>::Equals(!0)
  IL_0032: stloc.1
  IL_0033: br.s IL_0035

  IL_0035: ldloc.1
  IL_0036: ret
} // end of method C`1::Cmp4

As far as I understand,

  • a local variable 'valT_0' is generated
  • 'valT_0' is cleared
  • then 'valT_0' gets boxed, before it is compared to 'null'

I have no clue about the purpose of all that.

from roslyn.

MillKaDe avatar MillKaDe commented on July 1, 2024

Even more info:

When T is constrained to struct, all 4 Cmp funcs do not box.
When T is constrained to class, all 4 Cmp funcs do box.

So, when T is not constrained to struct or class,

  • either boxing is missing in Cmp1/Cmp3 ?
  • (x)or it is not needed in Cmp2/Cmp4 ?

At the time when the JITter compiles the IL code to assembler code, it will be known whether T is class or struct.

If T is struct:

public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  T valT_0 = default (T);
  if (valT_0 == null) {
    valT_0 = refT_a;
    refT_a = ref valT_0;
  }
  return refT_a.Equals (A[i]);
}

From MS docs here :

Unbounded type parameters
Type parameters that have no constraints, [...] are called unbounded type parameters. [...]
You can compare them to null. If an unbounded parameter is compared to null, the comparison always returns false if the type argument is a value type.

In that case, 'valT_0 == null' is always false, and the then clause can be eliminated ..

public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  T valT_0 = default (T);
  if (valT_0 == null) { }
  return refT_a.Equals (A[i]);
}

.. which allows to eliminate the condition 'valT_0 == null' as well as the statement 'T valT_0 = default (T);' ..

public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  return refT_a.Equals (A[i]);
}

.. so the boxing is eliminated in the JITter ?

However, if T is class:

public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  T valT_0 = default (T);
  if (valT_0 == null) {
    valT_0 = refT_a;
    refT_a = ref valT_0;
  }
  return refT_a.Equals (A[i]);
}

In that case, 'valT_0 == null' is always true, so the code can be reduced to ..

public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  T valT_0 = refT_a; // = 'T valT_0 = a;'
  refT_a = ref valT_0;
  return refT_a.Equals (A[i]);
}

.. which can be further reduced to ..

public bool Cmp4 (T a, int i) {
  ref T refT_a = ref a;
  return refT_a.Equals (A[i]);
}

.. so, again, the boxing is eliminated in the JITter ?

from roslyn.

huoyaoyuan avatar huoyaoyuan commented on July 1, 2024

When constrained as class, box !T is actually an upcast, casting from T to object.

box followed by br is a well known pattern of the JIT:
https://github.com/dotnet/runtime/blob/84b33395057737db3ea342a5151feb6b90c1b6f6/src/coreclr/jit/importer.cpp#L2905-L2929
It will always be folded to true if T is not a nullable value type.

The compiler seems messed up when blending the gap between value type and reference type. I can't see any semantical difference of side effect of Cmp2 and Cmp3.
When constrained to class, the call is using with box !T and unconstrained callvirt. When constrained to struct, the call is using constrained !T callvirt without box.

from roslyn.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.