SEN2001 · 9. hafta · Subprograms II

Alt programlar II: activation record, static ve dynamic chain

Çağrı ve dönüşte ne olur, activation record'un yapısı, run-time stack, özyineleme, iç içe alt programlarda static chain ile nonlocal erişim (chain_offset, local_offset), bloklar ve dynamic scope'un deep/shallow access uygulaması.

call semanticsreturn semanticsactivation recordactivation record instancerun-time stackreturn addressdynamic linkstatic linkdynamic chainstatic chainstatic depthchain offsetlocal offsetenvironment pointerrecursionblockdeep accessshallow access

Geçen hafta alt programların ne yaptığını konuştuk; bu hafta derleyicinin bunu nasıl uyguladığını. Merkezde tek bir yapı var: activation record (ARI). Bir alt program her çağrıldığında run-time stack’e bir ARI konur, dönünce kaldırılır. Sınav soruları: ARI’nin içeriği, verilen çağrı dizisinde yığının resmi, static/dynamic link’lerin nereyi gösterdiği ve (chain_offset, local_offset) çiftleri.

Altın kurallar:

  1. Dynamic link çağıranı, static link static parent’ı gösterir.
  2. Static parent metinden okunur, çağrı sırasından değil.
  3. chain_offset = referans yerinin derinliği − bildirim yerinin derinliği.
  4. Her çağrı yeni ARI: özyinelemede aynı fonksiyonun birden çok ARI’si yığındadır.

Çağrı ve dönüş semantiği

Bir çağrıda yapılması gerekenler (call semantics):

  1. Çağıranın çalışma durumunu kaydet.
  2. Yerel değişkenler için yer ayır (stack-dynamic ise).
  3. Parametreleri aktar (value ise kopyala, reference ise adres).
  4. Return address’i kaydet.
  5. Nonlocal erişim için gereken bağlantıyı kur (static link).
  6. Kontrolü çağrılana ver.

Dönüşte (return semantics): out ve inout parametrelerin değerlerini geri kopyala, function ise dönüş değerini çağıranın erişebileceği yere koy, yığını eski haline getir, return address’e dön.

Activation record

Bir alt programın çalışırken ihtiyaç duyduğu yerel verinin biçimine activation record, bunun belirli bir çağrıya ait somut örneğine activation record instance (ARI) denir.

            ┌──────────────────┐  ← yığının tepesi
            │ yerel değişkenler │
            │ parametreler      │
            │ dynamic link      │  → çağıranın ARI'si
            │ static link       │  → static parent'ın ARI'si
            │ return address    │
            └──────────────────┘  ← ARI'nin tabanı

Basit durum: özyineleme yok, iç içelik yok

Erken Fortran’da tüm yereller static’ti. Her alt programın tek bir ARI’si vardı, derlemede sabit bir yere konurdu. Hızlı, ama özyineleme imkânsız: ikinci bir aktivasyon birincinin değişkenlerinin üstüne yazar.

Stack-dynamic yereller: run-time stack

C, Java gibi dillerde her çağrı yığına yeni bir ARI koyar. Environment pointer (EP) o an çalışan birimin ARI’sinin tabanını gösterir; yerellere EP + sabit ofset ile erişilir. Dynamic link, çağıranın ARI’sini gösterir; dönüşte EP bu değere geri alınır. Dynamic link’lerin oluşturduğu zincire dynamic chain (call chain) denir.

static int f(int n) {
    System.out.print("in" + n + " ");
    int r = (n <= 1) ? 1 : n * f(n - 1);
    System.out.print("out" + n + " ");
    return r;
}
// main: int v = f(3); System.out.println(); System.out.println(v);
in3 in2 in1 out1 out2 out3 
6

f(1) çalışırken yığın: main, f(3), f(2), f(1). Her ARI’nin kendi n’i ve r’si var. fact(4) main’den çağrılırsa yığında en fazla 5 ARI olur.

İç içe alt programlar: static chain

Ada, JavaScript, Python gibi dillerde bir fonksiyon başka bir fonksiyonun içinde tanımlanabilir. Static scope’ta nonlocal bir değişken, static ata’ların ARI’lerinden birindedir. Sorun: o ARI yığında nerede? Çözüm: her ARI’ye static parent’ın ARI’sini gösteren bir static link eklenir. Static link’lerin zincirine static chain denir.

  • Static depth: birimin en dıştan kaç seviye içeride olduğu. Main 0.
  • chain_offset (nesting depth): referansın yapıldığı birimin static depth’i − değişkenin bildirildiği birimin static depth’i.
  • local_offset: değişkenin kendi ARI’si içindeki konumu.

Nonlocal erişim: chain_offset kadar static link izle, varılan ARI’de local_offset’e git. İki sayı da derleme zamanında bilinir.

Çözümlü örnek

Main     declares p, q, r          static depth 0
  Sub1   declares q, s             static depth 1   (Main'in içinde)
    Sub2 declares t, p             static depth 2   (Sub1'in içinde)
  Sub3   declares r, s             static depth 1   (Main'in içinde)

Çağrı dizisi: Main → Sub3 → Sub1 → Sub2
ARI düzeni: 0 return address, 1 static link, 2 dynamic link, yereller 3'ten itibaren

Sub2 çalışırken yığın (alttan üste):

#  ARI    static link →   dynamic link →
3  Sub2   Sub1             Sub1
2  Sub1   Main             Sub3      ← dikkat: static parent Main, çağıran Sub3
1  Sub3   Main             Main
0  Main   -                -

Sub2 içindeki referanslar:

AdBulunduğu birimchain_offsetlocal_offsetÇift
tSub22 − 2 = 03(0, 3)
pSub2 (Main.p’yi gizler)04(0, 4)
qSub12 − 1 = 13(1, 3)
sSub114(1, 4)
rMain2 − 0 = 25(2, 5)

Sub3 içinde q ise Main’de: (1, 4). Sub3’ün kendi r’si: (0, 3).

TUZAK · static link çağırana gitmez. Sub1’i Sub3 çağırdı ama Sub1’in static link’i Main’i gösterir. Static link metindeki iç içeliği izler. Sınavda en sık kaybedilen puan bu: yığında hemen alttaki ARI’ye ok çizmek.

Çağrı anında, çağrılanın static parent’ının en son aktif ARI’si bulunmalı. Derleyici bunu da ofsetle yapar: çağıranın static depth’i ile çağrılanın static parent’ının depth’i arasındaki fark kadar static link izlenir. Sub3 (depth 1) Sub1’i (parent Main, depth 0) çağırıyor: Sub3’ten 1 link izle → Main.

Eleştiri: derin bir nonlocal’e erişim birçok link takibi gerektirir ve erişim süresi derinliğe göre değişir; zamana duyarlı kodda tahmin zorlaşır. Alternatif display yöntemi her derinlik için bir göstergeyi bir dizide tutar. Pratikte iç içelik nadiren 3’ü geçtiği için static chain yeterli.

Bloklar

{ int c, d; ... } gibi bloklar iki şekilde uygulanabilir:

  1. Parametresiz, hep aynı yerden çağrılan alt programlar gibi: her girişte ARI.
  2. Daha verimlisi: bloğun değişkenleri çevreleyen alt programın ARI’sine statik olarak yerleştirilir. Yan yana (sibling) bloklar aynı anda aktif olamaz, aynı hücreleri paylaşır.
void f() {
    int a, b;
    { int c, d; ... }
    { int e, f, g; ... }
}
gereken yerel hücre: 2 + max(2, 3) = 5

Dynamic scope’un uygulanması

Deep access: nonlocal bir isim, dynamic chain’de yukarıdan aşağı aranır. ARI’lerde değişken adları da tutulmalı (hangi değişkenin hangi ARI’de olduğu derlemede bilinmez). Zincir uzunluğu belirsiz, erişim yavaş.

Yukarıdaki program dynamic scope’lu olsaydı Sub2’deki r: Sub2’de yok, Sub1’de yok, Sub3’te var → Sub3.r. Static scope’ta Main.r. s ise iki kuralda da Sub1.s (Sub1 hem static parent hem çağıran).

Shallow access: her değişken adı için ayrı bir yığın (ya da merkezi tablo) tutulur; en üstteki aktif olan. Erişim hızlı, ama çağrı ve dönüşler pahalı (her yerel için push/pop).

Sık yapılan hatalar

YanlışDoğrusu
Static link çağıranı gösterirStatic parent’ı (metinde çevreleyeni)
Dynamic link static parent’ı gösterirÇağıranı
chain_offset = yığındaki ARI farkıStatic depth farkı
Sibling bloklar için 2 + 3 hücre toplanırPaylaşırlar: max
fact(4) için yığında 4 ARImain dahil 5
Static yerellerle özyineleme yapılabilirYapılamaz; tek ARI
Deep access static chain’i izlerDynamic chain’i izler
Özyinelemede çıkışlar giriş sırasıylaTers sırayla (LIFO)

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

List the typical contents of an activation record for a subprogram in a language with nested subprograms (e.g. Ada, JavaScript).

Cevabı göster
return address, static link, dynamic link, parameters, local variables

Return address: dönünce çalışmanın devam edeceği yer. Static link: static parent'ın ARI'si (nonlocal erişim için). Dynamic link: çağıranın ARI'sinin tepesi (dönüşte yığını geri sarmak için). Parametreler ve yereller. Function ise dönüş değeri için de yer olabilir.

Soru 2kolay

Use the program below. Main calls Sub3, Sub3 calls Sub1, Sub1 calls Sub2. What are the static depths of Main, Sub1, Sub2 and Sub3?

Main     declares p, q, r
  Sub1   (nested in Main) declares q, s
    Sub2 (nested in Sub1) declares t, p
  Sub3   (nested in Main) declares r, s
Cevabı göster
Main 0, Sub1 1, Sub2 2, Sub3 1

Static depth, bir birimin en dış scope'tan kaç seviye içeride olduğunu gösterir. Main 0. Main'in içindekiler (Sub1, Sub3) 1. Sub1'in içindeki Sub2 2. Çağrı sırası static depth'i etkilemez.

Soru 3orta

Same program. Each ARI has offset 0 = return address, 1 = static link, 2 = dynamic link, and locals from offset 3 in declaration order (Main too). Give the (chain_offset, local_offset) pair for each reference to t, q and r inside Sub2.

Cevabı göster
t: (0, 3), q: (1, 3), r: (2, 5)

chain_offset = referansın yapıldığı birimin static depth'i − değişkenin bildirildiği birimin static depth'i. t Sub2'de: 2 − 2 = 0, Sub2'nin ilk yereli → 3. q: Sub2'de yok, Sub1'de var (Sub2'nin p'si q'yu gizlemez, ad farklı): 2 − 1 = 1, Sub1'in ilk yereli → 3. r: Sub1'de de yok, Main'de: 2 − 0 = 2, Main'in üçüncü yereli → 5. (Sub2'deki p ise (0, 4); Main'in p'sini gizler.)

Soru 4zor

Same program and call sequence. When Sub2 is executing, which ARI does the static link of Sub1's ARI point to, and which does its dynamic link point to?

Cevabı göster
static link: Main; dynamic link: Sub3

Static link static parent'ı gösterir: Sub1 metinde Main'in içinde yazılmış → Main'in ARI'si. Dynamic link çağıranı gösterir: Sub1'i Sub3 çağırdı → Sub3'ün ARI'si. Yığın alttan üste: Main, Sub3, Sub1, Sub2. Sub2'nin static link'i Sub1'i, Sub3'ün static link'i Main'i gösterir.

Soru 5zor

Same program and call sequence, but suppose the language used dynamic scoping implemented with deep access. Which declaration does a reference to r inside Sub2 find? Under static scoping?

Cevabı göster
dynamic (deep access): Sub3's r; static: Main's r

Deep access dynamic chain'i yukarıdan aşağı tarar: Sub2'de r yok, Sub1'de yok, Sub3'te var → Sub3.r. Static scope static chain'i izler: Sub2 → Sub1 → Main, Main.r. Sub3, Sub2'nin static atası değildir.

Soru 6orta

What is printed in Java?

static int f(int n) {
    System.out.print("in" + n + " ");
    int r = (n <= 1) ? 1 : n * f(n - 1);
    System.out.print("out" + n + " ");
    return r;
}
// main: System.out.println(f(3));
Cevabı göster
in3 in2 in1 out1 out2 out3 then 6

Her çağrı yığına yeni bir ARI koyar (kendi n'i ve r'si). Önce tüm girişler: f(3), f(2), f(1). f(1) taban durum, döner; sonra ARI'ler ters sırayla silinir: out1, out2, out3. Sonuç 3 · 2 · 1 = 6.

Soru 7kolay

With fact(n) { if (n <= 1) return 1; return n * fact(n - 1); }, what is the maximum number of ARIs on the run-time stack during fact(4) called from main (count main's ARI too)?

Cevabı göster
5

main + fact(4) + fact(3) + fact(2) + fact(1) = 5. fact(1) çalışırken hepsi yığında, çünkü hiçbiri henüz dönmedi (çarpma dönüşten sonra yapılıyor). Özyineleme bu yüzden stack-dynamic yereller gerektirir: her ARI'nin kendi n'i var.

Soru 8orta

How many calls to fib are made in total when computing fib(4) with fib(n) = n < 2 ? n : fib(n-1) + fib(n-2)?

Cevabı göster
9

fib(4) → fib(3), fib(2). fib(3) → fib(2), fib(1). Her fib(2) → fib(1), fib(0). Sayım: fib(4) 1, fib(3) 1, fib(2) 2, fib(1) 3, fib(0) 2 → 9. Her çağrı bir ARI yaratır ve dönünce siler; yığın derinliği ise en fazla 4 fib + main.

Soru 9orta

A function declares int a, b; and contains two sibling blocks { int c, d; ... } and { int e, f, g; ... }. If blocks are implemented by static allocation inside the function's ARI, how many local cells does the ARI need?

Cevabı göster
5

Yan yana (sibling) bloklar aynı anda aktif olamaz, bu yüzden yerlerini paylaşabilir. a, b her zaman: 2. Bloklar için en büyüğü: max(2, 3) = 3. Toplam 5 (7 değil). Bloğun değişkenlerine sabit ofsetle erişilir, bloğa girişte ayrıca ARI yaratılmaz.

Soru 10orta

In the static chain method, how is a nonlocal variable accessed at run time, given its (chain_offset, local_offset) pair?

Cevabı göster
follow chain_offset static links starting from the current ARI, then add local_offset

chain_offset kaç static link takip edileceğini, local_offset hedef ARI içindeki konumu söyler. İkisi de derleme zamanında hesaplanır. Bedel: uzak (derin) nonlocal'ler için birden fazla link takibi; erişim süresi derinliğe bağlı.

Soru 11kolay

Compare the dynamic link and the static link: what does each point to and what is each used for?

Cevabı göster
dynamic link: caller's ARI, used to restore the stack on return; static link: static parent's ARI, used for nonlocal access

Dynamic link'lerin oluşturduğu zincir (dynamic chain) çağrı geçmişidir; dynamic scope da bunun üstünden çalışır. Static link'lerin zinciri (static chain) metindeki iç içeliği yansıtır; static scope'ta nonlocal erişim bununla yapılır. Bir alt programı static parent'ı çağırmışsa ikisi aynı ARI'yi gösterir.

Soru 12orta

Why can't a language with only static local variables (like early Fortran) support recursion?

Cevabı göster
each local has only one memory cell, so a recursive call would overwrite the caller's values

Static yerellerde ARI derlemede bir kez, sabit yerde ayrılır. Özyinelemede aynı alt programın birden fazla aktivasyonu aynı anda canlıdır ve her birinin kendi değişkenleri olmalı. Bu ancak her çağrıda yeni bir ARI yaratan run-time stack ile mümkün.

Doğrulama: Static depth, (chain_offset, local_offset) çiftleri, Main → Sub3 → Sub1 → Sub2 çağrısında yığındaki her ARI'nin static ve dynamic link'i ile deep access sonuçları node'da iç içe birim modelinden hesaplandı (verify/SEN2001/09.mjs). Özyinelemeli fonksiyonun giriş/çıkış sırası JDK ile çalıştırıldı (java verify/SEN2001/W09.java); en fazla ARI sayısı ve fib çağrı sayısı node ile sayıldı.

Deftere not kartı

çağrıda: ARI yarat, parametreleri aktar, return address kaydet, kontrolü ver dönüşte: out/inout değerleri kopyala, function değerini taşı, yığını geri sar, return address'e dön ARI: return address | static link | dynamic link | parametreler | yereller static yerel (Fortran I): ARI sabit → özyineleme YOK stack-dynamic: her çağrıda yeni ARI → özyineleme VAR dynamic link → ÇAĞIRAN. static link → STATIC PARENT (metinde çevreleyen) static depth: en dış = 0, her iç içelik +1 chain_offset = depth(referans yeri) - depth(bildirim yeri) erişim: chain_offset kadar static link izle, local_offset ekle yığın Main, Sub3, Sub1, Sub2 → Sub1'in static link'i Main (Sub3 DEĞİL) blok: yan yana bloklar yeri paylaşır → yer = dış yereller + max(blok) dynamic scope: deep access = dynamic chain'i tara; shallow access = her ad için merkezi tablo/yığın özyineleme: giriş sırası 3,2,1 ; çıkış sırası 1,2,3