Значения, переменные, цитирование, присваивание, определения, условные выражения, лямбды и применения компилируются в язык инструкций.
Вопрос для размышления
Какие решения исходного языка исчезают из цикла выполнения после компиляции?
Компиляция каждой базовой категории выражений в явные данные инструкций
Сохранение порядка вычисления оператора и операндов слева направо при вызовах
Представление скомпилированных замыканий через параметры, код и лексическое окружение
Изменение определений и присваиваний через явные операции над окружением
Выбор одной скомпилированной ветви без повторного обращения к синтаксису исходного кода
Выполнение скомпилированного кода на отдельной стековой машине
compile-expression выполняет всю классификацию исходного кода. Результат содержит только инструкции literal, load, closure, define, set, discard, branch и call. Форма lambda сохраняет уже скомпилированный код тела со своими параметрами. Форма begin вставляет discard между нефинальными формами, а применение помещает код оператора перед кодом операндов перед единственной инструкцией call.
run-code выполняет диспетчеризацию по тегам скомпилированных инструкций после того, как compile-expression классифицирует формы исходного кода. Скомпилированная процедура расширяет лексическое окружение, захваченное при создании замыкания, и выполняет код своего тела на чистом стеке операндов. Эта учебная VM выполняет глобальные определения, захваченные локальные присваивания, цитирование, ветвление, замыкания и применение примитивов с помощью указанного набора инструкций.
Код SICP10,895 из 1,048,576 байт UTF-8
(begin(define(tagged-list?expressiontag)(and(pair?expression)(eq?(carexpression)tag)))(define(self-evaluating?expression)(or(number?expression)(string?expression)(boolean?expression)))(define(quoted?expression)(tagged-list?expression'quote))(define(assignment?expression)(tagged-list?expression'set!))(define(definition?expression)(tagged-list?expression'define))(define(if?expression)(tagged-list?expression'if))(define(lambda?expression)(tagged-list?expression'lambda))(define(begin?expression)(tagged-list?expression'begin))(define(definition-variableexpression)(if(symbol?(cadrexpression))(cadrexpression)(car(cadrexpression))))(define(definition-valueexpression)(if(symbol?(cadrexpression))(caddrexpression)(cons'lambda(cons(cdr(cadrexpression))(cddrexpression)))))(define(compile-sequenceexpressions)(cond((null?expressions)'((literalok)))((null?(cdrexpressions))(compile-expression(carexpressions)))(else(append(compile-expression(carexpressions))(cons'(discard)(compile-sequence(cdrexpressions)))))))(define(compile-operandsoperands)(if(null?operands)'()(append(compile-expression(caroperands))(compile-operands(cdroperands)))))(define(compile-expressionexpression)(cond((self-evaluating?expression)(list(list'literalexpression)))((symbol?expression)(list(list'loadexpression)))((quoted?expression)(list(list'literal(cadrexpression))))((assignment?expression)(append(compile-expression(caddrexpression))(list(list'set(cadrexpression)))))((definition?expression)(append(compile-expression(definition-valueexpression))(list(list'define(definition-variableexpression)))))((if?expression)(append(compile-expression(cadrexpression))(list(list'branch(compile-expression(caddrexpression))(compile-expression(cadddrexpression))))))((lambda?expression)(list(list'closure(cadrexpression)(compile-sequence(cddrexpression)))))((begin?expression)(compile-sequence(cdrexpression)))((pair?expression)(append(compile-expression(carexpression))(append(compile-operands(cdrexpression))(list(list'call(length(cdrexpression)))))))(else(error"unknown expression for compiler"expression))))(define(pair-bindingsvariablesvalues)(cond((and(null?variables)(null?values))'())((null?variables)(error"too many arguments"))((null?values)(error"too few arguments"))(else(cons(cons(carvariables)(carvalues))(pair-bindings(cdrvariables)(cdrvalues))))))(define(make-framevariablesvalues)(cons'*frame*(pair-bindingsvariablesvalues)))(define(frame-bindingsframe)(cdrframe))(define(extend-environmentvariablesvaluesenvironment)(cons(make-framevariablesvalues)environment))(define(lookup-variable-valuevariableenvironment)(if(null?environment)(error"unbound compiled variable"variable)(let((binding(assocvariable(frame-bindings(carenvironment)))))(ifbinding(cdrbinding)(lookup-variable-valuevariable(cdrenvironment))))))(define(define-variable!variablevalueenvironment)(let*((frame(carenvironment))(binding(assocvariable(frame-bindingsframe))))(ifbinding(set-cdr!bindingvalue)(set-cdr!frame(cons(consvariablevalue)(frame-bindingsframe)))))(list'definedvariable))(define(set-variable-value!variablevalueenvironment)(if(null?environment)(error"unbound compiled assignment"variable)(let((binding(assocvariable(frame-bindings(carenvironment)))))(ifbinding(begin(set-cdr!bindingvalue)(list'assignedvariable))(set-variable-value!variablevalue(cdrenvironment))))))(define(make-primitiveimplementation)(list'primitiveimplementation))(define(primitive?procedure)(tagged-list?procedure'primitive))(define(primitive-implementationprocedure)(cadrprocedure))(define(make-compiled-procedureparameterscodeenvironment)(list'compiledparameterscodeenvironment))(define(compiled-procedure?procedure)(tagged-list?procedure'compiled))(define(compiled-parametersprocedure)(cadrprocedure))(define(compiled-codeprocedure)(caddrprocedure))(define(compiled-environmentprocedure)(cadddrprocedure))(define(split-callstackcount)(define(loopremaining-stackremaining-countarguments)(if(=remaining-count0)(list(carremaining-stack)arguments(cdrremaining-stack))(loop(cdrremaining-stack)(-remaining-count1)(cons(carremaining-stack)arguments))))(loopstackcount'()))(define(run-codecodestackenvironment)(if(null?code)(liststackenvironment)(let*((instruction(carcode))(tag(carinstruction)))(cond((eq?tag'literal)(run-code(cdrcode)(cons(cadrinstruction)stack)environment))((eq?tag'load)(run-code(cdrcode)(cons(lookup-variable-value(cadrinstruction)environment)stack)environment))((eq?tag'closure)(run-code(cdrcode)(cons(make-compiled-procedure(cadrinstruction)(caddrinstruction)environment)stack)environment))((eq?tag'define)(let((result(define-variable!(cadrinstruction)(carstack)environment)))(run-code(cdrcode)(consresult(cdrstack))environment)))((eq?tag'set)(let((result(set-variable-value!(cadrinstruction)(carstack)environment)))(run-code(cdrcode)(consresult(cdrstack))environment)))((eq?tag'discard)(run-code(cdrcode)(cdrstack)environment))((eq?tag'branch)(let*((selected(if(carstack)(cadrinstruction)(caddrinstruction)))(branch-state(run-codeselected(cdrstack)environment)))(run-code(cdrcode)(carbranch-state)(cadrbranch-state))))((eq?tag'call)(let*((parts(split-callstack(cadrinstruction)))(procedure(carparts))(arguments(cadrparts))(caller-stack(caddrparts)))(if(primitive?procedure)(run-code(cdrcode)(cons(apply(primitive-implementationprocedure)arguments)caller-stack)environment)(if(compiled-procedure?procedure)(let*((call-environment(extend-environment(compiled-parametersprocedure)arguments(compiled-environmentprocedure)))(call-state(run-code(compiled-codeprocedure)'()call-environment))(value(car(carcall-state))))(run-code(cdrcode)(consvaluecaller-stack)environment))(error"not a compiled procedure"procedure)))))(else(error"unknown compiled instruction"instruction))))))(define(make-global-environment)(list(cons'*frame*(list(cons'+(make-primitive+))(cons'-(make-primitive-))(cons'*(make-primitive*))(cons'/(make-primitive/))(cons'=(make-primitive=))(cons'<(make-primitive<))(cons'>(make-primitive>))(cons'list(make-primitivelist))(cons'cons(make-primitivecons))(cons'car(make-primitivecar))(cons'cdr(make-primitivecdr))(cons'null?(make-primitivenull?))))))(define(compile-and-runexpression)(let*((code(compile-expressionexpression))(environment(make-global-environment))(state(run-codecode'()environment)))(list(car(carstate))codeenvironment)))(defineprogram'(begin(definebase10)(define(make-adderx)(lambda(y)(+xy)))(defineadd-three(make-adder3))(set!base(+base5))(if(>base12)(list(add-threebase)(quotelarge)base)(list0(quotesmall)base))))(defineresult(compile-and-runprogram))(list(carresult)(mapcar(cadrresult))(length(cadrresult))))
Сначала проверьте теги кода и убедитесь, что в них не осталось тегов форм исходного кода. Во время выполнения проследите создание замыкания до вызова make-adder или make-counter, новый кадр вызова для каждого скомпилированного применения, изменение захваченной записи start между вызовами counter и выбор ровно одного вложенного списка кода в branch.
Попробуйте сами
Измените программу и сравните результат.
Добавьте рекурсивную скомпилированную процедуру или второе вложенное замыкание. Спрогнозируйте теги кода верхнего уровня, затем определите, в каком окружении процедуры каждая инструкция load и set должна выполнять поиск.
Показать подсказку
Определение помещает скомпилированное замыкание в общий глобальный кадр после того, как замыкание захватило этот объект кадра, поэтому последующий рекурсивный поиск сможет найти установленное имя.