Ну в принципе в этом проблемы нет, просто вам (или кому-то ещё кто заинтересуется) придётся довести Madhine до ума (но лучше написать новую с нуля — не регистровую машину например а какую-то уровнем абстракции выше, чтобы с функциями было меньше проблем).
Я пока никак.
Хм, если правильно помню, то одной из главных была проблема, на ком ответственность за входные и выходные регистры и как передавать аргументы и получать результаты. Если мы добавим к регистрам стек и опкоды для закидывания значения регистра в стек и выкидывания верхнего элемента стека в регистр, то по идее это самый простой способ разрубить узел с передачей аргументов через сами регистры и всякой относительной адресации.
Тогда вызов функции компилируется в такую последовательность опкодов: мы сначала закидываем из регистров в стек приготовленные значения аргументов, потом вызываем функцию (передав как возврат следующее состояние как обычно), и потом достаём из стека результаты, которые нам передали, в те регистры, в которые хотим. А тело функции компилируется в такую последовательность: мы достаём из стека аргументы в нужные регистры, далее делаем что там описано, и далее выкидываем результаты в стек.
Тут можно заметить, что этот «стек данных» используется очень консервативно: он почти всегда пустой, в нём ничего не лежит долго в ожидании вдали от вызовов и возвратов. И у этого опять следствие, что мы должны запретить рекурсию, и явную и неявную (f вызывает g вызывает h вызывает f), потому что на одну функцию у нас лишь один набор регистров.
Чтобы разрешить рекурсию, мы можем разделить весь код машины на отдельные функции (одна из которых будет запускаться при запуске машины, например первая в наборе) и выделять каждой функции своё «адресное пространство», но тогда если функция использует видимые ей извне переменные (если мы допускаем вложенные функции), доступ к ним будет непросто скомпилировать. А, нет, вру. Если передавать такие переменные как дополнительные аргументы и возвращать изменённые значения как дополнительные результаты, то всё прекрасно сработает.
Если захочется иметь глобальные переменные, их кстати в принципе тоже можно передавать вот так, хотя можно усложнить ситуацию и назначить им например отрицательные номера регистров, которые «не принадлежат» никакой функции и машина будет доставать/класть значения в отдельный кусок памяти. Все же регистры, соответствующие локальным переменным функции и промежуточным значениям в вычислениях, будут сидеть в стеке вызовов для каждой функции своя пачечка рядом с состоянием возврата, и это уже напоминает реальный стек вызовов, используемый обычно, хотя и не целиком. (И плюс «консервативный стек данных», который тоже как будто веет фортом и обычной реализацией конкатенативных языков, но на деле не таков.)