Предположите, что у меня есть стековый игрушечный язык, который идет с операционным Нажатием, Поп, Переходом и Если.
У меня есть программа, и ее вход является игрушечным языком. Например, я получаю последовательность
Push 1
Push 1
Pop
Pop
В этом случае максимальный стек был бы 2. Более сложный пример использовал бы ответвления.
Push 1
Push true
If .success
Pop
Jump .continue
.success:
Push 1
Push 1
Pop
Pop
Pop
.continue:
В этом случае максимальный стек был бы 3. Однако не возможно получить максимальный стек путем обхода от начала до конца как показано в этом случае, так как это привело бы к ошибке выхода за нижнюю границу стека на самом деле.
CFGs к спасению можно создать график и обойти каждый возможный путь базисных блоков, которые Вы имеете. Однако, так как количество путей может вырасти быстро для n вершин, которые Вы получаете (n-1)! возможные пути.
Мой текущий подход должен упростить график как можно больше и иметь менее возможные пути. Это работает, но я считал бы это ужасным. Есть ли лучшее (чтение: быстрее) способ приняться за решение этой проблемы? Хорошо, если алгоритм производит глубину стека, которая не оптимальна. Если корректный размер стека является m затем, мое единственное ограничение состоит в том, что результатом n является n> = m. Существует ли, возможно, жадный алгоритм, доступный, который привел бы к хорошему результату здесь?
Обновление: Я знаю о циклах и инварианте, что все слияния потока controlf имеют ту же глубину стека. Я думал, что записываю простой игрушечный язык для иллюстрирования проблемы. В основном у меня есть детерминированный стековый язык (байт-код JVM), таким образом, каждая операция имеет известную дельту стека.
Обратите внимание на то, что у меня действительно есть рабочее решение этой проблемы, которая приводит к хорошим результатам (упростил cfg), но я ищу лучший/быстрее подход.
Обычно вы хотите, чтобы глубина стека была инвариантной для скачков и циклов.
Это означает, что для каждого узла каждое входящее ребро должно иметь одинаковую глубину стека. Это значительно упрощает работу с CFG, потому что backedges больше не могут изменять глубину стека уже вычисленных инструкций.
Это также требование для ограниченной глубины стека. Если не принудительно, в вашем коде будут увеличиваться циклы.
Еще одна вещь, которую вы должны учитывать, - это сделать эффект стека всех кодов операций детерминированным. Примером недетерминированного кода операции может быть: POP IF TopOfStack == 0.
Изменить:
Если у вас есть детерминированный набор кодов операций и инвариант глубины стека, нет необходимости посещать все возможные пути программы. Достаточно выполнить DFS / BFS через CFG, чтобы определить максимальную глубину стека. Это можно сделать за линейное время (в зависимости от количества инструкций), но не быстрее.
Оценка того, нужно ли оценивать базовые блоки, в которых все еще должны быть оценены исходящие края текущего базового блока, не должна иметь отношения к производительности. Даже в худшем случае каждая инструкция представляет собой ЕСЛИ
, будет необходимо оценить только 2 * N ребер.
Учитывая, что в вашем языке, похоже, нет пользовательского ввода, все программы будут просто вычисляться одним и тем же способом все время. Поэтому вы могли бы выполнять программу и отслеживать максимальный размер стека во время выполнения. Вероятно, это не то, что вам нужно.
Что касается вашей аргументации по поводу пути: Имейте в виду, что прыжки допускают циклы, следовательно, без дальнейшего анализа цикл может означать незавершение и переполнение стека (т.е. размер стека увеличивается после каждого выполнения цикла). [n узлов все еще означает бесконечно много путей, если есть цикл]
Вместо фактического выполнения кода вы можете сделать некоторую форму абстрактной интерпретации.
Относительно комментария IVlad: простое подсчитывание толчков неверно из-за существования возможных циклов.
Я не уверен, какова семантика ваших if-выражений, поэтому это тоже может быть полезно: Предположим, что метка if-выражения может быть только прямой меткой (т.е. вы никогда не можете перепрыгнуть назад в коде). В этом случае ваш аргумент о подсчете путей оживает. По сути, результирующая CFG будет деревом (или DAG, если вы не копируете код). В этом случае вы можете сделать приблизительный подсчет, вычисляя количество переходов снизу вверх и затем беря максимальное количество переходов для обеих ветвей в случае if-выражения. Это все еще не оптимальный правильный результат, но дает лучшее приближение, чем простой подсчет push-выражений.