Modern derleyicide jenerikler nasıl uygulanır?


15

Demek istediğim, bir şablondan T add(T a, T b) ...oluşturulan koda nasıl gideriz ? Bunu başarmanın birkaç yolunu düşündüm, genel işlevi bir AST'de saklıyoruz Function_Nodeve daha sonra her kullandığımızda orijinal fonksiyon düğümünde, kendisinin bir kopyasını T, Kullanılan. Örneğin add<int>(5, 6)jenerik fonksiyonunun bir kopyasını saklar addve her türlü yerine T kopyada ile int.

Yani şöyle bir şey olurdu:

struct Function_Node {
    std::string name; // etc.
    Type return_type;
    std::vector<std::pair<Type, std::string>> arguments;
    std::vector<Function_Node> copies;
};

Daha sonra bunlar için kod oluşturabilir ve bir Function_Nodekopya listesini ziyaret copies.size() > 0ettiğinizde visitFunction, tüm kopyaları çağırırsınız.

visitFunction(Function_Node& node) {
    if (node.copies.size() > 0) {
        for (auto& node : nodes.copies) {
            visitFunction(node);
        }
        // it's a generic function so we don't want
        // to emit code for this.
        return;
    }
}

Bu işe yarar mı? Modern derleyiciler bu probleme nasıl yaklaşıyor? Belki de bunu yapmanın başka bir yolu, kopyaları AST'ye enjekte edebilmenizdir, böylece tüm anlamsal aşamalardan geçecektir. Ben de onları örneğin Rust'un MIR veya Swifts SIL gibi derhal oluşturabileceğinizi düşündüm.

Kodum Java ile yazılmış, buradaki örnekler C ++ çünkü örnekler için biraz daha az ayrıntılı - ama ilke temelde aynı şey. Birkaç hata olabilir, çünkü sadece soru kutusuna elle yazılmıştır.

Bu soruna yaklaşmanın en iyi yolu gibi modern derleyiciyi kastettiğimi unutmayın. Jenerik derken, tip silme kullandıkları Java jenerikleri gibi demek istemiyorum.


C ++ 'da (diğer programlama dillerinin jenerikleri vardır, ancak her biri farklı şekilde uygular), temelde dev bir derleme zamanı makro sistemidir. Gerçek kod, ikame edilmiş tip kullanılarak üretilir.
Robert Harvey

Neden silme işlemi yapılmıyor? Unutmayın, bunu yapan sadece Java değildir ve kötü bir teknik değildir (gereksinimlerinize bağlı olarak).
Andres F.

@AndresF. Bence benim dilimin çalışma şekli göz önüne alındığında iyi sonuç vermezdi.
Jon Flow

2
Bence ne tür jeneriklerden bahsettiğinizi açıklığa kavuşturmalısınız. Örneğin, C ++ şablonlarının, C # jeneriklerinin ve Java jeneriklerinin hepsi birbirinden çok farklıdır. Java jeneriklerini kastetmediğini söylüyorsun, ama ne demek istediğini söylemiyorsun.
svick

2
Aşırı
genişlemeden

Yanıtlar:


14

Modern derleyicide jenerikler nasıl uygulanır?

Modern bir derleyicinin nasıl çalıştığını bilmek istiyorsanız sizi modern bir derleyicinin kaynak kodunu okumaya davet ediyorum. C # ve Visual Basic derleyicilerini uygulayan Roslyn projesiyle başlardım.

Özellikle dikkatinizi C # derleyicisinde tür sembolleri uygulayan koda çekmek:

https://github.com/dotnet/roslyn/tree/master/src/Compilers/CSharp/Portable/Symbols

ayrıca dönüştürülebilirlik kuralları için koda bakmak da isteyebilirsiniz. Orada jenerik tiplerin cebirsel manipülasyonu ile ilgili çok şey var.

https://github.com/dotnet/roslyn/tree/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions

İkincisini okumayı kolaylaştırmak için çok uğraştım.

Bunu başarmanın birkaç yolunu düşündüm, genel işlevi bir İşlev_Dodu olarak bir AST'de saklıyoruz ve daha sonra her kullandığımızda orijinal işlev düğümünde, T tipleri ile değiştirilen tüm tiplerle kendi kopyasını saklıyoruz. kullanılmakta.

Jenerikleri değil şablonları açıklıyorsunuz . C # ve Visual Basic kendi tip sistemlerinde gerçek jeneriklere sahiptir.

Kısaca böyle çalışırlar.

  • Derleme zamanında biçimsel olarak neyin oluşturduğuna ilişkin kurallar oluşturarak başlarız. Örneğin: intbir tür, bir tür parametresi Tbir türdür, herhangi bir tür Xiçin dizi türü X[]de bir türdür, vb.

  • Jenerik kuralları ikameyi içerir. Örneğin, class C with one type parameterbir tür değildir. Tür yapmak için bir model. class C with one type parameter called T, under substitution with int for T olan bir tür.

  • Türler arasındaki ilişkileri açıklayan kurallar - ödev üzerine uyumluluk, bir ifadenin türünün nasıl belirleneceği, vb. - derleyicide tasarlanır ve uygulanır.

  • Meta veri sisteminde genel türleri destekleyen bir bayt kodu dili tasarlanmış ve uygulanmıştır.

  • Çalışma zamanında JIT derleyicisi bayt kodunu makine koduna dönüştürür; jenerik bir uzmanlık verilen uygun makine kodunun oluşturulmasından sorumludur.

Yani, örneğin, C #

class C<T> { public void X(T t) { Console.WriteLine(t); } }
...
var c = new C<int>(); 
c.X(123);

sonra derleyici C<int>, argümanın intgeçerli bir ikame olduğunu doğrular Tve buna göre meta veri ve bayt kodu oluşturur. Çalışma zamanında, titreşim a'nın C<int>ilk kez oluşturulduğunu algılar ve uygun makine kodunu dinamik olarak üretir.


9

Çoğu jenerik uygulama (ya da daha doğrusu: parametrik polimorfizm) tip silme kullanmaktadır. Bu, genel kod derleme sorununu büyük ölçüde basitleştirir, ancak yalnızca kutulu tipler için çalışır: her bir argüman etkili bir şekilde opak bir işaretçi olduğundan, argümanlar üzerinde işlem yapmak için VTable veya benzer bir gönderme mekanizmasına ihtiyacımız vardır. Java dilinde:

<T extends Addable> T add(T a, T b) { … }

derlenebilir, tür denetlenebilir ve aynı şekilde çağrılabilir

Addable add(Addable a, Addable b) { … }

bunun dışında jenerikler tip denetleyicisine çağrı sitesinde çok daha fazla bilgi sağlar. Bu ekstra bilgiler , özellikle genel tipler çıkarıldığında tip değişkenleriyle ele alınabilir . Tür denetimi sırasında, her genel tür bir değişkenle değiştirilebilir, diyelim $T1:

$T1 add($T1 a, $T1 b)

Daha sonra tür değişkeni, somut bir tür ile değiştirilinceye kadar, daha fazla gerçekle güncellenir. Tür kontrol algoritması, henüz tam bir türe çözümlenmemiş olsalar bile bu tür değişkenleri barındıracak şekilde yazılmalıdır. Java'nın kendisinde bu genellikle kolayca yapılabilir çünkü işlev çağrısı türünün bilinmesi gerekmeden önce argümanların türü genellikle bilinir. Dikkate değer bir istisna, bu tür değişkenlerin kullanılmasını gerektiren fonksiyon argümanı olarak lambda ifadesidir.

Çok daha sonra, bir iyileştirici olabilir argümanlar belirli bir dizi için özel kod oluşturmak, bu daha sonra etkili bir satır içine alma bir tür olacaktır.

Genel işlev tür üzerinde herhangi bir işlem gerçekleştirmezse, yalnızca bunları başka bir işleve geçirirse, genel tür argümanları için bir VTable'dan kaçınılabilir. Örneğin Haskell fonksiyonunun argümanı kutlaması call :: (a -> b) -> a -> b; call f x = f xgerekmeyecekti x. Ancak, bu, boyutlarını bilmeden değerlerden geçebilen ve aslında onu yine de işaretçilerle sınırlayan bir çağrı kuralı gerektirir.


C ++ bu açıdan çoğu dilden çok farklıdır. Şablonlu bir sınıf veya işlev (burada yalnızca şablonlanmış işlevleri tartışacağım) kendi içinde çağrılamaz. Bunun yerine, şablonlar, gerçek bir işlev döndüren bir derleme zamanı meta işlevi olarak anlaşılmalıdır. Şablon argüman çıkarımını bir an için yok sayarak, genel yaklaşım daha sonra şu adımlara dayanır:

  1. Şablonu sağlanan şablon bağımsız değişkenlerine uygulayın. Örneğin çağırarak template<class T> T add(T a, T b) { … }olarak add<int>(1, 2)bize gerçek işlevini verecekti int __add__T_int(int a, int b)(isim-bozma yaklaşım kullanılır ya da her neyse).

  2. Geçerli derleme biriminde bu işlevin kodu zaten oluşturulmuşsa, devam edin. Aksi takdirde, kodu int __add__T_int(int a, int b) { … }kaynak koduna bir işlev yazılmış gibi oluşturun . Bu, şablon bağımsız değişkeninin tüm tekrarlarının değerleriyle değiştirilmesini içerir. Bu muhtemelen bir AST → AST dönüşümüdür. Ardından, oluşturulan AST üzerinde tip kontrolü yapın.

  3. Çağrıyı kaynak kodu kullanılmış gibi derleyin __add__T_int(1, 2).

C ++ şablonlarının, burada tanımlamak istemediğim aşırı yük çözünürlüğü mekanizması ile karmaşık bir etkileşimi olduğunu unutmayın. Ayrıca, bu kod oluşturma işleminin sanal olan şablonlanmış bir yönteme sahip olmayı imkansız kıldığına dikkat edin - tür silme tabanlı bir yaklaşım bu önemli kısıtlamadan muzdarip değildir.


Bu derleyiciniz ve / veya diliniz için ne anlama geliyor? Sunmak istediğiniz tür jenerikleri dikkatlice düşünmelisiniz. Tür çıkarımının olmadığı durumlarda tür silme, kutulu türleri destekliyorsanız mümkün olan en basit yaklaşımdır. Şablon uzmanlığı oldukça basit görünmektedir, ancak şablonlar tanım sitesinde değil çağrı sitesinde somutlaştırıldığından, genellikle ad yönetimi ve (çoklu derleme birimleri için) çıktıda önemli bir tekrarlama içerir.

Gösterdiğiniz yaklaşım aslında C ++ benzeri bir şablon yaklaşımıdır. Ancak, özel / somutlaştırılmış şablonları ana şablonun "sürümleri" olarak depolarsınız. Bu yanıltıcıdır: kavramsal olarak aynı değildirler ve bir işlevin farklı örneklemelerinin çılgınca farklı türleri olabilir. İşlev aşırı yüklenmesine de izin verirseniz, uzun vadede işleri zorlaştıracaktır. Bunun yerine, bir adı paylaşan tüm olası işlevleri ve şablonları içeren bir aşırı yük kümesi kavramına ihtiyacınız olacaktır. Aşırı yüklemeyi çözmek dışında, farklı örneklenmiş şablonların birbirinden tamamen ayrı olduğunu düşünebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.