Source forms become instruction tags before execution begins.
Compile values, variables, quotation, assignment, conditionals, lambdas, and applications into an instruction language, then execute the result.
Guiding question
What source-language decisions disappear from the execution loop after compilation?
Compile every core expression category to explicit instruction data
Preserve left-to-right operator and operand order in calls
Represent compiled closures with parameters, code, and lexical environment
Mutate definitions and assignments through explicit environment operations
Select one compiled branch without revisiting source syntax
Execute compiled code with a separate stack machine
compile-expression performs every source classification. The result contains only literal, load, closure, define, set, discard, branch, and call instructions. A lambda stores already compiled body code with its parameters. A begin inserts discard between nonfinal forms, and an application places operator code before operand code before one call instruction.
run-code dispatches on compiled instruction tags after compile-expression has classified the source forms. A compiled procedure extends the lexical environment captured at closure construction and runs its body code on a fresh operand stack. This lesson VM executes global definition, captured local assignment, quotation, branching, closures, and primitive application through the listed instruction set.
SICP code10,895 of 1,048,576 UTF-8 bytes
(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))))
First inspect the code tags and confirm that no source-form tags remain. During execution, follow closure creation before make-adder or make-counter is called, the fresh call frame for each compiled application, the captured start record changing across counter calls, and the branch selecting exactly one nested code list.
Try it yourself
Change the program and compare the result.
Add a recursive compiled procedure or a second nested closure. Predict the top-level code tags, then identify which procedure environment each load and set instruction must search.
Show hint
Definition installs the compiled closure in the shared global frame after the closure has captured that frame object, so later recursive lookup can find the installed name.