наверное, все и так поняли, но у меня зуд пояснятельства. «процедурные карты» — это просто карты смещений указателей (и строк) от EBP. точнее, от некоторой другой базы, но неважно. важно то, что эти карты можно построить во время компиляции. а в ядре у нас есть такое:
Код:
(* this is allocated on the stack for each procedure with pointers or strings *)
ProcMapItem = POINTER TO RECORD [untagged, align1]
prev: ProcMapItem;
base: ADDR; (* EBP *)
info: ProcInfo;
END;
ProcInfo = POINTER TO RECORD [untagged, align1]
nameptr: ADDR; (* asciiz procedure name *)
ptrmap: ProcVarItem; (* pointer offset map; could be NIL *)
strmap: ProcVarItem; (* string offset map; could be NIL *)
END;
ProcVarItem = POINTER TO RECORD [untagged, align1]
ofs: INTEGER; (* offset from base; -1 means "no more" *)
END;
(* this is for GC; no need to make this writeable, the compiler knows its way around *)
VAR
currProcMap-: ProcMapItem;
на входе в процедуру кодоген выделяет место на стеке под `ProcMapItem`, и пишет такой код:
Код:
pmi.prev := Kernel.currProcMap;
pmi.base := LocFrameAdr;
pmi.info := procinfoptr;
Kernel.currProcMap := pmi;
и, очевидно, на выходе:
Код:
Kernel.currProcMap := pmi.prev;
это всё очень дёшево: свеженький стек, который только что занулили, и `Kernel.currProcMap`, к которой обращаются всегда. `LocFrameAdr` в регистре, `procinfoptr` вообще константа.
конечно, можно было просто прибить адрес карты по известному смещению от базы и обойтись всего одним push, но тогда возникают проблемы с колбэками, и появляются эвристики для детекта «правильных EBP». а, как известно: тот, кто платит скоростью за безопасность, не получает ни скорости, ни безопасности.
карты, кстати, линейные. массивы делаются плоскими, и карта строится для всего массива. сначала я писал типы переменных, и потом в ядре код по типу всё делал. но потом передумал. рационале: а не делайте многомегабайтных массивов с указателями и строками в процедурах! всё равно компилятор оборвёт полёт мысли где-то в районе попытки выделить на стеке около 128 кб. иба ваистену.
такие же плоские карты строятся и для глобалов. там полёт мысли оборвут где-то на мегабайте. рационале то же самое: юзайте динамические массивы, выделяйте при старте модуля.
decref'ы для строк компилятор пишет прямо в процедуру. но карта всё равно нужна, чтобы раскрутить стек после трапа.
кстати, если линковать карты для всех процедур — получаем более-менее халявный backtrace. без номеров строк, правда. не особо полезно, но в качестве отладочной штуки иногда годится.