Before doing any actual programming, read through all the instructions below. And as always, ask early on Piazza if something's unclear!
putchar(int) : int =================== The [putchar] function prints an integer in the range [0..2^8-1] to stdout. It returns the integer printed (or on error, the integer error code EOF, just as in C). If the integer argument supplied to [putchar] is not in the 8-bit character range given above, the result is undefined.Previously in a4, it returned the unit value.
If you're re-using your own type-checker for this assignment, you'll have to update putchar's return type to match the new specification.
(* NAME: Your name OUID: Ohio University ID I worked with ... on this assignment. *)Each student should individually turn in ssa.ml on Blackboard, regardless whether you worked with someone else. Pair programming does not mean each student does half of the assignment. Instead, it means the two of you construct tycheck.ml collaboratively, while both sitting at the same computer screen.
$ tar xzvf a5.tgzIn the resulting directory src you'll find the following file structure:
src/ -- compiler source files Makefile -- the project Makefile _tags -- the tags file for ocamlbuild AST.mli -- language-independent abstract syntax stuff AST.ml -- associated helper functions exp.mli -- the definition of Grumpy's abstract syntax exp.ml -- associated functions lexer.mll -- ocamllex source file (stub) parser.mly -- Menhir source file (stub) tycheck.mli -- The type-checker interface tycheck.ml -- The type-checker (stub) ssa.mli -- Defines the RTL intermediate language ssa.ml -- (Part II) grumpy.ml -- the toplevel compiler program tests/ -- test casesTo build the project, type
$ makeAs in a3 and a4, you'll see a bunch of warnings at this point. That's OK. The lexer, parser, and type-checker files are the same stubs that were given to you in the last assignment. Before you get started on this assignment, copy your own lexer.mll, parser.mly, and tycheck.ml in their place.
Run the tests by doing
$ make testor by typing ./run.sh from within the tests directory. (Here's sample passing test output and sample failing test output.)
For this assignment, the *.expected files in the tests directory contain the values we expect each Grumpy program to return. Building the test target does the following:
Your job in this part is to implement the compilation functions sketched out as stubs in src/ssa.ml. The top-level compilation function
val ssa_of_prog : (ty, ty exp) prog -> (ty, instr list) progmaps type-annotated Grumpy expression ASTs to programs in which function bodies and the program result are lists of RTL instructions; it's implemented for you. The main functions you need to implement are described below.
(** [ty]-annotated identifiers *) type iid = ty tid (** As in the [exp] language, we define two versions of RTL expressions, which together correspond to a subset of the expressions of type [exp] in [exp.mli]. The first, [raw_iexp], defines the RTL language's expression constructors. The second, [iexp], wraps [raw_iexp]s with a type [ty]. *) type raw_iexp = | IInt of int32 (** 32-bit integers *) | IFloat of float (** Double-precision floats *) | IId of id (** Identifiers *) ...You may need to reference other files, such as AST.mli, to recall the definitions of types such as tid (typed identifiers).
Once you've thoroughly read the comments describing RTL, define the provided stub functions:
let fresh_ids (p : gensym_pkg) (n : int) : id list = ... let rec instrs_of_exp (p : gensym_pkg) (out : id) (e : ty exp) : instr list = ... and instrs_of_explist (p : gensym_pkg) (out : id) (el : (ty exp) list) : instr list = ...Informal specifications for each of these functions are given in comments in the file. In short:
Implement this function in terms of the function fresh_id defined right above it in the file. This should be reasonably straightforward.
The result of instrs_of_exp is a list of RTL instructions corresponding to expression e. It may not be immediately obvious how to compile some expressions to instruction lists. If you get stuck, consider taking a break to think about it away from your computer.
| EInt n -> [IAssign(out, mk_iexp (IInt n) e.ety_of)] | EFloat f -> [IAssign(out, mk_iexp (IFloat f) e.ety_of)] | EId x -> [IAssign(out, mk_iexp (IId x) e.ety_of)]define the integer, float, and id cases. In each, you'll note that we convert an expression into an assignment instruction (not every expression is compiled to just an assignment of course), enforcing the following invariant: In each call to instrs_of_exp, the ultimate result of expression e is always stored in variable out.
To maintain this programming discipline in other cases, you'll need to introduce fresh variable names, using fresh_id or fresh_ids, binding these vars to intermediate results.
To enforce this property, you may find yourself generating fresh variables at a number of points. Think hard, in particular, about ELet. (The new function subst_var described in exp.mli may come in handy while implementing this case.)
Pay attention to the compilation of type information, which will be used in a later stage of the compiler. In particular, make sure that you're correctly compiling types through from decorated expressions to generated instructions (the types themselves are not compiled, but should be properly labeled in each iid and iexp that appears in your generated code). Read ssa.mli for pointers on how the types correspond across the two languages.
IPhi(phi_lbl, res, (x,l1), (y,l2)are entered via either label l1 or label l2, with no intervening labels. You may need to think a bit about this when implementing case EIf, the only case of instrs_of_exp in which phi nodes need appear...