From bfdb83952ec23f2afd3983fd983abf30cec194ae Mon Sep 17 00:00:00 2001 From: David Van Horn Date: Mon, 1 Jun 2026 08:47:21 -0400 Subject: [PATCH] Replace main with next. --- www/Makefile | 2 +- www/assignments.scrbl | 12 +- www/assignments/0.scrbl | 7 + www/assignments/1.scrbl | 15 +- www/assignments/10.scrbl | 265 +++++++++++++++- www/assignments/2.scrbl | 15 +- www/assignments/3.scrbl | 10 +- www/assignments/4.scrbl | 13 +- www/assignments/5.scrbl | 16 +- www/assignments/6.scrbl | 12 +- www/assignments/7-overloading.scrbl | 160 ++++++++++ www/assignments/7.scrbl | 122 +------ www/assignments/8.scrbl | 9 +- www/assignments/9.scrbl | 2 +- www/defns.rkt | 29 +- www/exams.scrbl | 18 ++ www/{midterms => exams}/1.scrbl | 26 +- www/{midterms => exams}/2.scrbl | 26 +- www/main.scrbl | 4 +- www/midterms.scrbl | 15 - www/notes/1/ocaml-to-racket.scrbl | 47 ++- www/notes/a86.scrbl | 114 ++++--- www/notes/abscond.scrbl | 325 +++++++++++-------- www/notes/blackmail.scrbl | 43 +-- www/notes/con.scrbl | 181 ++++++++--- www/notes/dodger.scrbl | 17 +- www/notes/dupe.scrbl | 131 +++++--- www/notes/evildoer.scrbl | 474 ++++++++++++++-------------- www/practice.scrbl | 10 + www/schedule.scrbl | 65 ++-- www/syllabus.scrbl | 186 +++++------ 31 files changed, 1514 insertions(+), 857 deletions(-) create mode 100644 www/assignments/0.scrbl create mode 100644 www/assignments/7-overloading.scrbl create mode 100644 www/exams.scrbl rename www/{midterms => exams}/1.scrbl (70%) rename www/{midterms => exams}/2.scrbl (71%) delete mode 100644 www/midterms.scrbl create mode 100644 www/practice.scrbl diff --git a/www/Makefile b/www/Makefile index 4ad83e01..3ba42d28 100644 --- a/www/Makefile +++ b/www/Makefile @@ -11,7 +11,7 @@ $(course): scribble zips langs = abscond blackmail con dupe dodger evildoer extort fraud hustle hoax iniquity iniquity-gc jig knock loot mug mountebank neerdowell outlaw -assigns = hoax-plus +assigns = dupe-plus dupe-plus-plus fraud-plus hoax-plus iniquity-plus knock-plus loot-exceptions zips: mkdir -p $(course)/code/ diff --git a/www/assignments.scrbl b/www/assignments.scrbl index c8bb4f05..b60951f2 100644 --- a/www/assignments.scrbl +++ b/www/assignments.scrbl @@ -3,17 +3,17 @@ @local-table-of-contents[#:style 'immediate-only] -@include-section{assignments/1.scrbl} -@include-section{assignments/2.scrbl} +@include-section{assignments/0.scrbl} @include-section{assignments/3.scrbl} @include-section{assignments/4.scrbl} @include-section{assignments/5.scrbl} @include-section{assignments/6.scrbl} @include-section{assignments/7.scrbl} +@include-section{assignments/7-overloading.scrbl} @include-section{assignments/8.scrbl} -@;include-section{assignments/9.scrbl} -@;include-section{assignments/10.scrbl} +@include-section{assignments/9.scrbl} +@include-section{assignments/10.scrbl} -@;{assignment 8: quote in general, and quasiquote} -@;{assignment 9: standard library, IO} +@;{assignment 9: quote in general, and quasiquote} +@;{assignment 10: standard library, IO} diff --git a/www/assignments/0.scrbl b/www/assignments/0.scrbl new file mode 100644 index 00000000..4fa6c939 --- /dev/null +++ b/www/assignments/0.scrbl @@ -0,0 +1,7 @@ +#lang scribble/manual +@(require "../defns.rkt") +@title[#:tag "Assignment 1" #:style 'unnumbered]{Assignment 1} + +@bold{Due: @assign-deadline[1]} + +Details of this assignment will be released later. diff --git a/www/assignments/1.scrbl b/www/assignments/1.scrbl index 41346251..759957c0 100644 --- a/www/assignments/1.scrbl +++ b/www/assignments/1.scrbl @@ -1,18 +1,15 @@ #lang scribble/manual @(require "../defns.rkt") -@title[#:tag "Assignment 1" #:style 'unnumbered]{Assignment 1: Racket primer} +@title[#:tag "Practice 1" #:style 'unnumbered]{Practice 1: Racket primer} -@bold{Due: @assign-deadline[1]} +@bold{Starter code:} @link["code/racket-basics.zip"]{@tt{racket-basics.zip}} The goal of this assignment is to gain practice programming in Racket. -@bold{This is a collaborative assignment.} You may work with anyone -you'd like on this assignment, but each person must submit their -@tt{submit.zip} file on Gradescope. - -You are given a @tt{racket-basics.zip} file (in ELMS, linked in the -description for Assignment 1) that contains a README, a Makefile, and -a number of Racket modules. In each module there are several function +Use the starter code in +@link["code/racket-basics.zip"]{@tt{racket-basics.zip}}, which contains +a README, a Makefile, and a number of Racket modules. In each module +there are several function ``stubs,'' i.e. incomplete function definitions with type signatures, descriptions, and a small set of tests. Each function has a bogus (but type correct) body marked with a ``TODO'' comment. Your job is diff --git a/www/assignments/10.scrbl b/www/assignments/10.scrbl index e1790abd..f099de7d 100644 --- a/www/assignments/10.scrbl +++ b/www/assignments/10.scrbl @@ -1,9 +1,268 @@ #lang scribble/manual @(require "../defns.rkt") -@title[#:tag "Assignment 10" #:style 'unnumbered]{Assignment 10: Patterns} +@(require "../notes/ev.rkt" + "../notes/utils.rkt") +@(require (for-label (except-in racket ...))) -@(require (for-label a86 (except-in racket ...))) +@title[#:tag "Assignment 10" #:style 'unnumbered]{Assignment 10: Exceptions} @bold{Due: @assign-deadline[10]} -Details of this assignment will be released later in the semester. +@bold{Starter code:} @link["code/loot-exceptions.zip"]{@tt{loot-exceptions.zip}} + +@(ev '(require loot-exceptions)) + +The goal of this assignment is to add an exception handling mechanism +to the Loot compiler. + +An exception handler consists of a predicate and a function. When a +value is raised to a handler, the predicate is applied and if it +produces a true value, the raised value is handled by applying the +function to it. If the predicate returns @racket[#f], the value is +raised to the next exception handler. An error is signalled if a +raised value is never handled by an exception handler. + +Use the starter code in +@link["code/loot-exceptions.zip"]{@tt{loot-exceptions.zip}}, which is +like Loot, but the parser, AST, and interpreter have been extended to +include these new features. You have to extend the compiler to match +the specification of the interpreter. + +The key features that need to be added are: + +@itemlist[ + +@item{@racket[(raise _e)] evaluates @racket[_e] and then raises the +value to the most recently installed exception handler, thereby +abandoning whatever computation surrounds the @racket[(raise _e)] +expression up to the next exception handler. If there is no exception +handler installed, an error is signalled.} + +@item{@racket[(with-handlers ([_e1 _e2]) _e)] evaluates +@racket[_e1], then @racket[_e2]. The values of @racket[_e1] and +@racket[_e2] are installed as the current exception handler predicate +and function, respectively, while @racket[_e] is evaluated. + +Should @racket[_e] produce a value @racket[_v], the exception handler +is removed and the @racket[with-handlers] expression evaluates to +@racket[_v]. But if in evaluating @racket[_e], a value is raised that +reaches this exception handler, the predicate is applied. If the +predicate returns a true value, the result of the +@racket[with-handlers] expression is computed by applying the handler +function to the raised value. If the predicate returns false, the +value continues being raised, thereby abandoning whatever computation +surrounds the @racket[with-handlers] expression up to the next +exception handler.} + +] + +@section[#:tag-prefix "a10-" #:style 'unnumbered]{Examples} + +@ex[ +(interp (parse '(raise 1))) +] + +This signals an error because there is no exception handler installed +that handles the raised value @racket[1]. + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) #t) (lambda (x) 0)]) + 1))) +] + +This evaluates to @racket[1] because even though an exception handler +is installed, the body of the @racket[with-handlers] expression never +raises. + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) #t) (lambda (x) 0)]) + (raise 1)))) +] + +This evaluates to @racket[0] because the predicate returns +@racket[#t] when applied to @racket[1], so the result of the +@racket[with-handlers] expression is computed by applying the handler +function to @racket[1]. + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) #f) (lambda (x) 0)]) + (raise 1)))) +] + +This signals an error because although there is an exception handler +installed when @racket[1] is raised, the predicate returns +@racket[#f], so the value continues to be raised to the enclosing +exception handler, but there is no such handler. + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) #t) (lambda (x) 2)]) + (with-handlers ([(lambda (x) #f) (lambda (x) 0)]) + (raise 1))))) +] + +This evaluates to @racket[2] because @racket[1] is raised to the most +recent handler, which does not handle it, and then to the next handler, +which does handle it. + +Note that when a value is raised, it abandons the computation at the +point of the raise. + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) #t) (lambda (x) x)]) + (+ (raise 1) (add1 #f))))) +] + +Here the addition computation is abandoned when @racket[1] is raised. +Consequently, the expression @racket[(add1 #f)] is never evaluated and +the whole expression evaluates to @racket[1]. + +Since the handler predicate and function are arbitrary functions, they +too can raise exceptions, which will be handled by the surrounding +exception handlers. + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) (if (zero? x) #t (raise 0))) + (lambda (x) x)]) + (raise 1)))) +] + +This signals an error because in applying the predicate to +@racket[1], the value @racket[0] is raised, and there is no enclosing +handler. + +It's important to note that the most recently installed handler is a +dynamic property; it is not a lexical property. A @racket[raise] +expression may occur lexically outside of the @racket[with-handlers] +form that ends up handling it. + +@ex[ +(interp + (parse + '(define (f x) (raise x)) + '(with-handlers ([(lambda (x) #t) (lambda (x) x)]) + (f 3)))) +] + +Here, calling @racket[f] triggers the raising of @racket[3], which is +handled by the @racket[with-handlers] expression, producing +@racket[3]. + +@section[#:tag-prefix "a10-" #:style 'unnumbered]{Differences with Racket's exception system} + +There are important differences between Loot exceptions and Racket's +exception system. + +In Loot, errors are distinct from exceptions. For example, +@racket[(add1 #f)] signals an error. It does not raise an exception +and therefore it cannot be handled with an exception handler: + +@ex[ +(interp + (parse + '(with-handlers ([(lambda (x) #t) (lambda (x) 1)]) + (add1 #f)))) +] + +Racket, on the other hand, uses its exception mechanism to signal +errors: + +@ex[ +(with-handlers ([(lambda (x) #t) (lambda (x) 1)]) + (add1 #f)) +] + +Consequently, Racket evaluates this expression to @racket[1] for this +example. + +This simplifies things in Loot because the only way something can be +raised is an explicit @racket[raise] expression and anything that used +to be an error continues to be an error. + +Since there are subtle differences between Loot exceptions and Racket +exceptions, use the interpreter for guidance on what a particular +example should do. + +Another difference is syntactic: Racket allows each +@racket[with-handlers] form to have any number of predicate and +function clauses, but for our purposes, each @racket[with-handlers] +has exactly one predicate and function. + +@section[#:tag-prefix "a10-" #:style 'unnumbered]{Implementation hints} + +An implementation of exception handling for Loot can be fairly +succinct, but tricky. + +The basic idea is that a handler is installed by +@racket[with-handlers] by pushing some information on the stack before +executing the code for the body expression. This information will +include the predicate value and function value for the handler. + +If execution of the body makes it through to the end, a value is being +returned normally, and the handler information can be popped off the +stack. However, if at some point during execution of the body there is +a raise, then the compiler needs to start the process of handling the +raised value, applying the predicate, and so on. A basic problem is +that there may be an arbitrary amount of stuff pushed on to the stack +between the handler information and the point where the raise occurs. + +For example: + +@ex[ +(define (f x) + (if (zero? x) + (raise x) + (+ x (f (sub1 x))))) + +(with-handlers ([(lambda (x) #t) (lambda (x) 1)]) + (f 100)) +] + +The handler is pushed on the stack. As the recursion unfolds, return +pointers, arguments, and intermediate results will be pushed on the +stack. This continues until reaching the base case, at which point the +raise occurs. + +Everything on the stack up to the handler should be discarded because +the raise discards the computation that has built up to this point: +all those pending function calls will never be returned to. Instead, +control needs to transfer back to the @racket[with-handlers] +expression and execute the code that follows it. + +A simple approach is to designate a register to hold a pointer to the +current handler on the stack. When executing a @racket[raise], the +stack can be popped back to the handler, giving access to the +predicate, the function, and the return label used to jump back to the +@racket[with-handlers] expression. + +This works when there is exactly one exception handler, but nested +handlers require one more piece of information: a pointer to the parent +handler. + +So each handler can be represented by four things pushed on the stack: + +@itemlist[ +@item{a predicate,} +@item{a function,} +@item{a return label, and} +@item{a pointer to the parent handler on the stack, if there is one.} +] + +A designated register can then hold a pointer to the current handler. + +@section[#:tag-prefix "a10-" #:style 'unnumbered]{Submitting} + +Submit a zip file containing your work to Gradescope. Use +@tt{make submit.zip} from within the @tt{loot-exceptions} directory to +create a zip file with the proper structure. diff --git a/www/assignments/2.scrbl b/www/assignments/2.scrbl index 8a4eda36..2498e372 100644 --- a/www/assignments/2.scrbl +++ b/www/assignments/2.scrbl @@ -1,18 +1,15 @@ #lang scribble/manual @(require "../defns.rkt") -@title[#:tag "Assignment 2" #:style 'unnumbered]{Assignment 2: Assembly primer} +@title[#:tag "Practice 2" #:style 'unnumbered]{Practice 2: Assembly primer} -@bold{Due: @assign-deadline[2]} +@bold{Starter code:} @link["code/a86-basics.zip"]{@tt{a86-basics.zip}} The goal of this assignment is to gain practice programming in a86. -@bold{This is a collaborative assignment.} You may work with anyone -you'd like on this assignment, but each person must submit their -@tt{submit.zip} file on Gradescope. - -You are given a @tt{a86-basics.zip} file (in ELMS, linked in the -description for Assignment 2), that contains a README, a Makefile, and -a number of Racket modules. In each module there are several +Use the starter code in +@link["code/a86-basics.zip"]{@tt{a86-basics.zip}}, which contains a +README, a Makefile, and a number of Racket modules. In each module +there are several ``stubs,'' i.e. incomplete definitions with type signatures, descriptions, and a small set of tests. Each definition has a bogus (but type correct) body marked with a ``TODO'' comment. Your job is diff --git a/www/assignments/3.scrbl b/www/assignments/3.scrbl index 7dd69e03..91fda359 100644 --- a/www/assignments/3.scrbl +++ b/www/assignments/3.scrbl @@ -1,10 +1,12 @@ #lang scribble/manual @(require "../defns.rkt") -@title[#:tag "Assignment 3" #:style 'unnumbered]{Assignment 3: Primitives, conditionals} +@title[#:tag "Assignment 2" #:style 'unnumbered]{Assignment 2: Primitives, conditionals} @(require (for-label a86/ast (except-in racket ...))) -@bold{Due: @assign-deadline[3]} +@bold{Due: @assign-deadline[2]} + +@bold{Starter code:} @link["code/dupe-plus.zip"]{@tt{dupe-plus.zip}} The goal of this assignment is to extend the language developed in @secref{Dupe} with some simple unary numeric and boolean operations @@ -58,8 +60,8 @@ expression @racket[_e-an]'s value is the value of the @racket[cond]. @section[#:tag-prefix "a3-" #:style 'unnumbered]{Implementing Dupe+} You must extend the interpreter and compiler to implement Dupe+. (The -parser for Dupe+ is given to you.) You are given a file -@tt{dupe-plus.zip} on ELMS with a starter compiler based on the +parser for Dupe+ is given to you.) Use the starter code in +@link["code/dupe-plus.zip"]{@tt{dupe-plus.zip}}, which is based on the @secref{Dupe} language we studied in class. You may use any a86 instructions you'd like, however it is possible to diff --git a/www/assignments/4.scrbl b/www/assignments/4.scrbl index fd9bc46a..21dc36db 100644 --- a/www/assignments/4.scrbl +++ b/www/assignments/4.scrbl @@ -1,10 +1,12 @@ #lang scribble/manual @(require "../defns.rkt") -@title[#:tag "Assignment 4" #:style 'unnumbered]{Assignment 4: Case} +@title[#:tag "Assignment 3" #:style 'unnumbered]{Assignment 3: Case} @(require (for-label a86/ast (except-in racket ...))) -@bold{Due: @assign-deadline[4]} +@bold{Due: @assign-deadline[3]} + +@bold{Starter code:} @link["code/dupe-plus-plus.zip"]{@tt{dupe-plus-plus.zip}} The goal of this assignment is to extend the language developed in @secref{a3-dupe-plus} with another new form of control flow expressions: @@ -57,9 +59,10 @@ this is not true in general for Racket. The parser will reject any @section[#:tag-prefix "a4-" #:style 'unnumbered]{Implementing Dupe++} -You must extend the interpreter and compiler to implement Dupe++. You -are given a file @tt{dupe-plus-plus.zip} on ELMS with a starter -compiler based on the @secref{Dupe} language we studied in class. +You must extend the interpreter and compiler to implement Dupe++. Use +the starter code in +@link["code/dupe-plus-plus.zip"]{@tt{dupe-plus-plus.zip}}, which is +based on the @secref{Dupe} language we studied in class. You may use any a86 instructions you'd like, however it is possible to complete the assignment using @racket[Cmp], @racket[Je], @racket[Jg], diff --git a/www/assignments/5.scrbl b/www/assignments/5.scrbl index b5d7eab6..d350ddcb 100644 --- a/www/assignments/5.scrbl +++ b/www/assignments/5.scrbl @@ -2,23 +2,25 @@ @(require "../defns.rkt" "../notes/ev.rkt") -@title[#:tag "Assignment 5" #:style 'unnumbered]{Assignment 5: Let There Be (Many) Variables} +@title[#:tag "Assignment 4" #:style 'unnumbered]{Assignment 4: Let There Be (Many) Variables} @(require (for-label a86/ast (except-in racket ...))) @(ev '(require fraud-plus fraud-plus/compiler/compile fraud-plus/executor/run)) @(ev '(define (exec e) (run (compile e)))) -@bold{Due: @assign-deadline[5]} +@bold{Due: @assign-deadline[4]} + +@bold{Starter code:} @link["code/fraud-plus.zip"]{@tt{fraud-plus.zip}} The goal of this assignment is to extend a compiler with binding forms that can take any number of arguments. @section[#:tag-prefix "a5-" #:style 'unnumbered]{Overview} -For this assignment, you are given a @tt{fraud-plus.zip} file on ELMS -with a starter compiler similar to the @seclink["Fraud"]{Fraud} -language we studied in class. +For this assignment, use the starter code in +@link["code/fraud-plus.zip"]{@tt{fraud-plus.zip}}, which is similar to +the @seclink["Fraud"]{Fraud} language we studied in class. @section[#:tag-prefix "a5-" #:style 'unnumbered]{Fraud+} @@ -27,7 +29,7 @@ new features: @itemlist[ -@item{The features added in @seclink["Assignment 4"]{Assignment 4}, namely: +@item{The features added in @seclink["Assignment 3"]{Assignment 3}, namely: @itemlist[ @@ -49,7 +51,7 @@ new features: Implement the @racket[abs], unary @racket[-], and @racket[not] operations and the @racket[cond] and @racket[case] forms from -@seclink["Assignment 4"]{Assignment 4} by modifying @tt{interp.rkt}, +@seclink["Assignment 3"]{Assignment 3} by modifying @tt{interp.rkt}, @tt{interp-prim.rkt}, @tt{compile.rkt}, and @tt{compile-ops.rkt}. You can start from your previous code, but you will need to update it to work for the code you are given. What's essentially left for you to do is to make sure to diff --git a/www/assignments/6.scrbl b/www/assignments/6.scrbl index 1746d04c..38d1a5f6 100644 --- a/www/assignments/6.scrbl +++ b/www/assignments/6.scrbl @@ -2,7 +2,7 @@ @(require "../defns.rkt" "../notes/ev.rkt" "../notes/utils.rkt") -@title[#:tag "Assignment 6" #:style 'unnumbered]{Assignment 6: List and vector primitives} +@title[#:tag "Assignment 5" #:style 'unnumbered]{Assignment 5: List and vector primitives} @src-code["hoax-plus"] @@ -10,7 +10,9 @@ @(require racket/port) -@bold{Due: @assign-deadline[6]} +@bold{Due: @assign-deadline[5]} + +@bold{Starter code:} @link["code/hoax-plus.zip"]{@tt{hoax-plus.zip}} @(ev '(require hoax-plus)) @@ -20,9 +22,9 @@ list and vector primitives. @section[#:tag-prefix "a6-" #:style 'unnumbered]{Overview} -For this assignment, you are given a @tt{hoax-plus.zip} file on ELMS -with a starter compiler similar to the @seclink["Hoax"]{Hoax} -language we studied in class. +For this assignment, use the starter code in +@link["code/hoax-plus.zip"]{@tt{hoax-plus.zip}}, which is similar to +the @seclink["Hoax"]{Hoax} language we studied in class. @section[#:tag-prefix "a6-" #:style 'unnumbered]{Hoax+} diff --git a/www/assignments/7-overloading.scrbl b/www/assignments/7-overloading.scrbl new file mode 100644 index 00000000..6814ae02 --- /dev/null +++ b/www/assignments/7-overloading.scrbl @@ -0,0 +1,160 @@ +#lang scribble/manual +@(require "../defns.rkt" + "../notes/ev.rkt") + +@title[#:tag "Assignment 7" #:style 'unnumbered]{Assignment 7: +Arity-overloaded functions} + +@(require (for-label a86 (except-in racket ...))) + +@bold{Due: @assign-deadline[7]} + +@bold{Starter code:} @link["code/iniquity-plus.zip"]{@tt{iniquity-plus.zip}} + +@(ev '(require iniquity-plus)) + +The goal of this assignment is to implement arity-overloaded functions. + +@section[#:tag-prefix "a7o-" #:style 'unnumbered]{Overview} + +For this assignment, use the starter code in +@link["code/iniquity-plus.zip"]{@tt{iniquity-plus.zip}}, which is +similar to the @seclink["Iniquity"]{Iniquity} language we studied in +class, extended with the variable-arity function definitions from +@seclink["Assignment 6"]{Assignment 6}. + +@section[#:tag-prefix "a7o-" #:style 'unnumbered #:tag "case-lambda"]{Arity dispatch} + +Some languages such as Java, Haskell, and Racket make it possible to +overload a single function name with multiple definitions where the +dispatch between these different definitions is performed based on the +number (or kind) of arguments given at a function call. + +In Racket, this is accomplished with the @racket[case-lambda] form for +constructing multiple-arity functions. + +Here is an example: + +@ex[ +(define f + (case-lambda + [(x) "got one!"] + [(p q) "got two!"])) + +(f #t) +(f #t #f) +(eval:error (f #t #f 0)) +] + +This function can accept @emph{either} one or two arguments. If given +one argument, it evaluates the right-hand-side of the first clause +with @racket[x] bound to that argument. If given two arguments, it +evaluates the right-hand-side of the second clause with @racket[p] and +@racket[q] bound to the arguments. If given any other number of +arguments, it signals an error. + +A @racket[case-lambda] form can have any number of clauses (including +zero!) and the first clause for which the number of arguments is +acceptable is taken when the function is called. + +Note that @racket[case-lambda] can be combined with rest arguments too. +A clause that accepts any number of arguments is written by simply +listing a parameter name (no parentheses). A clause that accepts some +non-zero minimum number of parameters is written with a dotted +parameter list. + +For example: + +@ex[ +(define f + (case-lambda + [(x y z . r) (length r)] + [(x) "just one!"])) + +(f 1 2 3 4 5 6) +(f #t) +(eval:error (f)) +(eval:error (f 1 2))] + +This function takes three or more arguments @emph{or} one argument. Any +other number of arguments (i.e. zero or two) results in an error. + +@ex[ +(define f + (case-lambda + [(x y z) "three!"] + [xs (length xs)])) + +(f) +(f 1 2) +(f 1 2 3) +(f 1 2 3 4 5 6) +] + +This function takes any number of arguments, but when given three, it +produces @racket["three!"]; in all other cases it produces the number +of arguments. + +@section[#:tag-prefix "a7o-" #:style 'unnumbered]{Representing overloaded definitions} + +In this assignment, Iniquity+ adds a third kind of function +representation: + +@#reader scribble/comment-reader +(racketblock +;; type Defn = (Defn Id Fun) +(struct Defn (f fun) #:prefab) + +;; type Fun = (FunPlain [Listof Id] Expr) +;; | (FunRest [Listof Id] Id Expr) +;; | (FunCase [Listof FunCaseClause]) +;; type FunCaseClause = (FunPlain [Listof Id] Expr) +;; | (FunRest [Listof Id] Id Expr) +(struct FunPlain (xs e) #:prefab) +(struct FunRest (xs x e) #:prefab) +(struct FunCase (cs) #:prefab) +) + +The parser already works for these new forms of function definitions. +Here is an example of how overloaded function definitions are parsed: + +@ex[ +(parse-define + '(define f + (case-lambda + [(x y) 2] + [(z) 1] + [(a b c . d) "3+"] + [q "other"]))) +] + +@section[#:tag-prefix "a7o-" #:style 'unnumbered]{Starter code} + +The compiler code given to you supports plain and rest-argument +function definitions. The interpreter code given to you works on the +full Iniquity+ language, so you do not need to update @racket[interp.rkt] +and can use the interpreter to guide your implementation of the compiler. + +@ex[ +(interp + (parse '(define f + (case-lambda + [(x y) 2] + [(z) 1] + [(a b c . d) "3+"] + [q "other"])) + '(cons (f 7) + (cons (f 3 4) + (cons (f) + (cons (f 7 8 9 10 11) + '())))))) +] + +Thus, you should only need to modify @racket[compile.rkt]. + +A small number of test cases are given as usual. + +@section[#:tag-prefix "a7o-" #:style 'unnumbered]{Submitting} + +To submit, use @tt{make} from within the @tt{iniquity-plus} directory +to create a zip file containing your work and submit it to Gradescope. diff --git a/www/assignments/7.scrbl b/www/assignments/7.scrbl index e2deffa5..a2f12b63 100644 --- a/www/assignments/7.scrbl +++ b/www/assignments/7.scrbl @@ -2,24 +2,27 @@ @(require "../defns.rkt" "../notes/ev.rkt") -@title[#:tag "Assignment 7" #:style 'unnumbered]{Assignment 7: -Variable-arity and arity-overloaded functions} +@title[#:tag "Assignment 6" #:style 'unnumbered]{Assignment 6: +Variable-arity functions} @(require (for-label a86 (except-in racket ...))) -@bold{Due: @assign-deadline[7]} +@bold{Due: @assign-deadline[6]} + +@bold{Starter code:} @link["code/iniquity-plus.zip"]{@tt{iniquity-plus.zip}} @(ev '(require iniquity-plus)) The goal of this assignment is to implement variable arity functions. -@section[#:tag-prefix "a7-" #:style 'unnumbered]{Overview} +@section[#:tag-prefix "a7v-" #:style 'unnumbered]{Overview} -For this assignment, you are given a @tt{iniquity-plus.zip} file on ELMS -with a starter compiler similar to the @seclink["Iniquity"]{Iniquity} -language we studied in class. +For this assignment, use the starter code in +@link["code/iniquity-plus.zip"]{@tt{iniquity-plus.zip}}, which is +similar to the @seclink["Iniquity"]{Iniquity} language we studied in +class. -@section[#:tag-prefix "a7-" #:style 'unnumbered #:tag "rest"]{Rest +@section[#:tag-prefix "a7v-" #:style 'unnumbered #:tag "rest"]{Rest arguments} Many languages including JavaScript, C, C++, Java, Ruby, Go, PHP, and @@ -97,79 +100,7 @@ constructing a list with with popped elements, and then finally pushes this list in order to bind it to the rest parameter. -@section[#:tag-prefix "a7-" #:style 'unnumbered #:tag "case-lambda"]{Arity dispatch} - -Some languages such as Java, Haskell, and Racket make it possible to -overload a single function name with multiple definitions where the -dispatch between these different definitions is performed based on the -number (or kind) of arguments given at a function call. - -In Racket, this is accomplished with the @racket[case-lambda] form for -constructing multiple-arity functions. - -Here is an example: - -@ex[ -(define f - (case-lambda - [(x) "got one!"] - [(p q) "got two!"])) - -(f #t) -(f #t #f) -(eval:error (f #t #f 0)) -] - -This function can accept @emph{either} one or two arguments. If given -one argument, it evaluates the right-hand-side of the first clause -with @racket[x] bound to that argument. If given two arguments, it -evaluates the right-hand-side of the second clause with @racket[p] and -@racket[q] bound to the arguments. If given any other number of -arguments, it signals an error. - -A @racket[case-lambda] form can have any number of clauses (including -zero!) and the first clause for which the number of arguments is -acceptable is taken when the function is called. - -Note that @racket[case-lambda] can be combined with rest arguments too. -A clause that accepts any number of arguments is written by simply -listing a parameter name (no parentheses). A clause that accepts some -non-zero minimum number of parameters is written with a dotted -parameter list. - -For example: - -@ex[ -(define f - (case-lambda - [(x y z . r) (length r)] - [(x) "just one!"])) - -(f 1 2 3 4 5 6) -(f #t) -(eval:error (f)) -(eval:error (f 1 2))] - -This function takes three or more arguments @emph{or} one argument. Any -other number of arguments (i.e. zero or two) results in an error. - -@ex[ -(define f - (case-lambda - [(x y z) "three!"] - [xs (length xs)])) - -(f) -(f 1 2) -(f 1 2 3) -(f 1 2 3 4 5 6) -] - -This function takes any number of arguments, but when given three, it -produces @racket["three!"]; in all other cases it produces the number -of arguments. - -@section[#:tag-prefix "a7-" #:style 'unnumbered]{Representing the +@section[#:tag-prefix "a7v-" #:style 'unnumbered]{Representing the syntax of function definitions} The @seclink["Iniquity"]{Iniquity} language has a single function @@ -182,7 +113,7 @@ with the following AST type: (struct Defn (f xs e) #:prefab) ) -Because there are three different forms of function definition in +Because there are two different forms of function definition in Iniquity+, we use the following AST representation: @#reader scribble/comment-reader @@ -192,12 +123,8 @@ Iniquity+, we use the following AST representation: ;; type Fun = (FunPlain [Listof Id] Expr) ;; | (FunRest [Listof Id] Id Expr) -;; | (FunCase [Listof FunCaseClause]) -;; type FunCaseClause = (FunPlain [Listof Id] Expr) -;; | (FunRest [Listof Id] Id Expr) (struct FunPlain (xs e) #:prefab) (struct FunRest (xs x e) #:prefab) -(struct FunCase (cs) #:prefab) ) What used to be represented as @racket[(Defn _f _xs _e)] is now @@ -212,16 +139,9 @@ are encouraged to try out more to get a better sense: (parse-define '(define (f x) x)) (parse-define '(define (f . xs) xs)) (parse-define '(define (f x y z . q) q)) -(parse-define - '(define f - (case-lambda - [(x y) 2] - [(z) 1] - [(a b c . d) "3+"] - [q "other"]))) ] -@section[#:tag-prefix "a7-" #:style 'unnumbered]{Starter code} +@section[#:tag-prefix "a7v-" #:style 'unnumbered]{Starter code} The compiler code given to you is just an implementation of Iniquity, but updated to parse the new forms of function definitions and @@ -244,18 +164,6 @@ the interpreter to guide your implementation of the compiler. (interp (parse '(define (f . x) x) '(f 1 2 3 4 5))) -(interp - (parse '(define f - (case-lambda - [(x y) 2] - [(z) 1] - [(a b c . d) "3+"] - [q "other"])) - '(cons (f 7) - (cons (f 3 4) - (cons (f) - (cons (f 7 8 9 10 11) - '())))))) ] @@ -263,7 +171,7 @@ Thus, you should only need to modify @racket[compile.rkt]. A small number of test cases are given as usual. -@section[#:tag-prefix "a7-" #:style 'unnumbered]{Submitting} +@section[#:tag-prefix "a7v-" #:style 'unnumbered]{Submitting} To submit, use @tt{make} from within the @tt{iniquity-plus} directory to create a zip file containing your work and submit it to Gradescope. diff --git a/www/assignments/8.scrbl b/www/assignments/8.scrbl index 7762ccbb..d3f53ec4 100644 --- a/www/assignments/8.scrbl +++ b/www/assignments/8.scrbl @@ -8,14 +8,17 @@ @bold{Due: @assign-deadline[8]} +@bold{Starter code:} @link["code/knock-plus.zip"]{@tt{knock-plus.zip}} + @(ev '(require knock-plus)) The goal of this assignment is to extend a compiler with new pattern matching forms for matching lists, vectors, and predicates. -You are given a file @tt{knock-plus.zip} on ELMS with a starter -compiler similar to the @seclink["Knock"]{Knock} language we studied -in class. You are tasked with: +Use the starter code in +@link["code/knock-plus.zip"]{@tt{knock-plus.zip}}, which is similar to +the @seclink["Knock"]{Knock} language we studied in class. You are +tasked with: @itemlist[ diff --git a/www/assignments/9.scrbl b/www/assignments/9.scrbl index 6f5d77f2..5dfcdd7e 100644 --- a/www/assignments/9.scrbl +++ b/www/assignments/9.scrbl @@ -1,6 +1,6 @@ #lang scribble/manual @(require "../defns.rkt") -@title[#:tag "Assignment 9" #:style 'unnumbered]{Assignment 9: Patterns} +@title[#:tag "Assignment 9" #:style 'unnumbered]{Assignment 9} @(require (for-label a86 (except-in racket ...))) diff --git a/www/defns.rkt b/www/defns.rkt index 89be1755..16b786d6 100644 --- a/www/defns.rkt +++ b/www/defns.rkt @@ -25,13 +25,15 @@ (define office-hour-location "TBD") (define start-date "June 1") -(define m1-date "June 16") -(define m2-date "July 1") -(define midterm-hours "24") +(define exam1-date "June 16") +(define exam2-date "July 1") +(define exam3-date "July 10") +(define exam-hours "24") (define final-date "July 10") -(define final-end-time "11:59pm") +(define exam-end-time "11:59pm") (define elms-url "https://umd.instructure.com/courses/1406982") +(define online? #t) (define racket-version "8.18") @@ -45,19 +47,26 @@ ;(define discord "TBD") (define piazza "https://piazza.com/umd/summer2026/cmsc430/home") -(define gradescope "https://www.gradescope.com/") +(define gradescope "https://www.gradescope.com/courses/1319905") (define feedback "https://forms.gle/99yTz7HVfopCaDMz9") +(define (practice-deadline i) + (list-ref '("Thursday, June 4, 11:59PM" + "Friday, June 5, 11:59PM") + (sub1 i))) + (define (assign-deadline i) (list-ref '("Thursday, June 4, 11:59PM" - "Friday, June 5, 11:59PM" - "Friday, June 12, 11:59PM" + "Monday, June 8, 11:59PM" + "Thursday, June 11, 11:59PM" + "Monday, June 15, 11:59PM" + "Thursday, June 18, 11:59PM" "Monday, June 22, 11:59PM" + "Thursday, June 25, 11:59PM" "Monday, June 29, 11:59PM" - "Monday, July 6, 11:59PM" - "Wednesday, July 8, 11:59PM" - "Thursday, July 9, 11:59PM") + "Thursday, July 2, 11:59PM" + "Monday, July 6, 11:59PM") (sub1 i))) (define office-hours diff --git a/www/exams.scrbl b/www/exams.scrbl new file mode 100644 index 00000000..02938b96 --- /dev/null +++ b/www/exams.scrbl @@ -0,0 +1,18 @@ +#lang scribble/manual +@(require "defns.rkt") +@title[#:style '(toc unnumbered)]{Exams} + +There will be three examinations, which will be @bold{take-home} +exams. Exams will be distributed at least @exam-hours hours before the due +date of the exam. + +@itemlist[ + @item{@secref["Exam_1"], due @exam1-date} + @item{@secref["Exam_2"], due @exam2-date} + @item{Exam 3, due @exam3-date} + @;item{@secref["Midterm_3"], due @exam3-date} +] + +@include-section["exams/1.scrbl"] +@include-section["exams/2.scrbl"] +@;include-section["exams/3.scrbl"] diff --git a/www/midterms/1.scrbl b/www/exams/1.scrbl similarity index 70% rename from www/midterms/1.scrbl rename to www/exams/1.scrbl index 05a7a14f..34b1529c 100644 --- a/www/midterms/1.scrbl +++ b/www/exams/1.scrbl @@ -3,33 +3,33 @@ @(require (for-label racket)) @(require "../defns.rkt") -@title{Midterm 1} +@title{Exam 1} -@(define prefix "m1-") +@(define prefix "exam1-") -@bold{Due: @m1-date 11:59PM} +@bold{Due: @exam1-date @exam-end-time} -Midterm 1 will be released at least @midterm-hours hours prior to +Exam 1 will be released at least @exam-hours hours prior to its due date. @section[#:tag-prefix prefix]{Practice} -There is a practice midterm available on ELMS. You may submit to the -Practice Midterm 1 assignment on Gradescope to get feedback on your -solution. However during the real midterm, you will not get this +There is a practice exam available on ELMS. You may submit to the +Practice Exam 1 assignment on Gradescope to get feedback on your +solution. However during the real exam, you will not get this level of feedback from the autograder. @bold{Make sure you do not -submit your practice midterm solution for the real midterm! We will +submit your practice exam solution for the real exam! We will not allow late submissions if you submit the wrong work.} @section[#:tag-prefix prefix]{Instructions} -The midterm will be released as a zip file on ELMS (see the -description of Midterm 1 there for the link). +The exam will be released as a zip file on ELMS (see the +description of Exam 1 there for the link). -There are several parts to this midterm. Each part has its own +There are several parts to this exam. Each part has its own directory with a README and supplementary files. Read the README in each part for instructions on how to complete that part of the -midterm. +exam. @section[#:tag-prefix prefix]{Communications} @@ -43,7 +43,7 @@ If you have trouble reaching the course staff via Piazza, email @tt{@prof1-email}. You may not communicate with anyone outside of the course staff about -the midterm. +the exam. @section[#:tag-prefix prefix]{Submissions} diff --git a/www/midterms/2.scrbl b/www/exams/2.scrbl similarity index 71% rename from www/midterms/2.scrbl rename to www/exams/2.scrbl index 3917ccae..2ecf4084 100644 --- a/www/midterms/2.scrbl +++ b/www/exams/2.scrbl @@ -5,33 +5,33 @@ @(require "../defns.rkt") -@title{Midterm 2} +@title{Exam 2} -@(define prefix "m2-") +@(define prefix "exam2-") -@bold{Due: @m2-date 11:59PM} +@bold{Due: @exam2-date @exam-end-time} -Midterm 2 will be released at least @midterm-hours hours prior to +Exam 2 will be released at least @exam-hours hours prior to its due date. @section[#:tag-prefix prefix]{Practice} -There is a practice midterm available on ELMS. You may submit to the -Practice Midterm 1 assignment on Gradescope to get feedback on your -solution. However during the real midterm, you will not get this +There is a practice exam available on ELMS. You may submit to the +Practice Exam 2 assignment on Gradescope to get feedback on your +solution. However during the real exam, you will not get this level of feedback from the autograder. @bold{Make sure you do not -submit your practice midterm solution for the real midterm! We will +submit your practice exam solution for the real exam! We will not allow late submissions if you submit the wrong work.} @section[#:tag-prefix prefix]{Instructions} -The midterm will be released as a zip file on ELMS (see the -description of Midterm 1 there for the link). +The exam will be released as a zip file on ELMS (see the +description of Exam 1 there for the link). -There are several parts to this midterm. Each part has its own +There are several parts to this exam. Each part has its own directory with a README and supplementary files. Read the README in each part for instructions on how to complete that part of the -midterm. +exam. @section[#:tag-prefix prefix]{Communications} @@ -45,7 +45,7 @@ If you have trouble reaching the course staff via Piazza, email @tt{@prof1-email}. You may not communicate with anyone outside of the course staff about -the midterm. +the exam. @section[#:tag-prefix prefix]{Submissions} diff --git a/www/main.scrbl b/www/main.scrbl index 751ba0ec..ea541c42 100644 --- a/www/main.scrbl +++ b/www/main.scrbl @@ -61,7 +61,7 @@ staff using this @link[feedback]{form}. @include-section{notes.scrbl} @include-section{slides.scrbl} @include-section{assignments.scrbl} -@include-section{midterms.scrbl} -@include-section{project.scrbl} +@include-section{practice.scrbl} +@include-section{exams.scrbl} @include-section{software.scrbl} @include-section{colophon.scrbl} diff --git a/www/midterms.scrbl b/www/midterms.scrbl deleted file mode 100644 index 04a07052..00000000 --- a/www/midterms.scrbl +++ /dev/null @@ -1,15 +0,0 @@ -#lang scribble/manual -@(require "defns.rkt") -@title[#:style '(toc unnumbered)]{Midterms} - -There will be two midterm examinations, which will be @bold{take-home} -exams. Exams will be distributed at least @midterm-hours hours before the due -date of the midterm. - -@itemlist[ - @item{@secref["Midterm_1"], due @m1-date} - @item{@secref["Midterm_2"], due @m2-date} -] - -@include-section["midterms/1.scrbl"] -@include-section["midterms/2.scrbl"] diff --git a/www/notes/1/ocaml-to-racket.scrbl b/www/notes/1/ocaml-to-racket.scrbl index 5a43154a..a4b097a4 100644 --- a/www/notes/1/ocaml-to-racket.scrbl +++ b/www/notes/1/ocaml-to-racket.scrbl @@ -37,12 +37,51 @@ @table-of-contents[] +When implementing a compiler, we have three choices to make before +getting started: + +@itemlist[ + +@item{what is the language of programs that the compiler will compile? +This is called the @bold{source language}.} + +@item{what is the language of programs that the compiler will +translate source programs into? This is called the @bold{target +language}.} + +@item{what is the language that the compiler will be written in? This +is called the @bold{host language} or @bold{implementation language}.} +] + +We're going to be building a compiler for a modern, high-level, memory +safe programming language with facilities for basic primitive types +like integers and booleans, inductive types like lists and trees, +array types like vectors, pattern matching, and first-class functions. +Rather than design this language from scratch, we're going to choose +an existing language that has these features and implement a compiler +for (a subset of) it. Our source language is Racket. + +When writing a compiler, it is often useful to take advantage of the +mechanisms provided by modern, high-level, memory safe programming +languages. For example, we need facilities for basic primitive types +like integers and booleans, inductive types like lists and trees, +pattern matching, etc. So we will choose an existing language that +has these features suitable for writing compilers. Our host language +is Racket. + +We'll discuss our chosen target language in a bit, but already we have +two reasons to learn Racket: we will need to know enough Racket to +write a compiler and we will need to know the parts of Racket we plan +to compile so that we build a correct compiler. + +Let's know look at Racket, starting from a familiar and close +relative: OCaml. + @section{Basic values} -Let's start by looking at something you know: OCaml. In OCaml, -expressions can include literals for numbers, strings, booleans. Here -we are using the OCaml read-eval-print-loop (REPL) to type in examples -and evaluate their results: +In OCaml, expressions can include literals for numbers, strings, +booleans. Here we are using the OCaml read-eval-print-loop (REPL) to +type in examples and evaluate their results: @ocaml-repl{ # 8;; diff --git a/www/notes/a86.scrbl b/www/notes/a86.scrbl index 028cf8d5..b464dd43 100644 --- a/www/notes/a86.scrbl +++ b/www/notes/a86.scrbl @@ -80,6 +80,35 @@ HERE @table-of-contents[] +As previously discussed, when implementing a compiler, we have three +choice to make before getting started: source language, target +language, and host language. We've settled on Racket as both the +source and host language. + +When choosing a target language we are essentially deciding on the +underlying computational model of our language. In order to run +compiled programs, the machine must provide that computational model. +So there's a tradeoff here. If we target a high-level language, our +lives as compiler writers is easy and we can delegate much of the work +to the target language's facilities. But then to run our programs, we +require the machine support that target language. If we target a +low-level language, our lives as compiler writers is more difficult +because we have to do the work of expressing the high-level +abstractions the source language provides using only the low-level +abstractions the target language provides. But in doing so, we reduce +the machine requirements, meaning we can run programs on a larger set +of machines. + +In this course, we are going to target a very low-level language: x86 +assembly. This means that our compiled programs can run on any +computer that can run (or emulate) x86 instructions, which is a very +large set. It also means we will have to build our of our high-level +language features out the primordial machinery of assembly code. + +In this chapter we cover a86, a library for representing and running +x86 programs within Racket. We will use a86 as the target language of +our compilers. + @section[#:tag "a86-Overview"]{Overview} x86 is an instruction set architecture (ISA), which is a @@ -129,10 +158,9 @@ This chapter describes the a86 language at a high-level. See Before describing a86, let's take a brief look at x86. There are a few different human-readable formats for writing -x86 assembly code, but we'll be using the one supported by -@link["https://www.nasm.us/"]{the Netwide Assembler} (NASM). +x86 assembly code, but we'll be using "Intel syntax." -Here's an example x86 program, written using nasm syntax. +Here's an example x86 program, written using Intel syntax. The program has one global label called @tt{entry}, which will be the main entry point for the program. This program computes the 36th triangular number, which will reside in @@ -142,11 +170,7 @@ register @tt{rax} when the code returns. Linux systems. On MacOS, you need to prefix all label names with an underscore, while on Linux you do not. So on a Mac, you would use the names @tt{_entry}, @tt{_tri}, and @tt{_done}, while on Linux you would -use @tt{entry}, @tt{tri}, and @tt{done}. - -Alternatively, you can impose the underscore naming convention by -passing @tt{--gprefix _} to @tt{nasm}; this way you avoid having to -write the underscores within the file.} +use @tt{entry}, @tt{tri}, and @tt{done}.} @filebox-include[fancy-nasm a86 "tri.s"] @@ -244,22 +268,32 @@ x86 code, which should not need to push anything to the stack or use the @tt{call} instruction.} We can compile the @tt{tri.s} assembly program to an object -file with @tt{nasm}: +file using @tt{clang} to assemble it: + +@shellbox["clang -c tri.s"] -@margin-note{The format argument should be @tt{macho64} on macOS and - @tt{elf64} on Linux. The @tt{--gprefix _} argument should be given - on macOS in order to follow the system's naming convention - requirements.} +The @tt{-c} flag is telling @tt{clang} to compile and assemble, but +not link. Since the input file is an assembly file, there's no +compiling to do, so it simply assembles the object file, which by +default is named by replacing the suffix of the input file with +@tt{.o}, so this command created @tt{tri.o}. -@shellbox[ - (format "nasm -f ~a -o tri.o tri.s" - (if (eq? 'macosx (system-type 'os)) - "macho64 --gprefix _" - "elf64"))] +We can use the @tt{nm} command to inspect the object file. -To run the object file, we will need to link with a small C program -that can call the @tt{entry} label of our assembly code and then -print the result: +@shellbox["nm tri.o"] + +The @tt{nm} command prints out the symbol table of the object file. +You'll notice that there are three symbols defined, each corresponding +to the labels we used in the source assembly program. The @tt{t} and +@tt{T} symbol codes indicate local and global visibility of the +symbols, respectively, and the numbers indicate the byte offset of +where the code for each label is found relative to the start of the +code section of the object file. + +The object file itself is not executable, but we can link it with a +small support file to build an executable program. The support +program will define the main entry point for the program and call our +@tt{entry} function, then print the result. @filebox-include[fancy-c a86 "main.c"] @@ -279,24 +313,32 @@ is part of a larger set of conventions known as the @bold{ Application Binary Interface}. For a reference, see the @secref{Texts} section of the notes. +We can assemble an object file using @tt{clang}. -We can compile the @tt{main.c} C program to an object file with @tt{gcc}: +@shellbox["clang -c main.c"] -@shellbox[ - "gcc -c main.c -o main.o" - ] +Here @tt{clang} is compiling the C code into assembly, then +assemblying (but not linking) to produce @tt{main.o}. -Now we can make an executable by linking the two together: +It's worth inspecting the symbol table for this program. -@shellbox[ - "gcc main.o tri.o -o tri" -] +@shellbox["nm main.o"] + +Notice that it defines a global symbol @tt{main} but it references +undefined symbols @tt{printf} and @tt{entry}; the @tt{U} symbol code +means undefined. The idea is that we can link these two objects +together, along with the standard C library for @tt{printf}, in order +to get a complete executable that has no undefined symbols. + +@shellbox["clang main.o tri.o -o tri"] + +This will link together the object files into an executable. By +default that executable's name will be @tt{a.out}, but here we're +choosing the name @tt{tri}. Finally, we can run the executable. + +@shellbox["./tri"] -Finally, we can run the executable: -@shellbox[ - "./tri" -] There, of course, is a lot more to x86-64 than what's been shown here. If you want to dig deeper, check the references @@ -307,7 +349,7 @@ in @secref{Texts}. But now let's turn to a86. Here we will employ one of the great ideas in computer science: we will represent programs as data. Rather than toil away at the level of x86, writing programs directly in -nasm syntax, compiling, and running them, we will instead +Intel syntax, compiling, and running them, we will instead design a data type definition for representing x86 programs and @emph{compute} programs. @@ -405,7 +447,7 @@ It's also easy to go from our data representation to its interpretation as an x86 program. There is a function provided for printing an a86 program as an x86 -program using nasm notation, called @racket[asm-display]. Calling +program using Intel notation, called @racket[asm-display]. Calling this function prints to the current output port, but it's also possible to write the output to a file or convert it to a string. @@ -428,7 +470,7 @@ us, which what the implementors of the a86 library have done: The @racket[asm-interp] function consumes an @tt{a86} program as input and produces the integer result the program computes, i.e. it is an @bold{Interpreter} for a86. Behind -the scenes it does this by converting to nasm, assemblying, +the scenes it does this by assemblying, compiling a thin C wrapper, executing the program, and reading the result. This will be a rather handy tool both in interactively exploring the a86 language (you can write diff --git a/www/notes/abscond.scrbl b/www/notes/abscond.scrbl index 83c307fe..281402a2 100644 --- a/www/notes/abscond.scrbl +++ b/www/notes/abscond.scrbl @@ -1,6 +1,6 @@ #lang scribble/manual -@(require (for-label (except-in racket compile) a86/ast a86/printer a86/registers)) +@(require (for-label (except-in racket compile) a86/ast a86/printer a86/registers a86/interp)) @(require scribble/examples redex/reduction-semantics redex/pict @@ -13,8 +13,7 @@ @(define codeblock-include (make-codeblock-include #'here)) -@(ev '(require rackunit a86 abscond abscond/correct abscond/compiler/compile abscond/executor/run)) -@(ev '(define (exec-expr e) (run (compile e)))) +@(ev '(require rackunit a86 abscond abscond/correct abscond/compiler/compile)) @(define (shellbox . s) (parameterize ([current-directory (build-path langs "abscond")]) @@ -29,9 +28,6 @@ (begin (apply shell (syntax->datum #'(s ...))) #'(void)))])) -@;{ Have to compile 42.s (at expand time) before listing it } -@(shell-expand "cat 42.rkt | racket -t compile-stdin.rkt -m > 42.s") - @(define this-lang "Abscond") @(define prefix (string-append this-lang "-")) @@ -213,7 +209,7 @@ well-formed Abscond program, then it runs the intepreter and displays the result. For example, interpreting the program @tt{42.rkt} shown above: -@shellbox["cat 42.rkt | racket -t interp-stdin.rkt -m"] +@shellbox["cat 42.rkt | racket -t interpreter/interp-stdin.rkt -m"] @;{ Even though the semantics is obvious, we can provide a formal @@ -269,6 +265,7 @@ operational semantics and an interpreter, which is (obviously) correct. Now let's write a compiler. } +@;{ @section[#:tag-prefix prefix]{Toward a Compiler for Abscond} A compiler, like an interpreter, is an implementation of a programming @@ -287,6 +284,11 @@ So in general, the relationship between an interpreter and compiler is (source-interp e) = (target-interp (source-compile e)) } +We can even turn this specification into a property that we can check: + +HERE + + We can in principle choose any target language we'd like. For this class, we will choose the @bold{x86-64} instruction set architecture. @@ -409,162 +411,113 @@ This creates the file @tt{42.run}, an exectuable program: We now have a working example. The remaining work will be to design a compiler that takes an Abscond program and emits a file like @tt{42.s}, but with the appropriate integer literal. +} @section[#:tag-prefix prefix]{A Compiler for Abscond} -We will now write a compiler for Abscond. To heart of the compiler -will be a function with the following signature: -@#reader scribble/comment-reader -(racketblock -;; Expr -> Asm -(define (compile e) ...) -) +Writing a compiler is essentially a problem of translation. We want +to translate programs in the source language into programs in the +target language. For the compiler to be correct, we want this translation +to preserve the original meaning of the source language. -Where @tt{Asm} is a data type for representing assembly programs, -i.e. it will be the AST of x86-64 assembly. +This provides an alternative implementation of the language compared +to the interpreter we wrote. Rather than interpret programs, we +compile them into another language and then use the interpreter of +that language to run the program. -So the AST representation of our example is: +For us, that target language is a86. To run target programs, we +simply have the CPU execute the code. (In other words, the +interpreter of our target language is implemented in hardware.) As a +convenience, we can use @racket[asm-interp] to carry out this +execution from within Racket. This will be very useful for stating +the specification of our compiler's correctness and making examples. -@racketblock[ -(list (Label 'entry) - (Mov rax 42) - (Ret)) -] +Let's say the Abscond program we have is @racket[42]. What would +be an equivalent a86 program that, when run, would produce +@racket[42]? Well every a86 program needs to have a globally declared +label where execution will start when called. The result of running +the program is communicated as whatever is in the @racket[rax] +register when the program returns, using the @racket[Ret] instruction. -Writing the @racket[compile] function is easy: +So, a possible translation of the Abscond program @racket[42] is the +a86 program: -@codeblock-include["abscond/compiler/compile.rkt"] - -@#reader scribble/comment-reader -(examples #:eval ev -(compile (Lit 42)) -(compile (Lit 38)) -) - -To convert back to the concrete NASM syntax, we use -@racket[asm-display]. - -@margin-note{Note: the printer takes care of the macOS vs Linux label -convention by detecting the underlying system and printing -appropriately.} - -@#reader scribble/comment-reader -(examples #:eval ev -(asm-display (compile (Lit 42)))) - -Putting it all together, we can write a command line compiler much -like the command line interpreter before, except now we emit assembly -code: - -@codeblock-include["abscond/compiler/compile-stdin.rkt"] - -Example: - -@shellbox["cat 42.rkt | racket -t compile-stdin.rkt -m"] - -Using a Makefile, we can capture the whole compilation dependencies as: +@racketblock[ +(prog + (Global 'entry) + (Label 'entry) + (Mov rax 42) + (Ret)) +] -@margin-note{Note: the appropriate object file format is detected -based on the operating system.} +To see that it is equivalent, i.e. that it produces @racket[42], we +just have to run it: -@filebox-include[fancy-make abscond "Makefile"] +@ex[ +(asm-interp + (prog + (Global 'entry) + (Label 'entry) + (Mov rax 42) + (Ret)))] -And now compiling Abscond programs is easy-peasy: -@shellbox["make 42.run" "./42.run"] +From this example of a single compilation, it's pretty easy to write a +general compiler: -It's worth taking stock of what we have at this point, compared to the -interpreter approach. To run the interpreter requires all of Racket -in the run-time system. +@codeblock-include["abscond/compiler/compile.rkt"] -When running a program using the interpreter, we have to parse the -Abscond program, check the syntax of the program (making sure it's an -integer), then run the interpreter and print the result. +If we compile @racket[(Lit 42)] we get exactly the code we wrote by +hand: -When running a program using the compiler, we still have to parse the -Abscond program and check its syntax, but this work happens @emph{at -compile-time}. When we @emph{run} the program this work will have -already been done. While the compiler needs Racket to run, at -run-time, Racket does not need to be available. All the run-time -needs is our (very tiny) object file compiled from C. Racket doesn't -run at all -- we could delete it from our computer or ship the -executable to any compatible x86-64 machine and run it there. This -adds up to much more efficient programs. Just to demonstrate, here's -a single data point measuring the difference between interpreting and -compiling Abscond programs: +@ex[ +(compile (Lit 42))] -@shellbox["cat 42.rkt | time -p racket -t interp-stdin.rkt -m"] +And we can now compile any Abscond program: -Compiling: +@ex[ +(compile (Lit 0)) +(compile (Lit 99))] -@shellbox["time -p ./42.run"] +If we compose the compiler with the parser, we can write examples +using the symbolic concrete syntax: -Because Abscond is a subset of Racket, we can even compare results -against interpreting the program directly in Racket: +@ex[ +(compile (parse '42)) +(compile (parse '0)) +(compile (parse '99))] -@shellbox["touch 42.rkt # forces interpreter to be used" - "time -p racket 42.rkt"] +And by using @racket[asm-interp], we can confirm that these compiled +programs mean the same thing as the original semantics according to +@racket[interp]: -Moreover, we can compare our compiled code to code compiled by Racket: +@ex[ +(asm-interp (compile (parse '42))) +(asm-interp (compile (parse '99)))] -@shellbox["raco make 42.rkt" - "time -p racket 42.rkt"] @section[#:tag-prefix prefix]{But is it @emph{Correct}?} At this point, we have a compiler for Abscond. But is it correct? -What does that even mean, to be correct? - -First, let's formulate an alternative implementation of -@racket[interp] that composes our compiler and a86 interpreter to define -a lower-level execution wrapper for assembled programs: - -@codeblock-include["abscond/executor/exec.rkt"] - -For source expressions, we can recover the old behavior by composing -@racket[compile] with @racket[run]: - -@ex[ -(exec-expr (Lit 42)) -(exec-expr (Lit 19))] +What does that even mean, to be correct? Since we have specified the +meaning of Abscond with an interpreter and we can compose the +compilation of Abscond programs with the running of a86 programs, we +can state correctness as follows: -It captures the idea of a phase-distinction in that you can first -compile a program into a program in another language---in this case -a86---and can then interpret @emph{that} program to get the result. -If the compiler is correct, the result should be the same: @bold{Compiler Correctness}: @emph{For all @racket[e] @math{∈} @tt{Expr} and @racket[i] @math{∈} @tt{Integer}, if @racket[(interp e)] -equals @racket[i], then @racket[(exec-expr e)] equals +equals @racket[i], then @racket[(asm-interp (compile e))] equals @racket[i].} One thing that is nice about specifying our language with an interpreter is that we can run it. So we can @emph{test} the compiler against the interpreter. If the compiler and interpreter agree on all -possible inputs, then the compiler is correct. - - -This is actually a handy tool to have for experimenting with -compilation within Racket: - - -@ex[ -(exec-expr (Lit 42)) -(exec-expr (Lit 37)) -(exec-expr (Lit -8))] - -This of course agrees with what we will get from the interpreter: - -@ex[ -(interp (Lit 42)) -(interp (Lit 37)) -(interp (Lit -8))] - -We can turn this in a @bold{property-based test}, i.e. a function that -computes a test expressing a single instance of our compiler -correctness claim: +possible inputs, then the compiler is correct. We can turn this in a +@bold{property-based test}, i.e. a function that computes a test +expressing a single instance of our compiler correctness claim: @codeblock-include["abscond/correct.rkt"] @@ -606,4 +559,122 @@ correctness. It's tempting to declare victory. But... can you think of a valid input (i.e. some integer) that might refute the correctness claim? -Think on it. In the meantime, let's move on. + +@section[#:tag-prefix prefix]{Stand-alone execution} + + +From a conceptual point of view, we have covered the major elements of +the Abscond compiler. We can translate programs into a86 and then +execute them using @racket[asm-interp]. But from a pragmatic view, +this approach requires Racket to be available at run-time in order to run +@racket[asm-interp]. A more useful set-up here would be to use Racket +at compile-time to generate a86 code, but then to produce a +stand-alone executable that simply executes the code produced by the +compiler. In this way we don't need Racket around at all at run-time +and we can more clearly see the phase distinction betwee compile- and +run-time. + +In order to directly run the assembly code produced by compiler +@emph{without} @racket[asm-interp], we will need a couple of things. + +@itemlist[ +@item{We need to be able to assemble a86 code into object code.} + +@item{We need to be able to link this object code with code that +fills in for what @racket[asm-interp] was doing for us, namely: +calling the compiled code and displaying the result when it returns.}] + + +To handle the first issue we can rely on the +@racket[asm-display] function, which displays a86 code using +standard notation for x86: + +@#reader scribble/comment-reader +(examples #:eval ev +(asm-display (compile (Lit 42)))) + +@margin-note{Note: the printer takes care of the macOS vs Linux label +convention by detecting the underlying system and printing +appropriately.} + +We can turn this into a command-line utility that reads an Abscond +program and prints out assembly code: + +@codeblock-include["abscond/compiler/compile-stdin.rkt"] + +@shellbox["cat 42.rkt | racket -t compiler/compile-stdin.rkt -m"] + +If we save this output to a file, we can then assemble it into an +object file: + +@shellbox["cat 42.rkt | racket -t compiler/compile-stdin.rkt -m > 42.s" + "clang -c 42.s" + "nm 42.o"] + +You can see that in @tt{42.o} we have an object file that defines a +@tt{entry} label. + +To handle the second issue, we can write a small C helper program that +will fulfill the role of what Racket's @racket[asm-interp] was doing +for us. + +@filebox-include[fancy-c abscond "runtime/main.c"] + +Now we can use an existing C compiler to compile this into object code. + +@shellbox["clang -c runtime/main.c" + "nm main.o"] + +Here you can see we get an object with a @tt{main} label. + +Finally to produce an executable we just use an existing linker to +link the two objects together into an executable. + +@shellbox["clang main.o 42.o" + "./a.out"] + +Running @tt{a.out} executes the code, first invoking the @tt{main} +procedure originally written in C, which calls @tt{entry}, invoking +the compiled code. When the compiled code returns, the result is +printed. + + + +It's worth taking stock of what we have at this point, compared to the +interpreter approach. To run the interpreter requires all of Racket +in the run-time system. + +When running a program using the interpreter, we have to parse the +Abscond program, check the syntax of the program (making sure it's an +integer), then run the interpreter and print the result. + +When running a program using the compiler, we still have to parse the +Abscond program and check its syntax, but this work happens @emph{at +compile-time}. When we @emph{run} the program this work will have +already been done. While the compiler needs Racket to run, at +run-time, Racket does not need to be available. All the run-time +needs is our (very tiny) object file compiled from C. Racket doesn't +run at all -- we could delete it from our computer or ship the +executable to any compatible x86-64 machine and run it there. This +adds up to much more efficient programs. Just to demonstrate, here's +a single data point measuring the difference between interpreting and +compiling Abscond programs: + +@shellbox["cat 42.rkt | time -p racket -t interpreter/interp-stdin.rkt -m"] + +Compiling: + +@shellbox["time -p ./a.out"] + +Because Abscond is a subset of Racket, we can even compare results +against interpreting the program directly in Racket: + +@shellbox["touch 42.rkt # forces interpreter to be used" + "time -p racket 42.rkt"] + +Moreover, we can compare our compiled code to code compiled by Racket: + +@shellbox["raco make 42.rkt" + "time -p racket 42.rkt"] + + diff --git a/www/notes/blackmail.scrbl b/www/notes/blackmail.scrbl index b19366d1..3cd2ad21 100644 --- a/www/notes/blackmail.scrbl +++ b/www/notes/blackmail.scrbl @@ -12,10 +12,10 @@ @(define codeblock-include (make-codeblock-include #'here)) @(ev '(require rackunit a86)) -@(ev '(require blackmail/compiler/compile blackmail/executor/run)) +@(ev '(require blackmail/compiler/compile)) @(for-each (λ (f) (ev `(require (file ,(path->string (build-path langs "blackmail" f)))))) '("main.rkt" "syntax/random.rkt" "correct.rkt")) -@(ev '(define (exec e) (run (compile e)))) + @(define (shellbox . s) (parameterize ([current-directory (build-path langs "blackmail")]) @@ -309,31 +309,16 @@ what they each produce: (asm-interp (compile (parse '(sub1 8)))) (asm-interp (compile (parse '(add1 (add1 (sub1 (add1 -8)))))))] -Based on this, it's useful to define an @racket[exec] function that -(should) behave like @racket[interp], just as we did for Abscond: - -@codeblock-include["blackmail/executor/exec.rkt"] - -@ex[ -(exec (parse '(add1 (add1 40)))) -(exec (parse '(sub1 8))) -(exec (parse '(add1 (add1 (sub1 (add1 -8))))))] - -This function will be the basis of our compiler correctness statement -and a primary tool for testing the compiler. -And give a command line wrapper for parsing, checking, and compiling +We can write a command line wrapper for parsing, checking, and compiling in @link["code/blackmail/compile-stdin.rkt"]{@tt{compile-stdin.rkt}}, we can compile files as follows: -@shellbox["cat add1-add1-40.rkt | racket -t compile-stdin.rkt -m"] +@shellbox["cat add1-add1-40.rkt | racket -t compiler/compile-stdin.rkt -m"] -And using the same @link["code/blackmail/Makefile"]{@tt{Makefile}} -setup as in Abscond, we capture the whole compilation process with a -single command: +And another for parsing, checking, compiling and executing: -@void[(shellbox "touch add1-add1-40.rkt")] -@shellbox["make add1-add1-40.run" "./add1-add1-40.run"] +@shellbox["cat add1-add1-40.rkt | racket -t executor/run-stdin.rkt -m"] @section{Correctness and random testing} @@ -342,12 +327,12 @@ We can state correctness similarly to how it was stated for Abscond: @bold{Compiler Correctness}: @emph{For all @racket[e] @math{∈} @tt{Expr} and @racket[i] @math{∈} @tt{Integer}, if @racket[(interp e)] -equals @racket[i], then @racket[(exec e)] equals +equals @racket[i], then @racket[(asm-interp (compile e))] equals @racket[i].} (This statement is actually identical to the statement of correctness for Abscond, however, it should be noted that the meaning of -@tt{Expr}, @racket[interp], @racket[exec] refer to their Blackmail +@tt{Expr}, @racket[interp], @racket[compile] refer to their Blackmail definitions.) And we can test this claim by comparing the results of running @@ -402,10 +387,10 @@ x86 does. Let's see: @ex[ (define max-int (sub1 (expt 2 63))) (define min-int (- (expt 2 63))) -(exec (Lit max-int)) -(exec (Prim1 'add1 (Lit max-int))) -(exec (Lit min-int)) -(exec (Prim1 'sub1 (Lit min-int)))] +(asm-interp (compile (Lit max-int))) +(asm-interp (compile (Prim1 'add1 (Lit max-int)))) +(asm-interp (compile (Lit min-int))) +(asm-interp (compile (Prim1 'sub1 (Lit min-int))))] Now there's a fact you didn't learn in grade school: in the first example, adding 1 to a number made it smaller; in the @@ -438,14 +423,14 @@ literals: But the compiler will produce the wrong result: @ex[ -(exec (Lit (add1 max-int)))] +(asm-interp (compile (Lit (add1 max-int))))] It's also possible to exceed the bounds so thoroughly, that the program can't even be compiled: @ex[ (interp (Lit (expt 2 64))) -(eval:error (exec (Lit (expt 2 64))))] +(eval:error (asm-interp (compile (Lit (expt 2 64)))))] The issue here being that a @racket[Mov] instruction can only take an argument that can be represented in 64-bits. diff --git a/www/notes/con.scrbl b/www/notes/con.scrbl index 8854b467..5940b504 100644 --- a/www/notes/con.scrbl +++ b/www/notes/con.scrbl @@ -17,10 +17,9 @@ @(ev '(require rackunit a86)) -@(ev '(require con/compiler/compile con/executor/run)) +@(ev '(require con/compiler/compile)) @(for-each (λ (f) (ev `(require (file ,(path->string (build-path langs "con" f)))))) '("main.rkt" "syntax/random.rkt" "correct.rkt")) -@(ev '(define (exec e) (run (compile e)))) @title[#:tag "Con"]{Con: branching with conditionals} @@ -205,59 +204,159 @@ execution, we will need to jump to different parts of the code to either execute the code for @racket[2] or @racket[3]. There are several ways we could accomplish this, but we take the following approach: immediately after the comparison, do a conditional jump to -the code for the then branch when zero. Should the jump not occur, -the next instructions will carry out the evaluation of the else -branch, then (unconditionally) jump over the then branch code. +the code for the else branch when not zero, otherwise fall through to +the code for the then branch. At the end of the then branch, +unconditionally jump to the end of the code. + +To accomplish this, we will need two labels: one for the else +branch code and one for the end of the code. -To accomplish this, we will need two new labels: one for the then -branch code and one for the end of the then branch code. The -@racket[gensym] function can be used to generate symbols that have not -appeared before. @margin-note{Q: Why should we generate label names -here? What would go wrong if simply used labels like @racket['l0] and -@racket['l1]?} In total, the code for this example would look like: @racketblock[ -(let ((l0 (gensym)) - (l1 (gensym))) - (list (Mov rax 8) +(list (Mov rax 8) + (Cmp rax 0) + (Jne 'else) + (Mov rax 2) + (Jmp 'end) + (Label 'else) + (Mov rax 3) + (Label 'end)) +] + +Since @racket[(if (zero? 8) 2 3)] evaluates to @racket[3], we expect +that this code will leave @racket[3] in the @racket[rax] after +executing. Step through the code in your mind and try to confirm +this. After that, you can run the code using @racket[asm-interp] +to confirm mechanically. + +@ex[ +(asm-interp + (prog (Global 'entry) + (Label 'entry) + (Mov rax 8) (Cmp rax 0) - (Je l0) + (Jne 'else) + (Mov rax 2) + (Jmp 'end) + (Label 'else) (Mov rax 3) - (Jmp l1) - (Label l0) + (Label 'end) + (Ret)))] + +This does indeed seem to do the right thing. If changed the example +to @racket[(if (zero? 0) 2 3)] we would expect to get @racket[2]. We +can revise the code by changing what we initially put in @racket[rax] +and confirm: + +@#reader scribble/comment-reader +(ex +(asm-interp + (prog (Global 'entry) + (Label 'entry) + (Mov rax 0) ; changed from 8 to 0 + (Cmp rax 0) + (Jne 'else) (Mov rax 2) - (Label l1))) + (Jmp 'end) + (Label 'else) + (Mov rax 3) + (Label 'end) + (Ret))) +) + +From these two concrete examples, it would seem we're well on our way +to having a compiler. The way to compile @racket[(if (zero? _e0) _e1 +_e2)] is: + +@racketblock[ +(seq (compile-e _e0) + (Cmp rax 0) + (Jne 'else) + (compile-e _e1) + (Jmp 'end) + (Label 'else) + (compile-e _e2) + (Label 'end)) ] +But there is a lurking problem here. See if you can spot it. + +Although our sketch works for the simple examples we used, a more +compilicated example can demonstrate this compiler is incorrect. In +fact, we can write examples that will cause the compiler to error +instead of producing code, which definitely violates our specification +of correctness. + +The issue is that the compiler needs to be correct for all possible +subexpressions @racket[_e0], @racket[_e1], and @racket[_e2]. While it +works for things like @racket[8], @racket[2], and @racket[3], it will +not work for certain kinds of subexpressions, namely subexpressions +that themselves include conditional expressions. To see write out +what you expect @racket[(if (zero? 8) (if (zero? 0) 2 3) 3)] should +compile to. What do you notice? + @section{A Compiler for Con} -Notice that the @racket[(Mov rax 8)], @racket[(Mov rax 3)] and -@racket[(Mov rax 2)] parts are just the instructions generated by -compiling @racket[8], @racket[2] and @racket[3]. Generalizing from -this, we arrive at the following code for the compiler: +The problem with our sketch for compiling conditional expressions is +the choice of label names. We used @racket['else] and @racket['end] +as labels that get defined in the output of compiling a conditional. +This works fine so long as there's only one conditional expression, +but the moment there is more than one, we have label confusion: there +will be multiple ambiguous definitions of @racket['else] and +@racket['end]. The assembly will reject such programs and +@racket[prog] will error. This is a real bug because clearly nested +conditionals are valid Con expressions and our interpreter handles +them just fine, so our compiler is obligated to produce correct code. +In this case, the compiler doesn't even produce code, much less +correct code. + +The fix is to generate new label names ever time we compile a +conditional. These fresh names will avoid clashes. In Racket, the +@racket[gensym] function can be used to generate symbols that have not +appeared before, which is exactly the property we need here. What +symbol will Racket choose? We can try things out in the REPL to see. + +@ex[ +(gensym) +(gensym) +(gensym) +] + +You can see that Racket is just giving us arbitrary symbol names, but +they have the important property that are unique. + +Although not technically necessary it might be nice to make the +symbols more readable by telling @racket[gensym] a prefix to use: + +@ex[ +(gensym 'ifz) +(gensym 'ifz)] + +This way we can leave hints in the assembly code about where the +labels came from. + +Now for our compiler, we can do the following: @racketblock[ -(let ((l0 (gensym 'if)) - (l1 (gensym 'if))) - (seq (compile-e e1) +(let ((l1 (gensym 'ifz)) + (l2 (gensym) 'ifz)) + (seq (compile-e _e0) (Cmp rax 0) - (Je l0) - (compile-e e3) - (Jmp l1) - (Label l0) - (compile-e e2) - (Label l1))) + (Jne l1) + (compile-e _e1) + (Jmp l2) + (Label l1) + (compile-e _e2) + (Label l2))) ] +This will generate a new symbol and bind it to the @emph{variable} +@racket[l1] and similiarly for @racket[l2]. -This will require extending our use of a86 instructions; in -particular, we add @racket[Jmp], @racket[Je], and @racket[Cmp] -instructions. - -The complete compiler code is: +We're now ready to write the complete compiler for Con. @codeblock-include["con/compiler/compile.rkt"] @@ -274,10 +373,10 @@ Let's take a look at a few examples: And confirm they are running as expected: @ex[ -(exec (parse '(if (zero? 8) 2 3))) -(exec (parse '(if (zero? 0) 1 2))) -(exec (parse '(if (zero? 0) (if (zero? 0) 8 9) 2))) -(exec (parse '(if (zero? (if (zero? 2) 1 0)) 4 5))) +(asm-interp (compile (parse '(if (zero? 8) 2 3)))) +(asm-interp (compile (parse '(if (zero? 0) 1 2)))) +(asm-interp (compile (parse '(if (zero? 0) (if (zero? 0) 8 9) 2)))) +(asm-interp (compile (parse '(if (zero? (if (zero? 2) 1 0)) 4 5)))) ] @@ -286,7 +385,7 @@ And confirm they are running as expected: The statement of correctness follows the same outline as before: @bold{Compiler Correctness}: @emph{For all @racket[e] @math{∈} @tt{Expr}, -@racket[(interp e)] equals @racket[(exec e)].} +@racket[(interp e)] equals @racket[(asm-interp (compile e))].} Again, we formulate correctness as a property that can be tested: diff --git a/www/notes/dodger.scrbl b/www/notes/dodger.scrbl index 0778bd92..d4aebeb5 100644 --- a/www/notes/dodger.scrbl +++ b/www/notes/dodger.scrbl @@ -9,6 +9,10 @@ "ev.rkt" "../utils.rkt") +@(define (shellbox . s) + (parameterize ([current-directory (build-path langs "dodger")]) + (filebox (emph "shell") + (fancyverbatim "fish" (apply shell s))))) @(define codeblock-include (make-codeblock-include #'h)) @@ -215,4 +219,15 @@ the case of printing characters: @filebox-include[fancy-c dodger "runtime/print.c"] -@;{FIXME: examples should be creating executable at the command-line, not exec.} +We can now make stand-alone executable examples: + +@shellbox["printf \"#lang racket\\n(integer->char 955)\" > lambda-char.rkt" + "cat lambda-char.rkt | racket -t compiler/compile-stdin.rkt -m > lambda-char.s" + "clang -c lambda-char.s" + "make -C runtime" + "clang lambda-char.o runtime/runtime.o -o lambda-char"] + +And we can run it: + +@shellbox["./lambda-char"] + diff --git a/www/notes/dupe.scrbl b/www/notes/dupe.scrbl index e3787123..0abc42be 100644 --- a/www/notes/dupe.scrbl +++ b/www/notes/dupe.scrbl @@ -13,6 +13,10 @@ dupe/runtime/types "../utils.rkt") +@(define (shellbox . s) + (parameterize ([current-directory (build-path langs "dupe")]) + (filebox (emph "shell") + (fancyverbatim "fish" (apply shell s))))) @(define codeblock-include (make-codeblock-include #'h)) @@ -210,13 +214,11 @@ examples given earlier: Viewed as a specification, what is this interpreter saying about programs that do nonsensical things like @racket[(add1 #f)]? -First, let's revise the statement of compiler correctness to reflect -the fact that @racket[interp] can return different kinds of values: +Consider the beginning of the correctness statement: @bold{Compiler Correctness}: @emph{For all @racket[e] @math{∈} @tt{Expr} and @racket[v] @math{∈} @tt{Value}, if @racket[(interp e)] -equals @racket[v], then @racket[(exec e)] equals -@racket[v].} +equals @racket[v], then ...} Now, the thing to notice here is that this specification only obligates the compiler to produce a result consistent with the @@ -240,7 +242,7 @@ undefined may not be a great choice. As a compiler implementor, however, it makes our life easier: we simply don't need to worry about what happens on these kinds of programs.) -This will, however complicate testing the correctness of the compiler, +This will, however, complicate testing the correctness of the compiler, which is addressed in @secref["Correctness_and_testing"]. Let's now turn to the main technical challenge introduced by the Dupe @@ -249,22 +251,22 @@ langauge. @section{Ex uno plures: Out of One, Many} Before getting in to the compiler, let's first address the issue of -representation of values in the setting of x86. +representation of values in the setting of a86. -So far we've had a single type of value: integers. Luckily, x86 has a -convenient datatype for representing integers: it has 64-bit signed -integers. (There is of course the problem that this representation is +So far we've had a single type of value: integers. Luckily, a86 has a +convenient datatype for representing integers: it has 64-bit integers. +(There is of course the problem that this representation is @bold{inadequate}; there are many (Con) integers we cannot represent as a 64-bit integer. But we put off worrying about this until later.) The problem now is how to represent integers @emph{and} booleans, which should be @bold{disjoint} sets of values. Representing these -things in the interpreter as Racket values was easy: we Racket +things in the interpreter as Racket values was easy: we used Racket booleans to represent Dupe booleans; we used Racket integers to represent Dupe integers. Since Racket booleans and integers are disjoint types, everything was easy and sensible. -Representing this things in x86 will be more complicated. x86 doesn't +Representing this things in a86 will be more complicated. a86 doesn't have a notion of ``boolean'' per se and it doesn't have a notion of ``disjoint'' datatypes. There is only one data type and that is: bits. @@ -286,7 +288,7 @@ instruction that moves ``@racket[#t]'' into @racket[rax], but the We have to move some 64-bit integer into @racket[rax], but the question is: which one? -The immediate temptation is to just pick a couple of integers, one for +The immediate temptation is to just pick a couple of integers: one for representing @racket[#t] and one for @racket[#f]. We could follow the C tradition and say @racket[#f] will be @racket[0] and @racket[#t] will be 1. So compiling @racket[#t] would emit: @@ -1042,46 +1044,6 @@ incorporate the new representation. The run-time system is essentially playing the role of @racket[bits->value]: it determines what is being represented and prints it appropriately. -@section{Updated Run-time System for Dupe} - -Any time there's a change in the representation or set of values, -there's going to be a required change in the run-time system. From -Abscond through Con, there were no such changes, but now we have to -udpate our run-time system to reflect the changes made to values in -Dupe. - -We define the bit representations in a header file corresponding to -the definitions given in @tt{types.rkt}: - -@filebox-include[fancy-c dupe "runtime/types.h"] - -It uses an idiom of ``masking'' in order to examine on -particular bits of a value. So for example if we want to -know if the returned value is an integer, we do a -bitwise-and of the result and @tt{1}. This produces a single -bit: 0 for integer and 1 for boolean. In the case of an -integer, to recover the number being represented, we need to -divide by 2, which can be done efficiently with a -right-shift of 1 bit. Likewise with a boolean, if we shift -right by 1 bit there are two possible results: -@racket[#,(value->bits #f)] for false and @racket[#,(value->bits #t)] for -true. - -We use the following interface for values in the runtime system: - -@filebox-include[fancy-c dupe "runtime/values.h"] -@filebox-include[fancy-c dupe "runtime/values.c"] - -The @tt{main} function remains largely the same although now we use -@tt{val_t} in place of @tt{int64_t}: - -@filebox-include[fancy-c dupe "runtime/main.c"] - -And finally, @tt{print_result} is updated to do a case analysis on the -type of the result and print accordingly: - -@filebox-include[fancy-c dupe "runtime/print.c"] - @section{Correctness and testing} We already established our definition of correctness: @@ -1192,3 +1154,68 @@ how well this is actually testing the compiler). (for ([i (in-range 10)]) (check-compiler (random-expr))) ] + +@section{Updated stand-alone run-time system} + +We saw in @secref{Abscond} that if want to make a stand-alone +executable, i.e. if we want to compile programs into executables that +do not depend on the host language, we need to provide a small support +library that is linked with every compiled program. This is called +the @bold{run-time system}. + +Any time there's a change in the representation or set of values, +there's going to be a required change in the run-time system. From +Abscond through Con, there were no such changes, but now we have to +udpate our run-time system to reflect the changes made to values in +Dupe. + +One of the responsibilities of the run-time system is printing the +final result of computation, so the run-time system, like +@racket[bits->value], needs to be able to determine the kind of value +the return bits represent so it can print appropriately. + +We define the bit representations in a header file corresponding to +the definitions given in @tt{types.rkt}: + +@filebox-include[fancy-c dupe "runtime/types.h"] + +It uses an idiom of ``masking'' in order to examine on +particular bits of a value. So for example if we want to +know if the returned value is an integer, we do a +bitwise-and of the result and @tt{1}. This produces a single +bit: 0 for integer and 1 for boolean. In the case of an +integer, to recover the number being represented, we need to +divide by 2, which can be done efficiently with a +right-shift of 1 bit. Likewise with a boolean, if we shift +right by 1 bit there are two possible results: +@racket[#,(value->bits #f)] for false and @racket[#,(value->bits #t)] for +true. + +We use the following interface for values in the runtime system: + +@filebox-include[fancy-c dupe "runtime/values.h"] +@filebox-include[fancy-c dupe "runtime/values.c"] + +The @tt{main} function remains largely the same although now we use +@tt{val_t} in place of @tt{int64_t}: + +@filebox-include[fancy-c dupe "runtime/main.c"] + +And finally, @tt{print_result} is updated to do a case analysis on the +type of the result and print accordingly: + +@filebox-include[fancy-c dupe "runtime/print.c"] + +We can now make an example and compile it into a standalone executable. + +@shellbox["printf \"#lang racket\\n#t\" | \\\n + racket -t compiler/compile-stdin.rkt -m > true.s" + "clang -c true.s" + "make -C runtime" + "clang true.o runtime/runtime.o -o true"] + +Here we are taking advantage of a Makefile provided to build the +runtime system. Then we link it with the object code for the Racket +program. Finally we can run the executable: + +@shellbox["./true"] diff --git a/www/notes/evildoer.scrbl b/www/notes/evildoer.scrbl index c8b12501..451562ec 100644 --- a/www/notes/evildoer.scrbl +++ b/www/notes/evildoer.scrbl @@ -1,65 +1,40 @@ #lang scribble/manual -@(require (for-label (except-in racket ... compile) a86/printer a86/ast)) +@(require (for-label (except-in racket ... compile ->) a86/printer a86/ast a86/interp ffi/unsafe)) @(require redex/pict - racket/runtime-path - scribble/examples + racket/runtime-path + scribble/examples evildoer/runtime/types - "../fancyverb.rkt" + "../fancyverb.rkt" "utils.rkt" - "ev.rkt" + "ev.rkt" "../utils.rkt") @(define (shellbox . s) (parameterize ([current-directory (build-path langs "evildoer")]) (filebox (emph "shell") - (fancyverbatim "fish" (apply shell s))))) + (fancyverbatim "fish" (apply shell s))))) @(define codeblock-include (make-codeblock-include #'h)) -@(ev '(require rackunit a86 evildoer evildoer/correct evildoer/compiler/compile-ops evildoer/compiler/compile evildoer/executor/run)) -@(ev '(define (exec e) (run (compile e)))) -@(ev '(define (exec/io e i) (run/io (compile e) i))) +@(ev '(require rackunit a86 evildoer evildoer/correct evildoer/compiler/compile-ops evildoer/compiler/compile evildoer/executor/host)) @;{This is needed for the example that uses current-objects} @(ev `(current-directory ,(path->string (build-path langs "evildoer")))) -@(require (for-syntax racket/base)) +@(require (for-syntax racket/base "utils.rkt")) @(begin-for-syntax - (require "utils.rkt") - (parameterize ([current-directory (build-path langs "evildoer")]) - (save-file "simple.c" -#< + (define gcd.c + #< - -int64_t entry(); - -int main(int argc, char** argv) { - printf("%" PRId64 "\n", entry()); - return 0; +int gcd(int64_t n1, int64_t n2) { + return (n2 == 0) ? n1 : gcd(n2, n1 % n2); } HERE -) - (save-file "life.c" -#< - -int64_t meaning(void) { - return 42; -} -HERE -) - (save-file "double.c" -#< - -int64_t dbl(int64_t x) { - return x + x; -} -HERE -))) + ) + (parameterize ([current-directory (build-path langs "evildoer")]) + (save-file "gcd.c" gcd.c))) @title[#:tag "Evildoer"]{Evildoer: change the world a couple nibbles at a time} @@ -162,7 +137,7 @@ The s-expression parser is defined as follows: (parse '(write-byte 97)) (parse '(eof-object? eof)) (parse '(begin (write-byte 97) - (write-byte 98)))] + (write-byte 98)))] @section{Reading and writing bytes in Racket} @@ -300,8 +275,8 @@ can then use to assert the expected behavior: (with-input-from-string "hello" (λ () (with-output-to-string - (λ () - (write-byte (read-byte)))))) + (λ () + (write-byte (read-byte)))))) "h")] @@ -335,7 +310,7 @@ interpreting programs with strings representing stdin and stdout: @codeblock-include["evildoer/interpreter/interp-io.rkt"] @ex[ - (interp/io (parse '(write-byte 104)) "") + (interp/io (parse '(write-byte 104)) "") (interp/io (parse '(write-byte (read-byte))) "hello") ] @@ -345,7 +320,7 @@ computation into a pure one: @ex[ (check-equal? (interp/io (parse '(write-byte (read-byte))) "hello") - (cons (void) "h")) + (cons (void) "h")) ] @;{ @@ -395,48 +370,36 @@ We have a couple of options for how to approach these primitives: @item{generate assembly code for issuing operating system calls to do I/O operations, or} -@item{add C code for I/O primitives in the run-time and generate -assembly code for calling them.} +@item{assume some code in the stand-alone runtime system or the host +system will provide the functionality to I/O and have the compiler +emit calls to those external functions.} ] The first option will require looking up details for system calls on the particular operating system in use, generating code to make those calls, and adding logic to check for errors. For the second option, -we can simply write C code that calls standard functions like -@tt{getc}, @tt{putc}, etc. and let the C compiler do the heavy lifting -of generating robust assembly code for calling into the operating -system. The compiler would then only need to generate code to call -those functions defined in the run-time system. This is the simpler -approach and the one we adopt. +we can leave that up to the runtime or host system and produce code +that calls this code. This is the simpler approach and the one we +adopt. + +In order to call code potentially written in a different language and +compiled by a different compiler, we have to conform to the System V +ABI calling convention, which is essentially an agreement on how calls +should work (how are arguments passed, how are return values returned, +etc.) that enables calls to work at the level of assembly code. Up to this point, we've seen how C code can call code written in assembly as though it were a C function. To go the other direction, -we need to explore how to make calls to functions written in C from -assembly. Let's look at that now. +we need to explore how to make calls to functions written in C (or +whatever else) from assembly. Let's look at that now. @margin-note{If you haven't already, be sure to read up on how calls work in @secref{a86}.} - - -Once you brushed up on how calls work, you'll know you can -define labels that behave like functions and call them. - - -Instead of @racket[read-byte] and friends, let's first start with -something simpler. Imagine we want a function to compute the greatest -common divisor of two numbers. We could of course write such a -function in assembly, but it's convenient to be able to write it -in a higher-level language like C: - -@filebox-include[fancy-c evildoer "runtime/gcd.c"] - -We can compile this into an object file: - -@(define format (if (eq? (system-type 'os) 'macosx) "macho64" "elf64")) - -@shellbox["gcc -c runtime/gcd.c -o gcd.o"] +Let's start of by assuming our runtime system or host is going to +provide a @tt{gcd} function for computing the greatest common divisor +of two integers given as arguments to the function. Now, how can we call @tt{gcd} from aseembly code? Just as there is a convention that a return value is communicated through @racket[rax], @@ -448,8 +411,8 @@ and BSD systems. (Windows follows a different ABI.) The convention for arguments is that the first six integer or pointer parameters are passed in the registers -@racket['rdi], @racket['rsi], @racket['rdx], @racket['rcx], -@racket['r8], @racket['r9]. Additional arguments and large +@racket[rdi], @racket[rsi], @racket[rdx], @racket[rcx], +@racket[r8], @racket[r9]. Additional arguments and large arguments such as @tt{struct}s are passed on the stack. So we will pass the two arguments of @tt{gcd} in registers @@ -457,18 +420,19 @@ So we will pass the two arguments of @tt{gcd} in registers @racket[Call] instruction to call @tt{gcd}. Suppose we want to compute @tt{gcd(36,60)}: -@ex[ -(define p +@#reader scribble/comment-reader +(ex +(define example (prog (Global 'entry) (Label 'entry) - (Mov 'rdi 36) - (Mov 'rsi 60) - (Sub 'rsp 8) + (Mov 'rdi 36) ; first arg + (Mov 'rsi 60) ; second arg + (Sub 'rsp 8) ; align stack to 16-bytes (Extern 'gcd) - (Call 'gcd) - (Sal 'rax int-shift) - (Add 'rsp 8) - (Ret)))] + (Call 'gcd) ; call extern gcd + (Sal 'rax int-shift) ; convert result to a value + (Add 'rsp 8) ; restore stack + (Ret)))) A few things to notice in the above code: @@ -479,146 +443,195 @@ A few things to notice in the above code: program. We placed this declaration immediately before the @racket[Call] instruction, but it can appear anywhere in the program.} -@item{The stack pointer register is decremented by @racket[8] before -the @racket[Call] instruction and then incremented by @racket[8] after -the call returns. This is to ensure the stack is aligned to 16-bytes -for call; a requirement of the System V ABI.} +@item{The stack pointer register @racket[rsp] is decremented by +@racket[8] before the @racket[Call] instruction and then incremented +by @racket[8] after the call returns. This is to ensure the stack is +aligned to 16-bytes for call; a requirement of the System V ABI.} @item{For consistency with our run-time system, we return the result encoded as an integer value, which is accomplished by shifting the result in @racket[rax] to the left by @racket[#,int-shift].} ] -We could attempt to run this program with @racket[asm-interp], but it -will complain about @tt{gcd} being an undefined label: + +We could attempt to run this program with @racket[asm-interp], but +it will complain about @tt{gcd} being an undefined label. That makes +sense since we haven't actually said what we want @racket['gcd] to +mean. @ex[ -(eval:error (bits->value (asm-interp p)))] +(eval:error (bits->value (asm-interp example)))] -The problem is that @racket[asm-interp] doesn't know anything about -the @tt{gcd.o} file, which defines the @tt{gcd} symbol, however, -there is a mechanism for linking in object files to the assembly -interprer: -@ex[ -(current-objects '("gcd.o")) -(bits->value (asm-interp p))] +The idea is that either our standalone runtime could provide the +@racket['gcd] function, perhaps written in C, or the host language +Racket could provide the functionality. Let's look at both +approaches. + +@subsection{Calling host functions from @racket[asm-interp]} + +From within Racket it is possible to provide function definitions for +external labels when running code with @racket[asm-interp]. The way +this works is we parameterize the assembly interpeter's current set of +external labels and provide: + +@itemlist[ + +@item{the name of the label we want to link,} + +@item{a Racket function that will be called when the label is called,} + +@item{a function signatures for the Racket function that describes how +the arguments and return values are encoded and decode as Racket values.}] -We also could create an executable using the run-time system. -To do this, first, let's save the assembly code to a file: +@#reader scribble/comment-reader +(ex +(require ffi/unsafe) ; for _fun, _int64, etc. +(parameterize + ([current-externs + (list (extern 'gcd gcd (_fun _int64 _int64 -> _int64)))]) + (bits->value + (asm-interp example)))) + +Here we are declaring that the Racket @racket[gcd] function can be +called using the @racket['gcd] label following the System V ABI +calling convention. The bit arguments are automatically converted to +Racket integers when called, and the Racket integer result is convert +to bits when @racket[gcd] returns. + +@subsection{Calling functions in the stand-alone runtime system} + +Host provided functions are useful for testing and exploration, but +for a stand-alone executable, we want the runtime system to provide +that functionality. The compiler doesn't change, but instead we will +link the object code produced by the compiler with a definition of +@tt{gcd}. + +First, let's write that function in C. + +@filebox-include[fancy-c evildoer "gcd.c"] + +We can compile this into an object file: + +@shellbox["clang -c gcd.c"] + +Now let's save our example program as an assembly file: @ex[ - (with-output-to-file "p.s" + (with-output-to-file "example.s" (λ () - (asm-display p)) + (asm-display example)) #:exists 'truncate)] Now we can assemble it into an object file, link the objects together to make an executable, and then run it: -@shellbox[(string-append "nasm -f " format " p.s -o p.o") - "gcc runtime/runtime.o gcd.o p.o -o p.run" - "./p.run"] +@shellbox["clang -c example.s" + "clang -c gcd.c" + "make -C runtime" + "clang example.o gcd.o runtime/runtime.o -o example" + "./example"] So now we've seen the essence of how to call functions from assembly code, which opens up an implementation strategy for implementing features: write C code as part of the run-time system and call it from the compiled code. -@section{A Run-Time for Evildoer} -With new values comes the need to add new bit encodings. So -we add new encodings for @racket[eof] and @racket[void]: +@section{Hosted I/O primitives} -@filebox-include[fancy-c evildoer "runtime/types.h"] +We've now seen how functionality can be made available to compiled +programs, either as Racket hosted functions with @racket[asm-interp] or +as functions linked into the stand-alone run-time system. -The interface for the run-time system is extended to include -file pointers for the input and output ports: +Our approach to adding @racket[read-byte], @racket[write-byte] and +@racket[peek-byte] is going to be to assume we have functions +@tt{read_byte}, @tt{write_byte}, and @tt{peek_byte} available, either +from the host or runtime, and have the compiler generate calls to +these functions according to the System V ABI calling convention. +Both @tt{read_byte} and @tt{peek_byte} will take no arguments and +return either a byte value or the eof @emph{value}, while +@tt{write_byte} takes a byte value and returns the eof value. -@filebox-include[fancy-c evildoer "runtime/runtime.h"] +Let us first use the hosted approach, then develop the compiler, then +we can return to issue of the stand-alone runtime system. -The main entry point for the run-time sets up the input and output -pointers to point to @tt{stdin} and @tt{stdout} and is updated -to handle the proper printing of a void result: +To host these primitives, we will make the Racket @racket[read-byte], +@racket[write-byte], and @racket[peek-byte] callable from assembly +code. The only problem is that these function produce and consume +Racket values, not Evildoer values encoded as bits. We can solve this +issue by wrapping the functions to decode and encode on the way in and +out. For example: -@filebox-include[fancy-c evildoer "runtime/main.c"] +@ex[ +(define (prim-read-byte) + (value->bits (read-byte))) -But the real novelty of the Evildoer run-time is that there -will be new functions that implement @racket[read-byte], -@racket[peek-byte], and @racket[write-byte]; these will be C -functions called @racket[read_byte], @racket[peek_byte] and -@racket[write_byte]: +(define (prim-peek-byte) + (value->bits (peek-byte))) -@filebox-include[fancy-c evildoer "runtime/io.c"] +(define (prim-write-byte bs) + (value->bits (write-byte (bits->value bs)))) +] -This functionality is implemented in terms of standard C library -functions @tt{getc}, @tt{ungetc}, @tt{putc} and the run-time system's -functions for encoding and decoding values such as -@tt{val_unwrap_int}, @tt{val_wrap_void}, etc. +Now it's important to observe that these functions always return bit +encodings of values, which is exactly what our caller code will +expect. -As we'll see in the next section, the main novely of the -@emph{compiler} will be that emits code to make calls to these C -functions. +@ex[ +(with-input-from-string "hello" + (lambda () + (prim-read-byte))) +(with-input-from-string "" + (lambda () + (prim-read-byte))) +] +And if we want to call @racket[prim-write-byte], we need to provide a +bit encoding of a byte. -@;{ +@ex[ +(with-output-to-string + (lambda () + (prim-write-byte (value->bits 97)))) +] +But now we are in a good position to enrich @racket[asm-interp] with +this functionality. Let's make this part of the language +implementation. We'll define a @racket[asm-interp/host] function that +includes all of the functionality we expect for the Evildoer language. -Now we have all the tools needed to interact with libraries -written in C, and really any library object files that -adhere to the System V ABI. Perhaps the only remaining -wrinkle is how should we deal with the situation in which we -are using the registers that are needed to pass parameters -in a call? The answer is to save them on the stack and -restore them when the call returns. For example, suppose -@racket['rdi] held a value we wanted to use after the call -to @tt{dbl}. It's a bit contrived, but let's say we want to -use @racket['rdi] to hold the constant we'll add to the -result of calling @tt{dbl}. Now we need to save it before -writing the argument. All we need to do is add a push and -pop around the call: - -The wrinkle is actually a bit deeper than this too. Suppose -we are using other registers, maybe some that are not used -for parameters, but nonetheless are registers that the -function we're calling would like to use? Without knowing -the details of how the function is implemented, we could be -defensive and save @emph{everything} we're using with the -assumption the called function may clobber anything. But -here, the ABI comes into play again. There are conventions -around who is responsible for registers in calls. The called -function is responsible for maintaining the registers -@racket['rbx], @racket['rsp], @racket['rbp], @racket['r12], -@racket['r13], @racket['r14], @racket['r15]; these are -@bold{callee-saved registers}. This means we, the callers, -don't have to worry about these registers being clobbered -and don't need to save them to the stack. If the called -function wants to use these registers, it's responsible for -saving their value and restoring them before returning. On -the other hand, registers @racket['rax], @racket['rdi], -@racket['rdx], @racket['rcx], @racket['r8], @racket['r9], -@racket['r10], and @racket['r11] are @bold{caller-saved - registers}, which means the called function is free to -clobber them and if we want to preserve their value across -the call, we'll need to save and restore them. - -As a final note, keep in mind that the compiler generates -code this is both called and a caller, so it has to be -mindful of both sides of the convention. The main entry -point @tt{entry} is called from the C run-time. If the -generated code wants to use any of the callee-saved -registers, it should save them and restore them before the -return that delivers the final result of evaluation. On the -other hand, when it calls external functions implemented in -C, it is the caller and has to maintain the caller-saved -registers. - -OK, now let's use these new powers to write the compiler. +@codeblock-include["evildoer/executor/host.rkt"] -} +Now let's write an assembly program that calls some of these +functions. +@ex[ +(define read-write-byte + (prog + (Global 'entry) + (Extern 'read_byte) + (Extern 'write_byte) + (Label 'entry) + (Sub rsp 8) + (Call 'read_byte) + (Mov rdi rax) + (Call 'write_byte) + (Add rsp 8) + (Ret)))] + +This program reads a byte with @tt{read_byte} and then immediately +writes that byte with @tt{write_byte} and returns, so the return value +should be void since that's what will be in the @racket[rax] register +at the end. +@ex[ +(with-input-from-string "hello" + (lambda () + (asm-interp/host read-write-byte)))] + +Notice that this prints "h" and return @racket[#,(value->bits (void))]. @section{A Compiler for Evildoer} @@ -629,9 +642,9 @@ straightfoward and don't really involve anything new. For @racket[peek-byte], @racket[read-byte], and @racket[write-byte], we generate code that calls the -appropriate C function. In the case of @racket[write-byte], +appropriate external function. In the case of @racket[write-byte], we arrange for the byte that we'd like to write to be in -@racket['rdi] before the call. +@racket[rdi] before the call. Finally, since the emitted code is potentially issuing calls to external functions, we make sure to align the stack to @@ -650,66 +663,36 @@ The primitive operation compiler: Notice how expressions like @racket[(read-byte)] and @racket[(write-byte)] -compile to calls into the run-time system: +compile to calls into the run-time/host system: @ex[ (compile-op0 'read-byte) (compile-op1 'write-byte)] +So we can write some nice high-level examples: +@ex[ +(asm-interp/host (compile (parse '(write-byte 97))))] -@section{Testing and correctness} - - - -We can continue to interactively try out examples with -@racket[asm-interp], although there are two issues we need -to deal with. - -The first is that the @racket[asm-interp] utility doesn't know -anything about the Evildoer run-time. Hence we need to tell -@racket[asm-interp] to link it in when running an example; otherwise -labels like @tt{byte_write} will be undefined. We saw how to do this -in @secref["calling-c"] using the @racket[current-objects] parameter to -link in object files to @racket[asm-interp]. This time, the object -file we want to link in is the Evildoer run-time. - -The other is that we need to have an @racket[asm-interp/io] analog of -@racket[interp/io], i.e. we need to be able to redirect input and -output so that we can run programs in a functional way. The -@secref["a86"] library provides this functionality by providing -@racket[asm-interp/io]. The way this function works is @emph{if} -linked objects define an @tt{in} and @tt{out} symbol, it will set -these appropriately to read input from a given string and collect -output into a string. - -@racketblock[ -(current-objects '("runtime/runtime.o")) -(asm-interp/io (compile (parse '(write-byte (read-byte)))) "a")] -Notice though, that @racket[asm-interp/io] gives back a pair -consisting of the @emph{bits} and the output string. To match the -return type of @racket[interp/io] we need to convert the bits to a -value: +@section{Testing and correctness} -@racketblock[ -(match (asm-interp/io (compile (parse '(write-byte (read-byte)))) "a") - [(cons b o) (cons (bits->value b) o)])] -Using these pieces, we can write a function that matches the type signature -of @racket[interp/io]: +Let's define an @racket[exec] function that should be equivalent to +@racket[interp]. We can also define an @racket[exec/io] function +equivalent to @racket[interp/io] that's useful for testing programs +that do I/O. @codeblock-include["evildoer/executor/exec.rkt"] -@racketblock[ -(exec/io (parse '(write-byte (read-byte))) "z")] +And we can confirm that it works as expect: -Note that we still provide an @racket[exec] function, but it -assumes there is no input and it prints all output: +@ex[ +(exec (parse '(write-byte 97))) +(exec/io (parse '(write-byte 97)) "") +(exec/io (parse '(write-byte (read-byte))) "a") +(exec/io (parse '(eof-object? (read-byte))) "")] -@racketblock[ -(exec (parse '(eof-object? (read-byte)))) -(exec (parse '(write-byte 97)))] We can now state the correctness property we want of the compiler: @@ -750,4 +733,35 @@ compiler: (check-compiler (random-expr) (random-input))) (for ((i 100)) (check-compiler (random-well-defined-expr) (random-input)))] - + + +@section{Stand-alone runtime system for Evildoer} + +With new values comes the need to add new bit encodings. So +we add new encodings for @racket[eof] and @racket[void]: + +@filebox-include[fancy-c evildoer "runtime/types.h"] + +The interface for the run-time system is extended to include +file pointers for the input and output ports: + +@filebox-include[fancy-c evildoer "runtime/runtime.h"] + +The main entry point for the run-time sets up the input and output +pointers to point to @tt{stdin} and @tt{stdout} and is updated +to handle the proper printing of a void result: + +@filebox-include[fancy-c evildoer "runtime/main.c"] + +But the real novelty of the Evildoer run-time is that there +will be new functions that implement @racket[read-byte], +@racket[peek-byte], and @racket[write-byte]; these will be C +functions called @racket[read_byte], @racket[peek_byte] and +@racket[write_byte]: + +@filebox-include[fancy-c evildoer "runtime/io.c"] + +This functionality is implemented in terms of standard C library +functions @tt{getc}, @tt{ungetc}, @tt{putc} and the run-time system's +functions for encoding and decoding values such as +@tt{val_unwrap_int}, @tt{val_wrap_void}, etc. diff --git a/www/practice.scrbl b/www/practice.scrbl new file mode 100644 index 00000000..34867de1 --- /dev/null +++ b/www/practice.scrbl @@ -0,0 +1,10 @@ +#lang scribble/manual +@title[#:style '(toc unnumbered)]{Practice} + +Practice assignments are helpful for learning course concepts, but do +not contribute toward your grade. + +@local-table-of-contents[#:style 'immediate-only] + +@include-section{assignments/1.scrbl} +@include-section{assignments/2.scrbl} diff --git a/www/schedule.scrbl b/www/schedule.scrbl index 115f6747..f2c294b4 100644 --- a/www/schedule.scrbl +++ b/www/schedule.scrbl @@ -15,38 +15,39 @@ @tabular[#:style 'boxed #:sep @hspace[1] #:row-properties '(bottom-border) -(list (list @bold{Date} @bold{Topic} @bold{Due}) -(list @day{6/1} @secref["Intro"] "") -(list @day{6/2} @secref["OCaml to Racket"] "") -(list @day{6/3} @secref["a86"] "") -(list @day{6/4} @secref["Abscond"] @seclink["Assignment 1"]{A1}) -(list @day{6/5} @itemlist[@item{@secref["Blackmail"]} @item{@secref["Con"]}] @seclink["Assignment 2"]{A2}) -(list @day{6/8} @itemlist[@item{@secref["Dupe"]} @item{@secref{Dodger}}] "") -(list @day{6/9} @secref["Evildoer"] "") -(list @day{6/10} @secref["Extort"] "") -(list @day{6/11} @secref["Fraud"] "") -(list @day{6/12} @secref["Hustle"] @seclink["Assignment 3"]{A3}) -(list @day{6/15} @secref["Hoax"] "") -(list @day{6/16} "Midterm 1" @secref["Midterm_1"]) -(list @day{6/17} @secref["Iniquity"] "") -(list @day{6/18} @elem{@secref["Iniquity"], cont.} "") -(list @day{6/19} @elem{Juneteenth Holiday} "") -(list @day{6/22} @secref["Jig"] @seclink["Assignment 4"]{A4}) -(list @day{6/23} @secref["Knock"] "") -(list @day{6/24} @elem{@secref["Knock"], cont.} "") -(list @day{6/25} @secref["Loot"] "") -(list @day{6/26} @elem{@secref["Loot"], cont.} "") -(list @day{6/29} @elem{GC} @seclink["Assignment 5"]{A5}) -(list @day{6/30} @secref["Mug"] "") -(list @day{7/1} "Midterm 2" @secref["Midterm_2"]) -(list @day{7/2} @secref["Mountebank"] "") -(list @day{7/3} "Independence Day Holiday" "") -(list @day{7/6} @secref["Neerdowell"] @seclink["Assignment 6"]{A6}) -(list @day{7/7} @secref["Outlaw"] "") -(list @day{7/8} @elem{@secref["Outlaw"], cont.} "") -(list @day{7/9} "Slack" "") -(list @day{7/10} "Slack" @secref{Project}) +(list (list @bold{Date} @bold{Topic} @bold{Notes} @bold{Due}) +(list @day{6/1} "Intro to compilers, Racket language, a86 assembly" @itemlist[@item{@secref["Intro"]} @item{@secref["OCaml to Racket"]} @item{@secref["a86"]}] + @seclink["Practice 1"]{P1}) +(list @day{6/2} "First compiler, control flow, type tags" @itemlist[@item{@secref["Abscond"]} @item{@secref["Blackmail"]} @item{@secref["Con"]} @item{@secref["Dupe"]}] @seclink["Practice 2"]{P2}) +(list @day{6/3} "I/O, system calls, ABI" @secref["Evildoer"] "") +(list @day{6/4} "Errors, type tag checking" @secref["Extort"] @seclink["Assignment 1"]{A1}) +(list @day{6/5} "Binding and variables, run-time stack, compile-time environment" @secref["Fraud"] "") +(list @day{6/8} "Binary operations" @secref["Fraud"] @seclink["Assignment 2"]{A2}) +(list @day{6/9} "Inductive data, memory allocation, pointer values" @secref["Hustle"] "") +(list @day{6/10} "Array data, pointer offsets, mutation" @secref["Hoax"] "") +(list @day{6/11} "Slack" "" @seclink["Assignment 3"]{A3}) +(list @day{6/12} @bold{Exam, no lecture} "" @seclink["Exam_1"]{E1}) +(list @day{6/15} "Function definitions and calls" @secref["Iniquity"] @seclink["Assignment 4"]{A4}) +(list @day{6/16} "Tail calls" @secref["Jig"] "") +(list @day{6/17} "First-class functions, closures" @secref["Loot"] "") +(list @day{6/18} "First-class functions, code pointers, environments" @secref["Loot"] @seclink["Assignment 5"]{A5}) +(list @day{6/19} @bold{Juneteenth Holiday, no lecture} "" "") +(list @day{6/22} "Pattern matching, interpretation" @secref["Knock"] @seclink["Assignment 6"]{A6}) +(list @day{6/23} "Pattern matching, compilation" @secref["Knock"] "") +(list @day{6/24} "Symbols, static and dynamic interning" @secref["Mug"] "") +(list @day{6/25} "Compound static data" @secref["Mountebank"] @seclink["Assignment 7"]{A7}) +(list @day{6/26} @bold{Exam, no lecture} "" @seclink["Exam_2"]{E2}) +(list @day{6/29} "Structures" @secref["Neerdowell"] @seclink["Assignment 8"]{A8}) +(list @day{6/30} "Slack" "" "") +(list @day{7/1} "Source transformations" "" "") +(list @day{7/2} "Peephole optimizations" "" @seclink["Assignment 9"]{A9}) +(list @day{7/3} @bold{Independence Day Holiday, no lecture} "" "") +(list @day{7/6} "Garbage collection" "" @seclink["Assignment 10"]{A10}) +(list @day{7/7} "Self-hosting" @secref["Outlaw"] "") +(list @day{7/8} "Conclusion" "" "") +(list @day{7/9} "Slack" "" "") +(list @day{7/10} @bold{Exam, no lecture} "" "E3") ) ] -@bold{Final project assessment: @|final-date|.} +@;{@bold{Final project assessment: @|final-date|.}} diff --git a/www/syllabus.scrbl b/www/syllabus.scrbl index f1ec4823..4ae6d704 100644 --- a/www/syllabus.scrbl +++ b/www/syllabus.scrbl @@ -1,22 +1,16 @@ #lang scribble/manual @(require scribble/core - "defns.rkt") + "defns.rkt") -@provide[exam-table] +@;provide[exam-table] -@(define grades:m1 (list @elem{Midterm, @m1-date} "10%")) -@(define grades:f (list @elem{Final Exam, @final-date} "20%")) +@(define ELMS (link elms-url "ELMS")) +@(define Piazza (link piazza "Piazza")) @(define (make-grade-component-table . entries) @tabular[#:style 'boxed - #:sep @hspace[1] - (list* (list @bold{Component} @bold{Percentage}) entries)]) - - -@(define exam-table - @make-grade-component-table[ - @grades:m1 - @grades:f]) + #:sep @hspace[1] + (list* (list @bold{Component} @bold{Percentage}) entries)]) @title[#:style 'unnumbered]{Syllabus} @@ -30,7 +24,7 @@ @bold{Email:} @prof1-email -@bold{Office Hours:} By appointment. Send email or ELMS message to set +@bold{Office Hours:} By appointment. Send email or @ELMS message to set up. @bold{Credits:} 3 @@ -117,14 +111,32 @@ of the course: @itemlist[ @item{Overview of compilation} - @;item{Operational semantics} - @item{Interpreters} - @item{Intermediate representations and bytecode} - @item{Code generation} - @item{Run-time systems} + @item{Introduction to Racket Language} + @item{Introduction to a86 Assembly Code} + @item{Compiler specification} + @item{First compiler} + @item{Run-time type tagging} + @item{I/O} + @item{Run-time system calls} + @item{Errors} + @item{Variables, binding, and lexical addressing} + @item{Binary primitivives} + @item{Run-time stack and compile-time environments} + @item{Inductive heap-allocated data} + @item{Sequential heap-allocate data} + @item{Function definitions and calls} + @item{Tail calls} + @item{First-class functions} + @item{Letrec expressions} + @item{Pattern matching} + @item{Symbols and interned data} + @item{Structures} + @item{Static analysis} + @item{Compiler optimization} @item{Garbage collection} @item{Type systems, type soundness, type inference} - @item{Register allocation and optimization} + @item{Memory safety} + @item{Bootstrapping} @item{Language design} @item{Advanced topics in compilation}] @@ -135,12 +147,11 @@ of the course: @section{Course Structure} -The course will consist of in-person lectures, which will be recorded -and available on ELMS immediately after each lecture. There are two -midterms, a final project, which counts as the final assessment for -the class, several assignments, and several quizes and surveys. -Midterms are take-home exams and completed online over a -@|midterm-hours|-hour period. +The course will consist of @if[online? @elem{synchronous on-line} +@elem{in-person}] lectures. Video recordings and slides will be +posted immediately after each lecture. There are three exams, several +assignments, and several quizes and surveys. Exams are take-home and +completed online over a @|exam-hours|-hour period. @section{Tips for Success in this Course} @@ -222,14 +233,14 @@ and we ask you to do the same for all of your fellow Terps. @bold{Communication with Instructor:} Email: If you need to reach out and communicate with @prof1, please email at @|prof1-email|. Please DO NOT email -questions that are easily found in the syllabus or on ELMS (i.e. When +questions that are easily found in the syllabus or on @ELMS (i.e. When is this assignment due? How much is it worth? etc.) but please DO reach out about personal, academic, and intellectual concerns/questions. -ELMS: IMPORTANT announcements will be sent via ELMS messaging. You +ELMS: IMPORTANT announcements will be sent via @ELMS messaging. You must make sure that your email & announcement notifications (including -changes in assignments and/or due dates) are enabled in ELMS so you do +changes in assignments and/or due dates) are enabled in @ELMS so you do not miss any messages. You are responsible for checking your email and Canvas/ELMS inbox with regular frequency. @@ -286,7 +297,7 @@ can easily see what issues are being brought up.} @section{Grades} All assessment scores will be posted on the course -@link[elms-url]{ELMS} page. +@ELMS page. Late work will not be accepted for course credit so please plan to have it submitted well before the scheduled deadline. @@ -294,7 +305,7 @@ have it submitted well before the scheduled deadline. Any formal grade disputes must be submitted in writing and within one week of receiving the grade. Final letter grades are assigned based on the percentage of total assessment points earned. To be fair to -everyone I have to establish clear standards and apply them +everyone, there are clear established standards which are applied consistently, so please understand that being close to a cutoff is not the same as making the cut (89.99 ≠ 90.00). It would be unethical to make exceptions for some and not others. @@ -303,15 +314,16 @@ Your final course grade will be determined according to the following percentages: @make-grade-component-table[ - (list "Assignments" "45%") - (list @elem{Quizzes & surveys} "15%") - (list "Midterms (2)" "25%") - (list "Final project" "15%")] + (list "Assignments" "30%") + (list @elem{Quizzes & surveys} "20%") + (list "Exam 1" "15%") + (list "Exam 2" "15%") + (list "Exam 3" "20%")] Final letter grades are assigned following this grading scheme: @tabular[#:style 'boxed @;#:sep @;"|" @;@hspace[1] - (list (list "A+" "[100,97]" "B+" "(90,87]" "C+" "(80,77]" "D+" "(70,67]" " " " ") + (list (list "A+" "[100,97]" "B+" "(90,87]" "C+" "(80,77]" "D+" "(70,67]" " " " ") (list "A" "(97,94]" "B" "(87,84]" "C" "(77,74]" "D" "(67,64]" "F" "(60,0]") (list "A-" "(94,90]" "B-" "(84,80]" "C-" "(74,70]" "D-" "(64,60]" " " " "))] @@ -321,7 +333,7 @@ number less than @math{x} and greater than or equal to @math{y}. @section[#:tag "syllabus-videos"]{Videos} -Lectures will be recorded and posted to ELMS shortly after every +Lectures will be recorded and posted to @ELMS shortly after every class. There are also prepared videos available covering the material. These videos will be made available as the course progresses. If there is ever any issue with accessing these videos, @@ -338,44 +350,35 @@ otherwise noted). Assignments will be submitted through @section[#:tag "syllabus-quiz"]{Quizzes & surveys} -There will be @bold{many} quizzes and surveys. These will be administered -through ELMS. Completed surveys receive full credit. Instructors reserve the -right to reject survey responses that are not considered thoughtful. - -@section[#:tag "syllabus-midterms"]{Midterms} - -There will be two @secref{Midterms}, which will be @bold{take-home} -exams. Exams will be distributed at least @|midterm-hours| hours -before the due date of the midterm. - -@itemlist[ - @item{Midterm 1: @bold{@m1-date}} - @item{Midterm 2: @bold{@m2-date}} -] +There will be @bold{many} quizzes and surveys. These will be +administered through @|ELMS|. Completed surveys receive full credit. +Instructors reserve the right to reject survey responses that are not +considered thoughtful. -@section[#:tag "syllabus-project"]{Project} +@section[#:tag "syllabus-exams"]{Exams} -There will be a course @secref{Project} that will be assessed during -the final exam period for the course: +There will be three @secref{Exams}, which will be @bold{take-home} +exams. Exams will be distributed at least @|exam-hours| hours +before the due date of the exam. @itemlist[ - @item{Final Project Assessment: @bold{@final-date}} + @item{Exam 1: @bold{@exam1-date}} + @item{Exam 2: @bold{@exam2-date}} + @item{Exam 3: @bold{@exam3-date}} ] -The project description will be distributed approximately 3 weeks -before the due date. @section{Computing Resources} -Programming projects can be developed on your own system and subitted +Programming assignments can be developed on your own system and subitted via @link[gradescope]{Gradescope}, which will provide virtual machines -suitably configured for running your code. All project submissions -@bold{must} work correctly on the Gradescope VMs, and your projects +suitably configured for running your code. All assignment submissions +@bold{must} work correctly on the Gradescope VMs, and your assignment will be graded solely based on their results on those machines. Because language and library versions may vary with the installation, in unfortunate circumstances a program might work perfectly on your system but not work at all on the VMs. Thus we -strongly recommend that as you develop any project, you should run it +strongly recommend that as you develop any assignment, you should run it @bold{several days early} on Gradescope to have time to address any compatibility problems. @@ -384,7 +387,7 @@ compatibility problems. Course staff will interact with students outside of class in primarily two ways: office hours, and electronically via e-mail. The use of -@link[piazza]{Piazza} and/or other classroom forums is allowed, and +@Piazza and/or other classroom forums is allowed, and discussion amongst the students is encouraged, as long as the discuss is @italic{about the concepts} and not @italic{the solutions}. @;{The majority of communication should be via office hours.} @@ -394,7 +397,7 @@ will be provided during office hours. Office hours for the instructional staff will be posted on the course web page. Additional assistance will provided via discussion on -@link[piazza]{Piazza}. You may use this forum to ask general +@|Piazza|. You may use this forum to ask general questions of interest to the class as a whole, e.g., administrative issues or problem set clarification questions. The course staff will monitor it on a daily basis, but do not expect immediate answers to @@ -410,7 +413,7 @@ Personal e-mail to TAs should be reserved for issues that cannot be handled by the above methods. Important announcements will be made in class or on the class web -page, and via Piazza. +page, and via @|Piazza|. @section{Excused Absences} @@ -438,16 +441,16 @@ relative's funeral) will be excused so long as the absence is requested in writing at least @bold{2 days} in advance and the student includes documentation that shows the absence qualifies as excused; @bold{a self-signed note} is not sufficient as exams are Major -Scheduled Grading Events. For this class, such events are the final -project assessment and midterms, which will be due on the following +Scheduled Grading Events. For this class, such events are the exams, +which will be due on the following dates: @itemlist[ - @item{Midterm 1: @bold{@m1-date}} - @item{Midterm 2: @bold{@m2-date}} - @item{Final Project Assessment: @bold{@final-date}}] + @item{Exam 1: @bold{@exam1-date}} + @item{Exam 2: @bold{@exam2-date}} + @item{Exam 3: @bold{@exam3-date}}] -The final exam is scheduled according to the University Registrar. +@;{The final exam is scheduled according to the University Registrar.} For medical absences, you must furnish documentation from the health care professional who treated you. This documentation must verify @@ -473,13 +476,13 @@ discuss the circumstances. We are not obligated to offer a substitute assignment or to provide a makeup exam unless the failure to perform was due to an excused absence. -The policies for excused absences @bold{do not} apply to project -assignments. Projects will be assigned with sufficient time to be +The policies for excused absences @bold{do not} apply to +assignments. Assignments will be assigned with sufficient time to be completed by students who have a reasonable understanding of the necessary material and begin promptly. In cases of @bold{extremely serious} documented illness of @bold{lengthy duration} or other protracted, severe emergency situations, the instructor may consider -extensions on project assignments, depending upon the specific +extensions on assignments, depending upon the specific circumstances. Besides the policies in this syllabus, the University's policies apply @@ -516,22 +519,22 @@ exam and assignment. Please also carefully read the Office of Information Technology's @link["http://www.nethics.umd.edu/aup/"]{policy} regarding acceptable use of computer accounts. -Assignments and projects are to be completed @bold{individually}, -therefore cooperation with others or use of unauthorized materials on -assignment or projects is a violation of the University's Code of -Academic Integrity. Both the person receiving assistance @bold{and the -person providing assistance} are in violation of the honor -code. @bold{Any evidence} of this, or of unacceptable use of computer -accounts, use of unauthorized materials or cooperation on exams or -quizzes, or other possible violations of the Honor Code, @bold{will be -submitted} to the Student Honor Council, which could result in an XF -for the course, suspension, or expulsion. +Assignments are to be completed @bold{individually}, therefore +cooperation with others or use of unauthorized materials on assignment +is a violation of the University's Code of Academic Integrity. Both +the person receiving assistance @bold{and the person providing +assistance} are in violation of the honor code. @bold{Any evidence} of +this, or of unacceptable use of computer accounts, use of unauthorized +materials or cooperation on exams or quizzes, or other possible +violations of the Honor Code, @bold{will be submitted} to the Student +Honor Council, which could result in an XF for the course, suspension, +or expulsion. @itemlist[ @item{For learning the course concepts, students are welcome to study together or to receive help from anyone else. You may discuss with -others the assignment or project requirements, the features of the +others the assignment requirements, the features of the programming languages used, what was discussed in class and in the class web forum, and general syntax errors. Examples of questions that would be allowed are "Does a cond expression always end with an @@ -559,10 +562,9 @@ section of the program. } ] @bold{AI tool disclosure:} If a student chooses to use an AI tool to -assist in any course work (e.g. assignments, programs, projects, -reports, etc), they must disclose this information to the -instructor. This disclosure should include the name of the AI tool and -explain how it was used. +assist in any course work (e.g. assignments, programs, reports, etc), +they must disclose this information to the instructor. This disclosure +should include the name of the AI tool and explain how it was used. Failure to adhere to this policy may result in a zero on the particular course work where the AI tool is used. In addition the @@ -575,21 +577,21 @@ are not limited to:} @itemlist[ -@item{Failing to do all or any of the work on a project by yourself, +@item{Failing to do all or any of the work on a assignment by yourself, other than assistance from the instructional staff.} -@item{Using any ideas or any part of another person's project, or copying any other individual's work in any way.} +@item{Using any ideas or any part of another person's assignment, or copying any other individual's work in any way.} -@item{Giving any parts or ideas from your project, including test +@item{Giving any parts or ideas from your assignment, including test data, to another student.} @item{Allowing any other students access to your program on any computer system.} -@item{Posting solutions to your projects to publicly-accessible sites, +@item{Posting solutions to your assignment to publicly-accessible sites, e.g., on github.} -@item{Transferring any part of an assignment or project to or from another +@item{Transferring any part of an assignment to or from another student or individual by any means, electronic or otherwise.}] If you have any question about a particular situation or source then