2014-10-12 23 views
15

Dla celów edukacyjnych uczę się trochę IL (głównie dlatego, że byłem ciekawy, co dzieje się z "%" pod maską (która okazuje się być rem) i zaczęło dygresja ...).Dlaczego w tym przypadku używany jest kod opcyjny "br.s" IL?

pisałem sposób, po prostu wraca prawdziwy złamać rzeczy nieco w dół i zastanawiałem się o opcode „br s”:

.method public hidebysig static bool ReturnTrue() cil managed 
{ 
    // Code size  7 (0x7) 
    .maxstack 1 
    .locals init ([0] bool CS$1$0000) 
    IL_0000: nop 
    IL_0001: ldc.i4.1 
    IL_0002: stloc.0 
    IL_0003: br.s  IL_0005 
    IL_0005: ldloc.0 
    IL_0006: ret 
} // End of method Primes::ReturnTrue 

Po ldc.i4.1 wypycha 1 na stosie i stloc .0 umieszcza to w 0. lokalnym, br.s zasadniczo (o ile wiem) robi "goto" do ldloc.0 na linii IL_0005.

Dlaczego tak jest? Dlaczego po prostu nie ma linii IL_0004, więc można ją pominąć?

+6

Nie ma IL_0004, ponieważ 'br.s' jest dwubajtową instrukcją. Zgaduję, że wyłączyłeś optymalizacje? Tak, oczekuj dziwnych rzeczy. Co otrzymasz po włączeniu optymalizacji? – hvd

+1

Następnie po prostu popycha "1" do stosu i natychmiast wraca. IL_0000: ldc.i4.1 IL_0001: ret – Apeiron

+0

W "IL_xxxx" xxxx jest przesunięciem instrukcji w bajtach od początku metody. br.s jest instrukcją jednobajtową (tak jak reszta), która pobiera operand jednobajtowy, do którego jest skierowana instrukcja docelowa. –

Odpowiedz

13

Ta gałąź służy do celów debugowania, wartość zwracana została obliczona i zmagazynowana, a teraz debugger może zostać "wywołany". Tak samo jest z NOP we wpisie metody.

W odniesieniu do IL_0004, jak stwierdza @hvd, br.s ma adres i nie mieści się w "jednym wierszu", jeden bajt tutaj (nie wiem, jak dobrze znasz adresowanie, ale jedna instrukcja zazwyczaj jest jeden bajt, czyli 8-bitowy, a także adres lub offset, zwykle 8, 16 lub 32-bitowy, w tym przypadku mamy 8-bitowy kod operacji z 8-bitowym przesunięciem Wikipedia has a good article on CIL-OP-codes). Dodatkowo, powiedzmy, twoja metoda ma wiele zwrotów i na przykład przez if -branches, wszystkie z nich przeskakują do końca, IL_0005 w twoim przypadku, więc tylko jeden punkt zatrzymania jest potrzebny przy powrocie funkcji.

+0

Huh? Jest to liczba bajtów, z jednym bajtem dla argumentu 'br.s', a adres zazwyczaj nie pasuje do bajtu. 'br.s' przyjmuje 8-bitowe względne przesunięcie, a nie adres. – hvd

+0

@hvd Masz rację, zawsze zakładałem, że adresowanie IL nie jest oparte na bajtach, ale raczej na długości słowa, ale masz rację, sir;) – flindeberg

3

Jest to bardzo popularny artefakt z recursive-descent parser, taki jak ten, którego używa kompilator C#. Pozbycie się tych gałęzi wymaga peephole optimizer.

Prawdopodobnie wystąpi, gdy sam kompilator zoptymalizuje trywialną operację, której wynik można określić podczas kompilacji. Kompilator C# nie ma optymalizatora peephole, ponieważ nie jest to konieczne, jitter dba o wyeliminowanie niepotrzebnych gałęzi. Umieszczenie optymalizatora w jitterze jest ogólnie zwycięską strategią, z której korzysta każdy kompilator języka. Utrzymuje kompilatory bardzo proste i znaczne koszty pisania i utrzymywania optymalizatora kodu w jednym (lub kilku) miejscach.

To nie jest miejsce, w którym kończy się optymalizator jitter, cała twoja metoda zniknie w czasie wykonywania. Z dużym prawdopodobieństwem, że niezależnie od kodu, który wywołuje metodę będzie również znacznie zoptymalizowany, ponieważ wartość powrotu metody jest znana w czasie kompilacji. Zobaczenie tego rodzaju MSIL jest silną podpowiedzią, że twój kod może być łatwo uproszczony lub ma błąd :)

0

To nie dzieje się w Visual Studio 2013, wygląda na to, że dev naprawił go. To będzie wyglądać tak w VS2013.

.method public hidebysig static bool ReturnTrue() cil managed 
{ 
    .maxstack 1 
    ldc.i4.1 
    ret 
} // End of method Primes::ReturnTrue 
+1

Spróbuj skompilować za pomocą zestawu flag 'debug'. – flindeberg

Powiązane problemy