├── .gitignore ├── Lecture 2.txt ├── Midterm.md ├── Lecture 17 (Lambda Calculus).md ├── Lecture 4(variable and stack).txt ├── Lecture 5(chunk and stack).md ├── Lecture 6(evaluating expression).md ├── Lecture 9(nested procedure).md ├── Lecture 14 (Context Sensitive Analysis).md ├── Lecture 16 Cheney's Copying GC.md ├── Lecture 11(closure and tail call).md ├── Lecture 7(if-while-register allocation).md ├── Lecture 8(procedure).md ├── Lecture 3(linking and reallocation).txt ├── Final └── Lambda.scala ├── Lecture 15 (Code Generation and Memory Management).md ├── Lecture 10(static link and nested procedure).md ├── Lecture 13 (Context-free language).md └── Lecture 12 (DFA and NFA).md /.gitignore: -------------------------------------------------------------------------------- 1 | ass/ 2 | ass2/ 3 | img/ 4 | cs241e/ 5 | META-INF/ 6 | *.pdf 7 | -------------------------------------------------------------------------------- /Lecture 2.txt: -------------------------------------------------------------------------------- 1 | Advantage of twos complement 2 | // yeah this is easy just google it urself -------------------------------------------------------------------------------- /Midterm.md: -------------------------------------------------------------------------------- 1 | 1. bits / binary / two's-complement 2 | 2. MIPS architecture 3 | - CPU 4 | - Registers 5 | - Memory 6 | - Fetch / Increment PC / execute cycle loop 7 | - Machine Language 8 | 3. Assembly Language / Labels / Symbol table 9 | 4. Relocation and Linking 10 | 5. Variables / Extent of variables 11 | 6. Stack pointer / frame pointers 12 | 7. Expressions / if / while 13 | 8. Register allocation / live variables (not on assignment) 14 | 9. Procedures / conventions / call / prologue / epilogue 15 | 10. Nested procedures / Static link -------------------------------------------------------------------------------- /Lecture 17 (Lambda Calculus).md: -------------------------------------------------------------------------------- 1 | # Lambda Calculus 2 | 3 | ``` 4 | e -> v | e e | λv.e 5 | ^ ^ ^ 6 | | | function creation, `v` is the parameter, `e` is the body 7 | | expr followed by expr, means function call 8 | | first `e` is the funciton / closure, second `e` is the argument 9 | expr evaluate to values 10 | 11 | (λv.e1) e2 -> e1 [e2 / v] β-reduction (substitution) 12 | 13 | (λv.v v) (a b c) -> (a b c) (a b c) 14 | (λv.v v) (λx. x x) -> (λx. x x) (λx. x x) 15 | λy.(λx. λy. x y) y) -> λy. λy.y y 16 | λy. (λx. λy'. x y') y -> λy. λy'. y y' 17 | ``` 18 | 19 | Abastraction : 20 | 21 | ``` Scala 22 | object Lambda { 23 | abstract class Expr 24 | case class Var(name: Symbol) extends Expr 25 | case class Apply(fun: Expr, args: Expr) extends Expr 26 | case class λ(name: Symbol, body: Expr) extends Expr 27 | implicit def toVar(name: Var): Expr = Var(name) 28 | } 29 | 30 | 31 | 32 | One = λ(f, λ(x, f @@ x)) 33 | Two = λ(f, λ(x, f @@ (f @@ x))) 34 | Zero = λ(f, λ(x, x)) 35 | Succ = λ(n, λ(f, λ(x, n @@ f @@ (f @@ x)))) 36 | 37 | 38 | ``` 39 | 40 | -------------------------------------------------------------------------------- /Lecture 4(variable and stack).txt: -------------------------------------------------------------------------------- 1 | A * variable * is an abstraction of a storage location that holds a value 2 | we translate variable into machine concepts 3 | the * extend * of a variable * instance * is the time interval in which its value can be accessed 4 | 5 | E.g. 6 | global variable -> entire execution of the program 7 | procedure-local variable -> one execution of procedure 8 | 9 | fields of abstract/record -> from object creation time to time of last access 10 | 11 | E.g. 12 | 13 | def fact(x: Int): Int = if(x < 2) 1 else x * fact(x-1) 14 | 15 | fact(3) = 3 * fact(2) 16 | = 3 * 2 * fact(1) 17 | = 3 * 2 * 1 18 | 19 | 20 | we have 3 instances of variable `x` at runtime 21 | 22 | 23 | _______ 24 | ____|-x = 1-|___ fact(1) 25 | ___________|------x = 2-----|____________ fact(2) 26 | |__________________x = 3__________________| fact(3) 27 | 28 | The LIFO nature suggests us to use stack 29 | 30 | 31 | 32 | Implement a stack: 33 | _______ 34 | / \ 35 | | Code | 36 | | ... | 37 | |_________| 38 | | | 39 | | ^ | 40 | | | | 41 | |---------| <- top 42 | | stack | 43 | \_______/ 44 | 45 | - designated storage (register) to store address of top of stack (stack pointer) 46 | - push a word onto the stack 47 | - decrement the stack pointer by 4 48 | - pop a record 49 | - increment stack pointer by 4 50 | 51 | The convention is to use $30 as the SP, SP is initialized to end of memory 52 | 53 | 54 | 55 | | | 56 | | a | <- SP($30) = 100 57 | | b | 104 58 | | c | 108 59 | | * | . 60 | | * | . 61 | |____*____| . 62 | 63 | 64 | -------------------------------------------------------------------------------- /Lecture 5(chunk and stack).md: -------------------------------------------------------------------------------- 1 | // (ctd) 2 | 3 | Implement a stack: 4 | - designate storage (register) to store address at top of stack (stack pointer) 5 | - push a word, decrement sp by 4 6 | - pop a word, 7 | - increment sp by 4 8 | 9 | convention: use $30 as the sp, $30 is initialized to the end of memory 10 | 11 | E.g. read "c" (two wrods from top of stack) into $1 12 | - __**symbol table**__ maps each variable to stack offset 13 | 14 | Note: symbol table is created in compile time 15 | 16 | ``` MIPS 17 | LW (1, 8, 30) 18 | ``` 19 | 20 | Define: **frame pointer** is a copy of stck pointer made at start of procedure thatdoes not change for the during of the procedure invocation. It enables us to modify stack pointer 21 | 22 | Convention: $29 is the frame pointer 23 | 24 | ## Summary 25 | 26 | stack pointer: $30 27 | - changes during a procedure invocation (push/pop) 28 | - the value is only known at run-time 29 | 30 | frame pointer 31 | - stay constant within a procedure invocation 32 | - only known at run-time 33 | 34 | symbol table 35 | - maps each variable to offsets from frame pointer 36 | - only around during compile time 37 | 38 | 39 | 40 | ## Assignment Convention: 41 | - all data in memory will be organized in "chunks" 42 | - block of consecutive memory locations 43 | - always push/pop a chunk at a time 44 | - offsets indexed by variables (symbol table variable -> offset in chunk) 45 | - read/write variable in chunk 46 | - need address of chunk in some register: the **base** register (run-time value) 47 | - add offset of variable, using **LW**/**SW** (compile time constant) 48 | - the size of the chunk is inferred by compiler during compile time by calculating the sizes of all variables 49 | 50 | ``` 51 | __________ 52 | | size | 0 ~ reserved 53 | | all | 4 ~ reserved 54 | | a | 8 55 | | b | 12 56 | |____c_____| 16 57 | ``` 58 | 59 | ## Expression 60 | e.g. 61 | 62 | `a * b + (a * d)` 63 | 64 | we represent the expression as a tree 65 | ``` 66 | + 67 | / \ 68 | * * 69 | / \ / \ 70 | a b c d 71 | ``` -------------------------------------------------------------------------------- /Lecture 6(evaluating expression).md: -------------------------------------------------------------------------------- 1 | (ctd) 2 | 3 | ## Expression 4 | e.g. 5 | 6 | `a * b + (a * d)` 7 | 8 | we represent the expression as a tree 9 | ``` 10 | + 11 | / \ 12 | * * 13 | / \ / \ 14 | a b c d 15 | ``` 16 | 17 | Techniques : 18 | 19 | 1. use a stack (used by CS241 regular sections) 20 | ``` Scala 21 | // generate code to evaluate `e1 op e2` 22 | // the convention we use is to put the result of evaluation result 23 | // in $3 24 | def evalute(e1 : Expression, op : Operator, e2 : Expression): Code = 25 | block( 26 | evaluate(e1), 27 | "push $3", // replace quoted string with mips code 28 | evaluate(e2), 29 | "pop $4", 30 | "$3 = $4 op $3" 31 | ) 32 | ``` 33 | 34 | 35 | Recursive descend is general :) 36 | but inefficient generated code :( 37 | and very difficult to optimize further :( 38 | 39 | 2. We can use __Variables__ / __Virtual Registers__ 40 | 41 | `t1 = a * b` 42 | 43 | `t2 = c * d` 44 | 45 | `t3 = t1 + t2` 46 | 47 | ``` Scala 48 | // generate code to evaluate e1 op e2 and put result in a varialbe 49 | def evaluates(e1 : Expression, op : Operator, e2 : Expression) : (Code, Variable) = { 50 | val (c1, v1) = evaluate(e1) 51 | val (c2, c2) = evaluate(e2) 52 | 53 | val v3 = new Variable(#uuid#) 54 | 55 | val code = block( 56 | c1, 57 | c2, 58 | "v3 = v1 op v2" 59 | ) 60 | 61 | (code, v3) 62 | ``` 63 | } 64 | 65 | :( it looks like we ended up using a lot of variables, 66 | is it bad? 67 | not really if we can efficiently map __many__ varaibles to __few__ registers/memory locations 68 | 69 | :) flexiable but general 70 | 71 | :) gerated code easy to optimize further 72 | 73 | :( machine language instructions used __registers__ but not __variables__ 74 | 75 | 3. Compromise: use variables, operate on registers (Assignment 4) 76 | 77 | ``` Scala 78 | // save result in $3 79 | def evaluate(e1: Expression, op: Operator, e2; Expression): Code = { 80 | withTempVar{t1 => 81 | block( 82 | evaluate(e1), 83 | "write t1 <- $3", 84 | evaluate(e2), 85 | "read $4 <- t1", 86 | "$3 = $3 + $4" 87 | ) 88 | } 89 | } 90 | ``` 91 | 92 | compared to technique #2, all the operations are done on the registers rather than on variables 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Lecture 9(nested procedure).md: -------------------------------------------------------------------------------- 1 | # Nested procedure 2 | E.g. 3 | ``` Scala 4 | def f() = { 5 | val x=2 6 | val w=5 7 | 8 | def g() = { 9 | val y=3 10 | val w=7 11 | x + y + h() 12 | } 13 | 14 | def h() = { 15 | val z=4 16 | z + w 17 | // ^ 18 | // which `w` is this ? 19 | // the one is g() or the one in f() 20 | // if w is 5, then this language is using static/lexical scope 21 | // that is h() uses f()'s `w` 22 | // names are resolved statically enclosing procedure 23 | // (in the source code) 24 | // else it is called Dynamic scope: 25 | // h() uses g()'s `w` (which is 7) 26 | // variable reference is resolved at runtime 27 | // (in the dynamic stack at calls) 28 | 29 | } 30 | 31 | g() 32 | } 33 | ``` 34 | 35 | 36 | E.g. using nested procedure to implement `while` loop 37 | ``` Scala 38 | // loop version 39 | def m() = { 40 | var i=0; 41 | var j=0; 42 | 43 | while(i<10) { 44 | // do something 45 | i++ 46 | j++ 47 | } 48 | 49 | i+j 50 | } 51 | 52 | // nested func version 53 | def m2() = { 54 | var i=0; 55 | var j=0; 56 | 57 | def loop() = { 58 | if(i<10){ 59 | i++ 60 | j++ 61 | loop() 62 | } 63 | } 64 | 65 | loop() 66 | i+j 67 | } 68 | ``` 69 | 70 | 71 | How to access `f`'s `w` from inside of `h` 72 | - how do we skip `g`'s `'w` 73 | - dynamic link doesn't really serve our purpose here 74 | - thus we need a static link 75 | 76 | ## Definition: 77 | - a `static link` is a pointer to the frame of the statically/lexically enclosing procedure of the currently executing procedure 78 | ``` 79 | frame 80 | 81 | fp+-> +-----+ 82 | h | | 83 | | | 84 | | | 85 | |dl pp 86 | ++----+ 87 | v +----+ 88 | v | | paramChunk 89 | v | | 90 | v | sl+------------+ 91 | v +----+ | 92 | +v----+ | 93 | | | | 94 | g | w | | 95 | | | | 96 | |dl pp | 97 | ++----+ | 98 | v +----+ | 99 | v | | | 100 | v | | | 101 | v | sl+------+ | 102 | v +----+ | | 103 | +v----+ | | 104 | | | v | 105 | f | w <-<---------<+<----+ 106 | | | 107 | |dl pp 108 | +-----+ 109 | 110 | ``` -------------------------------------------------------------------------------- /Lecture 14 (Context Sensitive Analysis).md: -------------------------------------------------------------------------------- 1 | # Context Sensitive Analysis 2 | 3 | ## Different definitions of __Type__ 4 | 5 | ### Definition: a __type__ is a collection of values 6 | ### Definition: a __type__ is an interpretation of sequence of bits 7 | ### Definition: a __type__ is a computable property of a program that guarentees some property of its execution 8 | 9 | E.g. 10 | - `1001` 11 | - unsigned integer: 9 12 | - two's complement: -7 13 | - `0100 0001` 14 | - ASCII : 'A' 15 | 16 | 17 | 18 | ## Type: 19 | 20 | ### Machine Language: 21 | - 32 bits binary string in __location__ 22 | 23 | ### Lacs/C/Java: 24 | - value of some type in __varriables__ 25 | 26 | ### Lacs 27 | - `Int` 28 | - between -2^31 and +2^31-1 with arithmetic module 2^32 29 | - `(type, ...)=>type` 30 | - function that takes arguments of specified type returns value of return type 31 | 32 | 33 | ### Definition: a __type system__ is a set of rules that computes the type of an expression of its subexpressions 34 | ### Definition: a type system is __sound__ if whenever it computes a type `τ` for expression `e`, then `e` evaluates to a value in `τ` 35 | 36 | ## To define type rules: it is similar to the definition of connectives from CS 245E 37 | E.g. 38 | 39 | ``` 40 | Γ(ID)=τ Γ(ID)=τ Γ ⊢ E:τ 41 | ___________ _________________ 42 | NUM:Int Γ ⊢ ID : τ Γ ⊢ ID = E : τ 43 | 44 | 45 | Γ ⊢ E1:Int Γ ⊢ E2:Int 46 | _________________________ 47 | Γ ⊢ E1 + E2 : Int 48 | Γ ⊢ E1 - E2 : Int 49 | Γ ⊢ E1 * E2 : Int 50 | Γ ⊢ E1 / E2 : Int 51 | 52 | 53 | Γ ⊢ E1:τ' Γ ⊢ E2:τ 54 | _____________________ 55 | Γ ⊢ E1 ; E2 : τ 56 | 57 | 58 | Γ ⊢ E1:Int Γ ⊢ E2:Int Γ ⊢ E3:τ Γ ⊢ E4:τ 59 | _____________________________ 60 | Γ ⊢ if (E1 == E2) E3 else E4 : τ 61 | Γ ⊢ if (E1 != E2) E3 else E4 : τ 62 | Γ ⊢ if (E1 <= E2) E3 else E4 : τ 63 | Γ ⊢ if (E1 >= E2) E3 else E4 : τ 64 | Γ ⊢ if (E1 > E2) E3 else E4 : τ 65 | Γ ⊢ if (E1 < E2) E3 else E4 : τ 66 | 67 | _ 68 | Γ ⊢ E': (τ)=>τ' ∀i Γ ⊢ Ei : τi 69 | ___________ 70 | _ 71 | Γ ⊢ E'(E) : 72 | 73 | 74 | ______ _______ ______ 75 | all name in vardef, vardef', defdef are distinct 76 | ______ _______ ______ ______ _______ ______ 77 | vardef, vardef' defdef ⊢ E: γ ∀i Γ ⊢ vardef, vardef', defdef ⊢ defdef; 78 | ___________________________________ 79 | ______ ______ ______ 80 | Γ ⊢ def ID (vardef) : γ = { vardef' defdef E } 81 | 82 | 83 | 84 | ______ 85 | ∀i defdef ⊢ defdef: 86 | ___________________________________ 87 | ______ 88 | ∅ ⊢ defdef 89 | {} 90 | ``` 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /Lecture 16 Cheney's Copying GC.md: -------------------------------------------------------------------------------- 1 | # Cheney's Copying Garbage Collector 2 | 3 | - split heap into two havles "semispaces" 4 | - allocate in from-space 5 | - when from-space is full: 6 | - compact from from-space to to-space, 7 | - switch from-space and to-space 8 | 9 | ``` 10 | _______ 11 | | code | 12 | | | 13 | | | 14 | | | 15 | |_______| 16 | | used | <- heapStart 1/4 17 | | | 18 | | | 19 | | | 20 | | - - - | 21 | | free | <- frome-space / heapPtr 22 | | | 23 | | | 24 | |_______| 25 | | | <- to-space / heap Middle 1/2 / semi-space top 26 | | | 27 | | | 28 | | | 29 | |_______| <- heap top 3/4 30 | | stack | 31 | | | 32 | | | 33 | | | 34 | |_______| 35 | 36 | 37 | ``` 38 | 39 | Algorithm 40 | 41 | ``` Scala 42 | def init = { 43 | heapPtr = heapStart 44 | semiSpaceTop = heapMiddle 45 | } 46 | 47 | def allocate(wanted) = { 48 | 49 | if(heapPtr + wanted > semiSpaceTop) { 50 | gc(); 51 | } 52 | 53 | val ret = heapPtr; 54 | heapPtr = heapPtr + wanted 55 | ret 56 | } 57 | 58 | def gc() = { 59 | 60 | val toSpace = if(semiSpaceTop == heapMiddle) heapMiddle else heapStart 61 | var free = toSpace 62 | 63 | var scan = dynamicLink // "top" of stack, not including frame of gc 64 | 65 | while(scan < memSize) { 66 | forwardPtr(scan) 67 | scan = scan + size(scan) 68 | } 69 | 70 | scan = toSpace 71 | while(scan < free) { 72 | forwardPtr(scan) 73 | scan = scan + size(scan) 74 | } 75 | 76 | semiSpaceTop = toSpace + semiSpaceSize 77 | heapPtr = free 78 | 79 | } 80 | 81 | def forwardPtr(block) = { 82 | for each offset `o` in `block` that holds a pointer { 83 | val newAddr = copy(deref(block+o)) // copy the pointed chunk into toSpace and return the new address in toSpace 84 | assignToAddr(block + o, newAddr) 85 | } 86 | } 87 | 88 | def copy(block) = { 89 | if(`block` is not in fromSpace) { block } 90 | else { 91 | if(size(block) >= 0) { // not yet copied 92 | copyChunk(free, block) 93 | setNext(block, free) // leave forwarding 94 | setSize(block, 0 - size(block)) // mark block as copied 95 | free = free + size(free) 96 | } 97 | next(block) // return forwarding address, which is the address of block on the toSpace 98 | } 99 | } 100 | 101 | ``` 102 | 103 | 104 | - Allocation: O(1) 105 | - GC: O(|reachable space|) 106 | - Cost per allocation depends on how full heap is 107 | 108 | ## Generational Garbage Collection: 109 | - Observation: most memory lives long or dies young 110 | - idea: 111 | - having more than two semispaces 112 | - smaller semispaces for blocks that dies young, collect frequently 113 | - larger semispaces for blocks that lives long, collect infrequently 114 | - we need to keep track of pointers between different semispaces 115 | -------------------------------------------------------------------------------- /Lecture 11(closure and tail call).md: -------------------------------------------------------------------------------- 1 | # Object and State 2 | 3 | Definiiton: An `object` is a structure that contains: 4 | - data (state) 5 | - methods/procedures (behaviour) 6 | 7 | ``` Scala 8 | class Counter { 9 | var value = 0 10 | def get() = value 11 | def incrementBy(amount: Int) = { 12 | value = value + amount 13 | } 14 | } 15 | 16 | val c = new Counter 17 | c.get() // 0 18 | c.incrementBy(42) 19 | c.incrementBy(-5) 20 | c.get() // 37 21 | ``` 22 | 23 | What if we chagne the `class` to a function ? 24 | ``` Scala 25 | def newCounter(): (()=>Int, Int=>Unit) = { 26 | var value = 0; 27 | def get() = value 28 | def incrementBy(amount: Int) = { 29 | value = value + amount 30 | } 31 | 32 | return (get, incremntBy) 33 | } 34 | 35 | val c2 = newCounter 36 | c2._1() // 0 37 | c2._2(42) 38 | c2._2(-5) 39 | c2._1() // 37 40 | ``` 41 | 42 | Observe that in this case the class behave bascially same as a closure 43 | 44 | Defintion: And `object` is a collection of closures (indexed by names) that close over a common environment 45 | 46 | 47 | 48 | 49 | 50 | 51 | ================================================================================== 52 | Recall, we can replace while loop using nested procedure 53 | 54 | ``` Scala 55 | def m() = { 56 | var i=0 57 | var j=0 58 | while(i<10){ 59 | i = i + 1 60 | j = j + i 61 | } 62 | i + j 63 | } 64 | 65 | def m2() = { 66 | var i=0 67 | var j=0 68 | def loop(): Unit = { 69 | if(i < 10) { 70 | i = i + 1 71 | j = j + i 72 | loop() 73 | } 74 | } 75 | loop() 76 | i+j 77 | } 78 | ``` 79 | 80 | 81 | if the number is huge, `loop` should run out of memory since it will potentially keep calling itself and eventually eats up all the stack space 82 | 83 | Potentially we could free the function frame before the function calls the next `loop`, this way the space utilized by recursion is now constant 84 | 85 | This is called _Tai call optimization_ 86 | 87 | We can only do this optimization can only be donw when a call is at the very end of a procedure 88 | 89 | Identifying tail call 90 | - a call is in tail position if it is the last thing executed before epilogue 91 | - maybe nested in if statement 92 | 93 | E.g. 94 | ``` Scala 95 | def f() = { 96 | def g() = { 97 | // g could access at f 98 | } 99 | } 100 | ``` 101 | 102 | - tail call optimiztion is not safe if it is nested in caller 103 | 104 | 105 | Assembly code of `loop` 106 | 107 | ``` 108 | evaluate arguments 109 | allocate param chunk 110 | write arguments into params 111 | JALR 112 | 113 | // epilogue 114 | Reg.savedC = savedPC 115 | Reg.fp = dynmaicLink 116 | stack.pop // frame 117 | stack.pop // params 118 | JR(31÷o) 119 | ``` 120 | 121 | 122 | We can rearrange this calling procedure 123 | 124 | ``` 125 | evluates args 126 | allocate param chunk 127 | write args into param 128 | Reg.savedPC = savedPC 129 | Reg.fp = dynmaicLink 130 | stack.pop 131 | stack.pop 132 | stack.pop 133 | allocate paramB 134 | copy paramA into paramB 135 | JR 136 | ``` -------------------------------------------------------------------------------- /Lecture 7(if-while-register allocation).md: -------------------------------------------------------------------------------- 1 | # Control structures : `If` / `While` 2 | 3 | ``` 4 | if(e1, op, e2) T else E 5 | ``` 6 | 7 | Evaluation code: 8 | ``` Scala 9 | def evaluate(e1: Expression, op: Operator, e2; Expression): Code = { 10 | withTempVar{t1 => 11 | block( 12 | evaluate(e1), 13 | "write t1 <- $3", 14 | evaluate(e2), 15 | "read $4 <- t1", 16 | "op, bne/beq", // left as exercise, branch to the else label 17 | T, 18 | beq "endLabel", 19 | Define("elseLabel"), 20 | E 21 | Define("endLabel") 22 | ) 23 | } 24 | } 25 | ``` 26 | 27 | `while` is similar to `if` 28 | 29 | 30 | # Arrays 31 | - contiguous area of memory 32 | - keep track of address at element 0 33 | - to access element `i`, add `c*i` to start address 34 | - need to read(`deref`)/write(`assignToAddr`) memory at computed address 35 | 36 | 37 | # Register Allocation 38 | - assigns registers / stack locations to variables 39 | 40 | Pipeline: 41 | 42 | `code-with-variables -(register alloc)-> code-with-registers / stack locations` 43 | 44 | ## Definiton: 45 | => A variable is `live at program point p` its value at `p` may be read later in the program 46 | 47 | ## Definition 48 | => the `live range` at a variable is the set at program points where it is live 49 | 50 | Varaibles can share a register if their `live ranges` are disjoint 51 | 52 | E.g. 53 | ``` Scala 54 | t1 = a + b // only t1 is alive at this point 55 | t2 = t1 + c // t2 is live, t1 is (no longer live) dead 56 | t3 = t2 + d // t3 is live, t1/t2 are dead 57 | t4 = t3 + e // t4 is probably live, it depnds on what comes after, t1/t2/t3 are dead 58 | ``` 59 | 60 | E.g. 61 | ``` Scala 62 | t1 = a * b // t1 is live r1 = a * b 63 | t2 = c * d // t2 is live r2 = c * d 64 | t3 = t1 + t2 // t3 is live, t1/t2 are dead r1 = r1 + r2 65 | t4 = e * f // t3/t4 are live, t1/t2 are dead r2 = e * f 66 | t5 = t3 - t4 // t3/t5 are live, t1/t2/t4 are dead r2 = r1 - r2 67 | g = t3 + t5 // t3/t5 probably are live g = r1 + r2 68 | ``` 69 | 70 | 71 | - The start of the live range is always after a write to a variable 72 | - The end of the live range is always before a read 73 | - A variable is dead if: 74 | - it is never read in the rest of the program 75 | - it will be overwritten before it is read 76 | 77 | ## Definition 78 | => `interference graph`: 79 | - vertices = variables 80 | - edge if two variables live at same time 81 | ``` 82 | t1 (r1) t2 (r2) 83 | +----------+ 84 | 85 | 86 | 87 | (r1) (r2) 88 | t3 t4 89 | +---------+ 90 | | 91 | | 92 | | 93 | + 94 | t5(r2) 95 | ``` 96 | 97 | ## Definition 98 | => a `colouring` assigns a colour(register) to each vertiex so that each edge connects different colours/registers 99 | 100 | Finding a minial clouring for an arbitrary graph is NP-hard 101 | 102 | Simple greedy tend to work well: 103 | ``` 104 | foreach vertex v { 105 | colour v with lowest-numbered color not yet used by any neighbours 106 | } 107 | ``` 108 | 109 | If graph requires more colours than registers, allocated some colors to memory locations on stack 110 | -------------------------------------------------------------------------------- /Lecture 8(procedure).md: -------------------------------------------------------------------------------- 1 | # Lecture 8: Procedure 2 | 3 | A `procedure` is an abstraction that encapsulates a reusable piece of code 4 | - the `caller` transfers control to `callee` (the procedure) by modifying `PC` 5 | - passing arguments for procedure parameters 6 | - `callee` (procedure) transfers control back to `callee` 7 | - returns a value 8 | 9 | 10 | `caller` and `callee` must agree on __`conventions`__ 11 | - where (memory/registers) arguments/return values are found 12 | - type of parameters/return value 13 | - which registers `callee` may modify 14 | - who allocates / free memory 15 | - `callee` allocates / deallocates the frame 16 | - `caller` modifies `Reg.savedPC` (caller-save) 17 | - `callee` preserves `Reg.framePtr` (callee-save) 18 | - `callee` preserves `Reg.stackPtr` (callee-save) 19 | - `callee` modifies `Reg.result` for return value (caller-save) 20 | - `callee` modifies all other registers (caller-save) 21 | - `callee` frees the parameters chunk 22 | - `caller` allocates the parameter chunk and passess its address in `Reg.allocated` 23 | 24 | ## Definition: 25 | - `dynamic link` is a pointer to the frame at the procedure that called the current procedure 26 | - `prologue/epilogue` is the block of code at beginning/end of a procedure that sets up/cleans up the frame and register 27 | 28 | ## CS 241E convention 29 | ### Caller 30 | ``` MIPS 31 | "eval args", 32 | "write to temp_vars", 33 | "stack.allocate (parameters)", 34 | "param1 = temp1", 35 | "param2 = temp2", 36 | LIS (Reg.targetPC), 37 | Use (proc), # load callee address into register 38 | JALR (Reg.targetPC) # Reg.targetPC = $8 39 | ``` 40 | 41 | ### Callee 42 | ``` MIPS 43 | // prologue 44 | Define(proc) # defining a label 45 | "Reg.savedParamPtr = Reg.allocated", 46 | "dynamicLink = Reg.framePtr", 47 | "stack.allocate (frame)", # we can use allocated register here 48 | "savePC = Reg.savedPC", # save the return address into a variable 49 | "Reg.framePtr = Reg.allocated", 50 | "paramPtr = Reg.savedParamPtr", 51 | 52 | 53 | "body", # body of the procedure code 54 | 55 | // epilogue 56 | Reg.savedPC = savedPC, # read back the savedPC value to register 57 | "Reg.framePtr = dynamicLink", 58 | "stack.pop(frame)", 59 | "stack.pop(parameters)", 60 | JR (Reg.savedPC) # Reg.savedPC = $31 61 | ``` 62 | 63 | 64 | ## Stack 65 | 66 | ### Callee' Frame 67 | ``` 68 | ______________ 69 | | size | 70 | | reserved | 71 | | vars | 72 | | temp vars | 73 | |______________| 74 | | savedPC | 75 | | dynamicLink | <- a.k.a. old frame pointer 76 | | paramPtr | 77 | |______________| 78 | | ... | 79 | | ... | 80 | ``` 81 | 82 | ### Param chunk 83 | ``` 84 | ______________ 85 | | size | 86 | | reserved | 87 | |______________| 88 | | | 89 | | parameters | 90 | | | 91 | |______________| 92 | ``` 93 | 94 | 95 | ### Caller's Frame 96 | ``` 97 | ______________ 98 | | ... | 99 | | ... | 100 | ``` 101 | 102 | 103 | ## When I call a procedure, which registers might chagne? (caller save) 104 | ## When I call a procedure, which registers may not chagne? (callee save) 105 | 106 | ``` Scala 107 | // eliminateVarAccessA5 108 | def eliminateVarAccessA5 109 | // if v is a varaible/temp variable (but not parameter) 110 | // same as the eliminateVarAccessA3 111 | // read/write v at offset in frame (Reg.framePtr) 112 | // else if it is a parameter 113 | // read paramPtr from frame (Reg.scratch) 114 | // read/wrte v in parameter chunk 115 | 116 | ``` -------------------------------------------------------------------------------- /Lecture 3(linking and reallocation).txt: -------------------------------------------------------------------------------- 1 | An * opcode * is a word that represents a machine language instruction (e.g. ADD, SUB, JR) 2 | An * assembly language * is a language for writing machine language program using opcodes instead of bits 3 | An * assembler * is a program that translates assembly language to machine language 4 | A * label * translates assembly language to machine language 5 | A * symbol table * is a map from label names to addresses 6 | A * relocation * is the process of adjusting addresses in machine language code so it can be loaded at a 7 | different memory address 8 | An * object file * contains: 9 | - machine language code 10 | - metadata describing what the labels used to be 11 | * linking * is the process of combining object files into one program/library/object file 12 | 13 | E.g. 14 | Find absolute value of $1 15 | 16 | SLT (2, 1, 0) val label = new Label("l") 17 | BEQ (2, 0, 1) SLT (2, 1, 0) 18 | SUB (1, 0, 1) beq (2, 0, label) // this is a different instruction 19 | JR (31) SUB (1, 0, 1) 20 | Define (label) 21 | JR (31)) 22 | 23 | 24 | 25 | 26 | How do we eliminate labels? 27 | - We do two pass-through of the code 28 | * First pass to find all the address of define 29 | * Second to resolve all the label lookup 30 | 31 | 32 | 33 | E.g. Calling a precedure 34 | 35 | 0 ... // main program 36 | 4 LIS (7) 37 | 8 100 38 | 12 JALR(7) 39 | 16 ... 40 | . 41 | . 42 | . 43 | 100 ... // precedure 44 | ... 45 | JR (31) 46 | 47 | 48 | However, this is not very modular. In this case, we can use labels. 49 | 50 | 0 ... // main program 51 | 4 LIS (7) 52 | 8 USE (procedure) 53 | 12 JALR(7) 54 | 16 ... 55 | . 56 | . 57 | . 58 | Define (procedure) 59 | 100 ... // precedure 60 | ... 61 | JR (31) 62 | 63 | 64 | assembly lang. 65 | _ _ _ 66 | |_| --------> |_| ---------------> |_| 67 | a.s assembler a.o 68 | ^ ^ 69 | assembly file object file 70 | 71 | 72 | file_1.asm 73 | 74 | Define (a): 75 | lis $1 76 | use(b) 77 | jalr $1 78 | jr $31 79 | 80 | Define (b): 81 | jr $31 82 | 83 | 84 | After assembling 85 | 86 | 0 lis $1 87 | 4 16 88 | 8 jalr $1 89 | 12 jr $31 90 | 16 jr $31 91 | metadata: 92 | export label a = 0 93 | export label b = 16 94 | address at 4 is b 95 | 96 | 97 | 98 | file_2.asm 99 | 100 | lis $1 101 | use (a) 102 | jalr $1 103 | jr $31 104 | 105 | 106 | After assembling (object file) 107 | 108 | 0 lis $1 109 | 4 0 <- we do not know what to put at this line at this point 110 | 8 jalr $1 111 | 12 jr $31 112 | metadata: 113 | use imported label a at 4 114 | 115 | 116 | 117 | 118 | 119 | Then we link file_1.o and file_2.o 120 | 121 | 0 lis $1 122 | 4 ? 123 | 8 jalr $1 124 | 12 jr $31 125 | 16 lis $1 126 | 20 16 127 | 24 jalr $1 128 | 28 jr $31 129 | 32 jr $31 130 | 131 | 132 | 133 | But observe that address at 4 and 20 are wrong, since now we have combined two files together 134 | Therefore, we need to perform * relocation * 135 | 136 | file_1.asm 137 | 138 | 0 lis $1 139 | 4 16 140 | 8 jalr $1 141 | 12 jr $31 142 | 16 jr $31 143 | metadata: 144 | export label a = 16 145 | export label b = 32 146 | address at 20 is b 147 | 148 | 149 | file_1.o + file_2.o 150 | 151 | 0 lis $1 152 | 4 16 153 | 8 jalr $1 154 | 12 jr $31 155 | 16 lis $1 156 | 20 32 157 | 24 jalr $1 158 | 28 jr $31 159 | 32 jr $31 160 | 161 | 162 | 163 | Note that linker only works with machine language file rather than assembly file 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /Final/Lambda.scala: -------------------------------------------------------------------------------- 1 | object Lambda { 2 | abstract class Expr { 3 | override def toString = prettyPrint(this) 4 | def @@(arg: Expr): Expr = Apply(this, arg) 5 | } 6 | case class Var(name: Symbol) extends Expr 7 | case class Apply(fun: Expr, arg: Expr) extends Expr 8 | case class λ(name: Symbol, body: Expr) extends Expr 9 | implicit def toVar(name: Symbol): Expr = Var(name) 10 | 11 | def prettyPrint(expr: Expr, followedByStuff: Boolean = false): String = expr match { 12 | case λ(name, e2) if followedByStuff => s"(λ${name.name}.$e2)" 13 | case λ(name, e2) => s"λ${name.name}.$e2" 14 | case Apply(e1, e2: Apply) => prettyPrint(e1, true) + s" ($e2)" 15 | case Apply(e1, e2) => prettyPrint(e1, true) + " " + prettyPrint(e2, followedByStuff) 16 | case Var(name) => s"${name.name}" 17 | } 18 | 19 | val step: PartialFunction[Expr, Expr] = { 20 | case Apply(λ(name, body), arg) => subst(body, name, arg) 21 | case Apply(fun, arg) if step.isDefinedAt(fun) => Apply(step(fun), arg) 22 | case Apply(fun, arg) if step.isDefinedAt(arg) => Apply(fun, step(arg)) 23 | case λ(name, body) if step.isDefinedAt(body) => λ(name, step(body)) 24 | } 25 | 26 | def subst(expr: Expr, name: Symbol, replacement: Expr): Expr = 27 | expr match { 28 | case Var(name2) => if(name2 == name) replacement else expr 29 | case Apply(fun, arg) => 30 | Apply(subst(fun, name, replacement), subst(arg, name, replacement)) 31 | case λ(name2, body) => 32 | val newName = uniqueName(name2, freeVariables(replacement) + name) 33 | val newBody = subst(body, name2, Var(newName)) 34 | λ(newName, subst(newBody, name, replacement)) 35 | } 36 | 37 | def uniqueName(name: Symbol, badNames: Set[Symbol]): Symbol = 38 | if(badNames.contains(name)) uniqueName(Symbol(name.name + "'"), badNames) 39 | else name 40 | 41 | def freeVariables(expr: Expr): Set[Symbol] = expr match { 42 | case Var(name) => Set(name) 43 | case Apply(fun, arg) => freeVariables(fun) ++ freeVariables(arg) 44 | case λ(name, body) => freeVariables(body) - name 45 | } 46 | 47 | def reduce(e: Expr): Expr = 48 | if(step.isDefinedAt(e)) reduce(step(e)) 49 | else e 50 | 51 | def main(args: Array[String]): Unit = { 52 | val ID = λ('x, 'x) 53 | val FIRST = λ('x, λ('y, 'x)) 54 | step(step(Apply(Apply(FIRST, 'zz), 'zzz))) 55 | println(FIRST @@ 'zz @@ 'zzz) 56 | println(reduce(FIRST @@ 'zz @@ 'zzz)) 57 | 58 | val TRUE = FIRST 59 | val IF = λ('c, λ('t, λ('e, 'c @@ 't @@ 'e))) 60 | val FALSE = λ('x, λ('y, 'y)) 61 | println(reduce(IF @@ TRUE @@ 'then @@ 'else)) 62 | println(reduce(IF @@ FALSE @@ 'then @@ 'else)) 63 | val ONE = λ('f, λ('x, 'f @@ 'x)) 64 | val TWO = λ('f, λ('x, 'f @@ ('f @@ 'x))) 65 | val ZERO = λ('f, λ('x, 'x)) 66 | val SUCC = λ('n, λ('f, λ('x, 'n @@ 'f @@ ('f @@ 'x)))) 67 | println(ONE) 68 | println(TWO) 69 | println(reduce(SUCC @@ TWO)) 70 | def NUM(n: Int): Expr = if(n==0) ZERO else reduce(SUCC @@ NUM(n-1)) 71 | println(NUM(6)) 72 | val PLUS = λ('m, λ('n, λ('f, λ('x, 'm @@ 'f @@ ('n @@ 'f @@ 'x))))) 73 | println(reduce(PLUS @@ TWO @@ NUM(3))) 74 | val TIMES = λ('m, λ('n, λ('f, 'n @@ ('m @@ 'f)))) 75 | println(reduce(TIMES @@ TWO @@ NUM(3))) 76 | val PAIR = λ('car, λ('cdr, λ('select, 'select @@ 'car @@ 'cdr))) 77 | val CAR = λ('pair, 'pair @@ TRUE) 78 | val CDR = λ('pair, 'pair @@ FALSE) 79 | val AB = PAIR @@ 'a @@ 'b 80 | println(reduce(CAR @@ AB)) 81 | println(reduce(CDR @@ AB)) 82 | 83 | // given pair (a,b), return (b, b+1) 84 | val INCPAIR = λ('pair, PAIR @@ (CDR @@ 'pair) @@ (SUCC @@ (CDR @@ 'pair))) 85 | val ZERO_ZERO = reduce(PAIR @@ ZERO @@ ZERO) 86 | val ZERO_ONE = reduce(INCPAIR @@ ZERO_ZERO) 87 | val ONE_TWO = reduce(INCPAIR @@ ZERO_ONE) 88 | val PRED = λ('num, CAR @@ ('num @@ INCPAIR @@ ZERO_ZERO)) 89 | println(reduce(PRED @@ NUM(5))) 90 | val ISZERO = λ('num, 'num @@ λ('x, FALSE) @@ TRUE) 91 | 92 | val MKFACTORIAL = λ('factorial, λ('num, 93 | IF @@ (ISZERO @@ 'num) @@ ONE @@ 94 | (TIMES @@ 'num @@ ('factorial @@ (PRED @@ 'num))))) 95 | 96 | val F4 = MKFACTORIAL @@ (MKFACTORIAL @@ (MKFACTORIAL @@ (MKFACTORIAL @@ ID))) 97 | println(reduce(F4 @@ NUM(2))) 98 | println(reduce(F4 @@ NUM(3))) 99 | println(reduce(F4 @@ NUM(4))) 100 | 101 | 102 | val Y = λ('f, λ('x, 'f @@ ('x @@ 'x)) @@ λ('x, 'f @@ ('x @@ 'x))) 103 | println(Y) 104 | val Yg = Y @@ 'g 105 | println(step(Yg)) 106 | println(step(step(Yg))) 107 | println(step(step(step(Yg)))) 108 | println(step(step(step(step(Yg))))) 109 | 110 | val FACTORIAL = Y @@ MKFACTORIAL 111 | println(reduce(FACTORIAL @@ NUM(4))) 112 | println(reduce(FACTORIAL @@ NUM(5))) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Lecture 15 (Code Generation and Memory Management).md: -------------------------------------------------------------------------------- 1 | # Code Generation: 2 | 3 | we go from `parse tree (scope and types)` to `code tree` 4 | 5 | ### Definition: a *syntax-directed translation* generates code for each node of a tree by combining the code that it generated for each subtree 6 | 7 | "yeah this is easy just go figure it out yourself" - Ondrej 8 | 9 | 10 | 11 | # Heap (memory management) 12 | 13 | ### Definition: a *heap* is some data structure for managing memory manages memory so it can be allocated and freed (returned) at any time 14 | 15 | ## Operations: 16 | - `new` / `malloc` - allocate new block of memory of given size 17 | - `free` / `delete` - returns memory to allocation pool (to be resued) 18 | 19 | ## To implement an heap 20 | - easy allocation: increment a pointer 21 | - deallocate?: we need a data structure -> add header to block/chunks 22 | 23 | ``` 24 | New Chunk 25 | _________ 26 | | size | <- header 27 | | next | 28 | |---------| 29 | | data | 30 | |_________| 31 | 32 | E.g. 33 | 34 | ________ 35 | 100 | size 8 | <- heap start 36 | 104 |next 108| 37 | ---------- 38 | 108 | size 48| 39 | 112 |next 156| <- points pass heap means the last chunk in the heap 40 | 116 | | 41 | 120 | | 42 | 124 | | 43 | 128 | | 44 | 132 | | 45 | 136 | | 46 | 140 | | 47 | 144 | | 48 | 148 | | 49 | 152 | | 50 | 156 | | 51 | 52 | ``` 53 | 54 | 55 | To implement such heap: 56 | 57 | ``` Scala 58 | 59 | def setSize(block, size) = assignToAddr(block, size) 60 | def setNext(block, size) = assignToAddr(block, size) 61 | def next(block) = ??? 62 | def size(block) = ??? 63 | 64 | def init() = { 65 | val block = heapStart + 8 66 | setSize(heapStart, 8) 67 | setSize(block, heapStart - 8) 68 | setNext(heapStart, block) 69 | setNext(block, heapStart + heapSize) 70 | } 71 | 72 | def malloc(wanted) = { 73 | 74 | def find(previous) = { 75 | 76 | val current = next(previous) 77 | if(size(current) < wanted + 8) { 78 | find(current) 79 | } else { 80 | if(size(current) >= wanted + 16) { 81 | // split block 82 | val newBlock = current + wanted + 8 83 | setSize(newBlock, size(current) - (wanted + 8)) 84 | setSize(current, wanted + 8) 85 | setNext(newBlock, next(current)) 86 | setNext(previous, newBlock) 87 | } else { 88 | setNext(previous, next(current)) 89 | } 90 | 91 | current 92 | } 93 | } 94 | 95 | find(heapStart) 96 | } 97 | 98 | // Ondrej: "There is one line of code still missing. Go figure it out yourself" 99 | def free(tofree) = { 100 | 101 | def find(previous) = { 102 | val current = next(previous) 103 | if(current < tofree) { 104 | find(next(previous)) 105 | } else { 106 | 107 | // merge with current 108 | if( (tofree + size(tofree)) == current && current < (heapStart + heapSize) ) { 109 | setSize(tofree, size(tofree) + size(current)) 110 | setNext(tofree, next(current)) 111 | } else { 112 | setNext(tofree, current) 113 | } 114 | 115 | 116 | // merge with previous 117 | if( (previous + size(previous)) == tofree && previous > heapStart ) { 118 | setSize(previous, size(previous) + size(tofree)) 119 | setNext(previous, next(tofree)) 120 | } else { 121 | setNext(previous, tofree) 122 | } 123 | 124 | } 125 | } 126 | 127 | find(heapStart) 128 | } 129 | 130 | ``` 131 | 132 | 133 | Efficiency: 134 | - alloc O(|heap|) 135 | - free: O(|heap|) 136 | - it could be O(1) with more bookkeeping 137 | 138 | 139 | However, we still have an issue: *Memory Fragmentation* 140 | ``` 141 | a = malloc(8) 142 | b = malloc(8) 143 | c = malloc(8) 144 | free(a) 145 | free(c) 146 | d = malloc(12) 147 | 148 | heap: 149 | _____ 150 | | a | <- 8: free 151 | | b | <- 8: used 152 | | c | <- 8: free 153 | ``` 154 | 155 | ### Definition: a heap is *fragmented* when free space is split into many small pieces 156 | 157 | ## Compaction (defragmentation) 158 | - copy all used blocks together (to beginning of heap) 159 | - updates all pointers in used block (in used blocks) to the new location 160 | - need to sound types to identify pointers in memory 161 | - in Lacs 162 | - `Variable` has a `isPointer` field 163 | - in `Chunk`s, put pointers first, record number of pointers 164 | ``` 165 | | size | 166 | | number of pointers | 167 | | pointers | 168 | | ... | 169 | | non-pointers | 170 | ``` 171 | 172 | 173 | Recall liveness of variable, we can define similar thing for memory block: 174 | 175 | ### Definition: a block is _live_ if it will be accessed in the future, it is _dead_ if it will not be accessed in the future 176 | 177 | ### Definition: a block is _reachable_ if: 178 | - its address is stored in the stack (or in a register) 179 | - or 180 | - its address is stored in some other reachable blocks in the heap 181 | 182 | 183 | - If a block is live => it is reachable, 184 | - then by contrapositiv : 185 | - unreachable => dead 186 | 187 | - Approximation: free unreachable blocks 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /Lecture 10(static link and nested procedure).md: -------------------------------------------------------------------------------- 1 | # Lecture 10: More on nesting procedure 2 | 3 | 4 | ``` Scala 5 | f() { // nesting depth: 0 6 | g() {} // nesting depth: 1 7 | h() { // nesting depth: 1 8 | k() {} // nesting depth: 2 9 | } 10 | } 11 | ``` 12 | 13 | ## Define: __static link__ is a pointer to the frame of the statically enclosing the procedure 14 | ## Define: __nesting depth__ of a procedure is the number of procedures that it is nested inside of 15 | 16 | 17 | Algorithm 18 | ``` 19 | depth(outer procedure) = 0 20 | if p nested immediately in p': 21 | then depth(p) = depth(p') + 1 22 | ``` 23 | 24 | 25 | To access a variable(`eliminateVarAccessA6`) 26 | ``` 27 | 1. n = depth(current procedure) - depth(declaring procedure of the variable) 28 | 2. follow the static link `n` times, then access variable in that frame 29 | ``` 30 | 31 | At a call site, compute static link: 32 | ``` 33 | depth(static link) = depth(callee) - 1 34 | n = depth(current procedure) - depth(static link) 35 | n = depth(current procedure) - depth(callee) + 1 36 | if n == 0: then static link = frame_pointer 37 | else: static_link = follow static link `n` times 38 | ``` 39 | 40 | 41 | ## Examples: 42 | `f` calls `g` : 43 | ``` 44 | depth(f) = 0 45 | depth(g) = 1 46 | n = 0 - 1 + 1 = 0 47 | static link = frame pointer 48 | ``` 49 | 50 | 51 | `g` calls `h` 52 | ``` 53 | depth(g) = 1 54 | depth(h) = 1 55 | n = 1 - 1 + 1 = 1 56 | follow g's static link once, 57 | static link = g's static link 58 | ``` 59 | 60 | 61 | 62 | `k` calls `g` 63 | ``` 64 | depth(k) = 2 65 | depth(g) = 1 66 | n = 2 - 1 + 1 = 2 67 | follow k's static link once, 68 | follow h's static link once, 69 | static link = h's static link 70 | ``` 71 | 72 | 73 | `f` calls `k` 74 | ``` 75 | illegal, because `k` is local to `g` 76 | n = -1 77 | ``` 78 | 79 | 80 | 81 | 82 | # First-class functions 83 | ## A.k.a. __Code is data__ / function values 84 | 85 | in Scala: 86 | ``` Scala 87 | def procedure(x: Int) = x + 1 88 | var increase: (Int)=>(Int) = procedure 89 | 90 | increase(5) // 6 91 | 92 | increase = { x => x + 2 } 93 | 94 | increase(5) // 7 95 | 96 | List(5,6,7).map(increase) // 7,8,9 97 | 98 | def twice(fun: Int=>Int): Int=>Int = { 99 | x => (fun(fun(x))) 100 | } 101 | 102 | increase = twice(increase) 103 | 104 | increase(5) // 9 105 | 106 | increase = { x => x + increment } // compile-error 107 | ``` 108 | 109 | ## A `Free Variable` in an expression is a variable that is not defined within the expression 110 | 111 | E.g. 112 | - `x` is free in `x + increment` 113 | - `x` is not free in `{ x => x + increment }` 114 | 115 | 116 | ## An expression is `closed` if it contains no free variable 117 | 118 | ``` Scala 119 | def increaseBy(increment: Int): Int=>Int = { x => x + increment } 120 | 121 | increase = increaseBy(3) 122 | 123 | increase(5) // 8 124 | 125 | List(increaseBy(1), increaseBy(2), increaseBy(3)) 126 | .map(fun => fun(5)) 127 | // List(6, 7, 8) 128 | 129 | ``` 130 | 131 | ## A `closure` is a pair of 132 | - the code of a function body 133 | - the code label 134 | - and `environment` for the free variables in the function body 135 | - frame at enclosing proceure 136 | - which is basically the static link 137 | 138 | ``` Scala 139 | var functionValue: (Int=>Int) 140 | def constuctor(n: Int): (Int=>Int) = { 141 | var b = a * 2 142 | def procedure(c: Int): Int = {a + b + c} 143 | procedure // closure creation 144 | } 145 | 146 | functionValue = constructor(42) 147 | // ^~~~~~~~~~~~~~ 148 | // Call(...), `constructor` is a Procedure(...) 149 | functionValue(5) 150 | // ^~~~~~~~~~~~~ 151 | // CallClosure(...) 152 | // essentially we are calling constructor(42)(5) 153 | // target is Code that eventually evaluates to a Closure 154 | // ^~~~ 155 | // code that reads variable functionValue() 156 | ``` 157 | 158 | `functionValue` must store: 159 | - label at procedure code 160 | - static link 161 | 162 | ## At closure creation, compute static link just like in a normal call 163 | ## At closure call, pass the environment from closure as the static link 164 | 165 | 166 | ## Question: What is the _Extend_ of `a` and `b` ? 167 | - begins at the beginning of the `constructor` 168 | - we *CANNOT* allocate frame of `constructor` on the stack 169 | - because we need to access `constructor`'s local vars after the call returns 170 | - ends when all copies of the closure values have been lost or overwritten 171 | - we can just heap in this case 172 | 173 | ## A `heap` is a data structure that manages memory, so it can be allocated / freed at any time 174 | (Assignment 11: implementing a real heap) 175 | 176 | (Assignment 6: simplified heap, allocates and never frees ) 177 | 178 | ## Question: Which procedure on heap? 179 | - a closure can access frames of all procedures in which it is nested 180 | - that means these frames must be on heap 181 | - frame of a procedure that contains p (a procedure) anywhere inside it must be on heap if we ever make closure out of p 182 | 183 | 184 | // ignore this 185 | ``` Scala 186 | // scala version 187 | def increaseBy2(increment: Int) :(Int)=>Int = { 188 | def procedure(x: Int) = { x + increment } 189 | procedure 190 | } 191 | 192 | def main(a: Int, b: Int) = (increaseBy2(a))(b) 193 | 194 | main(3, 5) // 8 195 | 196 | // lacs version 197 | def v(variable: Variable): Code = read(Reg.result, variable) 198 | 199 | val increment = new Variable("inrement") 200 | val increaseBy = new Procedure("increaseBy", Seq(increment), Seq()) 201 | 202 | val x = new Variable("x") 203 | val procedure = new Procedure("procedure", Seq(x), Seq(), Some(increaseBy)) 204 | // ^~~~~~~ 205 | // this is nested within the `increaseBy` procedure 206 | 207 | procedure.code = binOp(v(x), plus, v(increment)) 208 | increaseBy.code = Closure(procedure) 209 | 210 | val main = new Procedure("main", Seq(a, b), Seq()) 211 | val parameter = new Variable("parameter") 212 | main.code = CallClosure(call(increaseBy, v(a)), Seq(v(b), Seq(parameter)) 213 | // ^~~ ^~~~~~~~~~~~~~ 214 | // closure call param means closure takes one param 215 | 216 | val machineCode = compilerA6(Seq(main, increaseBy, procedure)) 217 | 218 | val endState = A1.loadAndRun(machineCode.words, Word(encodeSigned(3)), Word(encodeSigned(5))) 219 | // Reg(3) should be 8 220 | ``` 221 | -------------------------------------------------------------------------------- /Lecture 13 (Context-free language).md: -------------------------------------------------------------------------------- 1 | # Context-Free Language 2 | 3 | e.g. 4 | 5 | Expression: `a + b * c - d` 6 | 7 | !["tag1"][tag1] 8 | 9 | In general, regular languages is insufficient for nested language structures 10 | - loop 11 | - expressions 12 | - blocks 13 | - loops 14 | - functions 15 | 16 | Hence it is impossible to write a compiler using regular language 17 | 18 | We solve this using _Context-Free language_ 19 | 20 | We define a context-free language as follow 21 | ``` 22 | expr = ID | expr op expr | ( expor ) 23 | 24 | op = + | - | * | / 25 | 26 | 27 | Example: 28 | ( a + b ) * c 29 | ^ ^ ^ 30 | | | | 31 | expr .. op 32 | | | 33 | ID ID 34 | ``` 35 | 36 | !["tag2"][tag2] 37 | 38 | 39 | ## A **context-free grammar** is a 4 tuple of where 40 | - `V` is a finite set non-terminal symbols 41 | - e.g. {expr, op} 42 | - `Σ` is a finite set (alphabet) terminal symbols 43 | - e.g. {ID, +, -, ⋆, /, (, )} 44 | - `P` production rules 45 | - e.g. { expr -> ID, expr -> expr op expor, expr -> (expr), op -> +, op -> - ...} 46 | - `S ∈ V` is the start non-terminal 47 | - e.g. { s = expr } 48 | 49 | 50 | ## Convention 51 | 52 | ``` 53 | a, b, c, d ∈ Σ 54 | A, B, C, D, S ∈ V 55 | W, X, Y, Z ∈ Σ U V 56 | w, x, y, z ∈ Σ⋆ 57 | α, β, γ ∈ (Σ U V)⋆ 58 | ``` 59 | 60 | 61 | ## DEF: α A B => α γ β if (A -> γ) ∈ P 62 | E.g. 63 | ``` 64 | expr => expr op expr => ID op expr 65 | ``` 66 | 67 | 68 | ## DEF: α1 => αn if α1 => α2 => ... αn 69 | E.g. 70 | ``` 71 | expor =>⋆ ID + ID 72 | ``` 73 | 74 | 75 | ## DEF: the _language generated by G_ = {V, Σ, P, S} is: {w ∈ Σ⋆ | s => ⋆w} 76 | 77 | ## DEF: a language `L` is context-free if there exists a grammar `G` that generates `L` 78 | 79 | ## DEF: a context-free grammar is __ambigious__ if it allows multiple parse trees for same input string 80 | E.g. 81 | 82 | !["tag3"][tag3] 83 | 84 | - to specify language precisly, we want unambigious grammar 85 | - question: How do we prove a grammar is unambigious? 86 | - observe that our previous definition of grammar is ambigious 87 | - new definition 88 | ``` 89 | expr -> term 90 | | expr + term 91 | | expr - term 92 | term -> ID | term * ID | term / ID 93 | ``` 94 | 95 | Note: There is no algorithm to tell if two different grammar generates the same langauge (but sometimes provable) 96 | 97 | 98 | # Parsing Algorithm 99 | - input: grammar `G`, string `w` 100 | - output: 101 | - boolean: Does `S` =>⋆ `w` ? 102 | - OR 103 | - generate derivation / parse tree 104 | 105 | Recall that: 106 | 107 | 108 | ## A **context-free grammar** is a 4 tuple of where 109 | - `V` is a finite set non-terminal symbols 110 | - e.g. {expr, op} 111 | - `Σ` is a finite set (alphabet) terminal symbols 112 | - e.g. {ID, +, -, ⋆, /, (, )} 113 | - `P` production rules 114 | - e.g. { expr -> ID, expr -> expr op expor, expr -> (expr), op -> +, op -> - ...} 115 | - `S ∈ V` is the start non-terminal 116 | - e.g. { s = expr } 117 | 118 | 119 | ## Convention 120 | 121 | ``` 122 | a, b, c, d ∈ Σ 123 | A, B, C, D, S ∈ V 124 | W, X, Y, Z ∈ Σ U V 125 | w, x, y, z ∈ Σ⋆ 126 | α, β, γ ∈ (Σ U V)⋆ 127 | ``` 128 | 129 | 130 | ``` Psuedo Code 131 | parse(α, x): Boolean or Option[Seq[Tree]] = // does α =>⋆ x ? 132 | { 133 | if(α.isEmpty) { 134 | if(x.isEmpty) return true // Seq() 135 | else return false 136 | } else if(α == aβ) { 137 | if(x@(a :: z) && parse(β, z)) return true // a is the terminal term of α, then parse(β, z) // a +: parse(β, z) 138 | else return false 139 | } else if(α == A) { // A 140 | foreach(A->γ ∈ P) { // /|\ 141 | if(parse(γ, x)) return true // parse(γ, x) 142 | } 143 | return false 144 | } else { // α = Aβ, β is nonempty 145 | foreach(split x = x1 x2) { // split x in all possible ways // parse(A, x1) ++ parse(β, x2) 146 | if(parse(A, x1) && parse(β, x2)) return true 147 | } 148 | return false 149 | } 150 | } 151 | ``` 152 | 153 | 154 | 155 | E.g. 156 | ``` 157 | p( expr =>⋆ ID + ID ) 158 | // expr -> ID 159 | p( ID =>⋆ ID + ID ) 160 | p( ε =>⋆ + ID ) // false 161 | 162 | // 4th case 163 | // expr => expr op expr 164 | p( expr op expr => ID + ID) 165 | 166 | // split as: ε, ID + ID 167 | // 3rd case 168 | p(expr =>⋆ ε) 169 | | // expr -> ID 170 | | p(ID =>⋆ ε) // false, ID does not derive empty string 171 | | 172 | | // expr -> expr op expr 173 | | // 4th case again 174 | | p(expr op expr =>⋆ ε) 175 | | // split ε = ε, ε 176 | | p(expr =>⋆ ε) // false (now we memorize the result this case to avoid infinite recursion ) 177 | |>>>> false 178 | 179 | // split as: ID, + ID 180 | p( expr =>⋆ ID ) 181 | 182 | // expr -> ID 183 | p( ID =>⋆ ID ) 184 | 185 | p( ε => ⋆ ε ) // true 186 | 187 | // 4th case 188 | p( op expr =>⋆ + ID) 189 | // ... 190 | // ... 191 | // ... 192 | 193 | ``` 194 | 195 | 196 | ## Runtime for parsing algorithm 197 | - infinite or expential on some grammar 198 | - what are the all possible values of `α` and `x` 199 | - `α = S` or `α` is suffix of a R.H.S of some production rule 200 | - `x` string of terminal symbols 201 | - always a substring of the input 202 | - number of possible values: 203 | - α: O(1) for any given grammar 204 | - from: O(|w|) O(size of input) 205 | - length: O(|w|) 206 | 207 | ## Memoization table: 208 | ``` Scala 209 | val memo = Map[(Seq[Symbol], Int, Int), Option[Seq[Tree]]] 210 | // alpha from length 211 | // 1. Whenever parse/recur returns, save the return value in memo table 212 | // 2. At beginning of recur, check whether memo table already has result 213 | // 3. Just before starting the computation in recur, if the result is not being set already, set memo(α, x) = None 214 | ``` 215 | 216 | ### Complexity 217 | - O(|w|^2) space 218 | - O(|w|^3) time 219 | 220 | 221 | ### To instead of return true/false, we can use the memorization table to implement construction of parse tree 222 | 223 | 224 | ## Other parsing algorithm (for context) 225 | - CYK 226 | - O(|w|^3) time O(|w|^2) space 227 | - 0.5 week 228 | - works with all grammar 229 | - no correct prefix property 230 | - Earley 231 | - O(|w|^3) for ambigious grammar 232 | - O(|w|^2) for unambigious grammar 233 | - O(|w|) for most of LR(K) grammar 234 | - 1.5 weeks 235 | - correct prefix property 236 | - LR(1) / LR(K) 237 | - O(|w|) time / space 238 | - 3 weeks in CS 444 239 | - works with unambigious practical grammars 240 | - correct prefix property 241 | - LL(1) / LL(K) 242 | - 1.5 weeks 243 | - O(|w|) time / space 244 | - few practical grammars 245 | - cannot build left-associative parse tree 246 | - e.g. `3 - 2 - 1` cannot be parsed 247 | - correct prefix property 248 | 249 | ### Definition: 250 | - Let `w` = `xaz` ∉ `L` be some incorrect string, 251 | - ∃`y` s.t. `x`, `y` ∈ `L` is a valid input 252 | - ∀`v` `x``a``v` ∉ `L` 253 | - A parser has the __correct prefix property__ if it rejects the input when it encounters `a` 254 | 255 | 256 | ## Compiler: 257 | 258 | ``` 259 | Sequence of chars -> |scanner A7| -> |parser| -> | ??? | -> |code generation| -> machine language 260 | ^ ^ ^ 261 | tokens parse tree code tree 262 | ``` 263 | 264 | ### `???` - is the __context-sensitive analysis__ or __semantic analysis__ (A9) 265 | - rejects programs that satisfy grammar but are still wrong 266 | - computes information needed to generate code 267 | 268 | ### In Lacs 269 | - need two things 270 | 1. resolving name 271 | - map each `ID` to a specific variable or procedure 272 | - build symbol table for each scope (procedure) 273 | - detect undelcared variables / names 274 | - detect duplicate 275 | 2. compute and check types 276 | 277 | 278 | 279 | [tag1]: https://imagehosting-50cd6.firebaseapp.com/l13tag1.JPG "tag1" 280 | [tag2]: https://imagehosting-50cd6.firebaseapp.com/l13tag2.JPG "tag2" 281 | [tag3]: https://imagehosting-50cd6.firebaseapp.com/l13tag3.JPG "tag3" 282 | 283 | 284 | 285 | 286 | -------------------------------------------------------------------------------- /Lecture 12 (DFA and NFA).md: -------------------------------------------------------------------------------- 1 | # Formal Languages + DFAs + NFAs 2 | 3 | ## Question: What _is_ in the language and what _is not_ 4 | 5 | Example: a definition of a language 6 | ``` 7 | L1 = {R2D2, cat, BBQ} 8 | ``` 9 | 10 | 11 | ## `Recognition` 12 | - Given language `L` and word `w`, we ask: "is `w` in `L` ?" 13 | - Can we always create a decision algorithm 14 | - NO 15 | 16 | 17 | ## `Interpretation` / `Translation` 18 | - Given language `L` and word `w` 19 | - `w` has a meaning; that is an interpretation or translation 20 | - E.g. 21 | - `L` = C language, `w` = int, which is a type 22 | - `L` = French, `bonjour` = hello 23 | 24 | 25 | - Analysis = Recognition + Translation 26 | 27 | 28 | ## Definition: 29 | - Alphabet (Σ) 30 | - **Finite** set of symbols 31 | - Examples: 32 | - {0, 1} 33 | - {a, ..., z} 34 | - {+, -, int, ..., void, for, ...} 35 | - `ε` the empty word 36 | - A set of wrods over some alphabet 37 | - may be **infinite** 38 | - may be **empty**, {} or {ε} 39 | - Example: 40 | - all binary strings 41 | - all English words 42 | - all C++ programs 43 | 44 | ## Classes of Language 45 | - Finite (countable) 46 | - L = {alpha, R2D2, cat, dog} 47 | - L = all binary strings of 8 bits 48 | - Regular 49 | - L = all strings consisting of only the character 'x', E.g. xx, xxx, xxxx 50 | - L = {alpha, R2D2, cat, bbq} + all binary strings of 8 bits 51 | 52 | Note: All finite language are regular, but vice versa is not true 53 | 54 | - Context Free (no context is required to understand this language) 55 | - L = the language of balanced brackets, (), (()), ((())) 56 | - L = {alpha, R2D2, cat, bbq} 57 | - Undecidable 58 | - Languages that Turing machines cannot accept 59 | - CS341/CS360/5 60 | 61 | # DFAs 62 | - Deterministic finite automata 63 | - A "state machine" that decides if a word is accepted or rejected 64 | 65 | - DFA is a 5-tuple (Σ, Q, q0, A, Δ) 66 | - Σ - alphabet 67 | - Q - finite set of states 68 | - q0 ∈ Q - a start state 69 | - A ⊆ Q - accepting state 70 | - Δ: Q x Σ -> Q - transition function 71 | 72 | !["DFA"][page1] 73 | 74 | 75 | ## Intersection of two DFAs 76 | - Let P be the state of DFA1 and R be the states of DFA2 77 | - DFA1 intersect DFA2 = 78 | - Q = set of all pairs of states from P and R 79 | - q0 = pair of corresponding to start state of P and R 80 | - A = state-pairs where both are accepting 81 | - Δ(q, a) = q' if and only if Δ(p, a) = p' and Δ(q, a) = q' 82 | - Example: 83 | 84 | !["Intersection of DFA"][page4] 85 | 86 | 87 | 88 | ## DEF Recognition algorithm: 89 | - Δ⋆: Q x Σ⋆ -> Q - extended transition function 90 | - Δ⋆(q, ε) = q 91 | - Δ⋆(q, head::tail) = Δ⋆(Δ(q, head), tail) 92 | - Example: 93 | - Δ⋆(q, 01) = Δ⋆(Δ(q, 0), 1) 94 | 95 | - A word `w` is accepted by the DFA is Δ⋆(q0, w) ∈ A 96 | 97 | 98 | # NFAs 99 | - Non-determnistic finite automata 100 | - state machine where there can be more than one transition out of a state on the same symbol, hence, non-deterministic 101 | - Accepts words if there exists any path to accepting state 102 | 103 | - NFS is a 5-tuple (Σ, Q, q0, A, Δ) 104 | - Σ - alphabet 105 | - Q - finite set of states 106 | - q0 ∈ Q - a start state 107 | - A ⊆ Q - accepting state 108 | - Δ: Q x Σ -> 2^Q - transition functions 109 | - 2^Q - set of all states 110 | 111 | - A words `w` is accepted by the NFA if Δ⋆(q0, w) intersects `A` is not ϕ 112 | - At least ONE of the final states is accepting 113 | - Recognition algorithm: 114 | - Δ⋆: Q x Σ⋆ -> 2^Q - extended transition function 115 | - Δ⋆(q, ε) = {q} 116 | - Δ⋆(q, head::tail) = Uq ∈ Δ(q, head), Δ⋆(q', tail) 117 | - Example 118 | - Δ⋆(A, 0xF) = Δ⋆(Δ(B, 0), xF) U Δ⋆(Δ(E, 0)) 119 | 120 | 121 | !["NFA"][page2] 122 | 123 | ## ε transition 124 | - `ε-NFA` 125 | - NFA that has transition on nothing 126 | - Move from state q -> q' without consuming input 127 | - Why? 128 | 129 | !["ε-NFA"][page3] 130 | 131 | 132 | - Note that, every ε-NFA can be converted into a NFA and every NFA can be converted into DFA 133 | 134 | 135 | 136 | ## Regular Expression 137 | - Alternate (to DFA) method for specifying regular language 138 | - Used everywhere 139 | 140 | 141 | Rules: 142 | - R ::= ε 143 | - L(ε) = {ε} 144 | - empty state 145 | - R ::= a ∈ Σ 146 | - L(a) = {a} 147 | - letters from alphabet 148 | - R ::= R1 | R2 149 | - L(R1|R2) = L(R1) U L(R2) 150 | - or 151 | - R ::=R1 R2 152 | - L(R1 R2) = {xy | x ∈ L(R1), y ∈ L(R2)} 153 | - concat 154 | - R ::= R⋆ 155 | - L(R⋆) = {x1x2x3 ... xn | ∀ i xi ∈ L(R), n >= 0} 156 | - repetition 157 | 158 | E.g.: 159 | - (ab)⋆ = {ε, ab, abab, ........} 160 | - a+ = a(a)⋆ 161 | 162 | 163 | ## Kleene's Theorem 164 | Given a regular language L, then there exists 165 | - A DFA specifying L 166 | - A regular expression specifying L 167 | - An NFA specifying L 168 | - An ε-NFA specifying L 169 | 170 | That means that there exists ε-NFA can be converted into an NFA and an NFA can be converted into a DFA 171 | 172 | !["ε-NFA to DFA"][page5] 173 | 174 | ## Example: 175 | 1. The language of all English words ending in "ing" 176 | - `[a-zA-Z]*(ing)` 177 | 2. The language of all English words containing "issi" 178 | - `[a-zA-Z]*(issi[A-Za-z]*)*` 179 | 3. The language of all binary strings with even-length runs of 0s and even-length runs of 1s 180 | - `((11)*(00)*)*` 181 | 4. The union of (1) and (2) 182 | - `[a-zA-Z]*(issi[A-Za-z]*)*ing` 183 | 184 | 185 | ## Recognition vs Scanning 186 | ### Recognition 187 | - Is word `w` in language `L` 188 | 189 | ### Scanning 190 | - Given a string, divide into tokens that are in language `L` 191 | - Examples 192 | - Split sentence "R2D2 bbqs lizards on Dagobah" into "words" 193 | - {R2D2, bbqs, lizards, on, Dagobah} 194 | - Split C++ program "int main() {return 0; }" into "words" 195 | - {int, main, (, ), {, return, 0, ;, }} 196 | - Split string "0xDEADBEEF 42 0xABBA" into "words" 197 | - {0xDEADBEEF, 42, 0xABBA} 198 | 199 | 200 | ### Scanning - Maximal Munch 201 | - *Input*: string `w`, language `L` (specifies set of valid tokens) 202 | - *Output*: sequence of words, `w1`, `w2`, ..., `wn` such that: 203 | - `w` = `w1w2w2` ... `wn` 204 | - `wi` ∈ `L` for all `i` 205 | - For each `i`, `wi` is the longest prefix of `wi`, `wi+1` ... `wn` that is in `L` 206 | - or ERROR 207 | - Question: is it always possible that : 208 | - Let `L` be the language of possible tokens 209 | - `L*` is the language of words that can be scanned 210 | - Possible to scan a string `w` if and only if `w` ∈ `L*` 211 | 212 | !["idk what she was talking about"][page6] 213 | 214 | - Question: is scanning output always unique? 215 | - consider L = {aa, aaa} and w = {aaaaa} 216 | - do we output {aaa, aa} or {aa, aaa} ? 217 | - ε-NFA: 218 | 219 | !["i think i was checking tinder when she was talking about this"][page7] 220 | 221 | - There is no way of knowing that the user intent was, 222 | - Scanning output is not unique 223 | - maximal munch choose {aaa, aa} to ensure unique tokenization 224 | - but no necessarily the intended one 225 | - E.g. if w = {aaaa} 226 | - then the output with maximal munch is {aaa} follow by an error 227 | - It gives unique solution 228 | - but it cannot always find valid solution even if it exists 229 | 230 | 231 | ### Scanning - Extended Maximal Munch 232 | - *Input*: string `w`, language `L` (specifies set of valid tokens) 233 | - *Output*: sequence of words, `w1`, `w2`, ..., `wn` such that: 234 | - `w` = `w1w2w2` ... `wn` 235 | - `wi` ∈ `L` for all `i` 236 | - For each `i`, `wi` is the longest prefix of `wi`, `wi+1` ... `wn` that is in `L` 237 | - or ERROR 238 | - Use DFA for `L` to recongnize `w`; at the first state where there is no possible transition out (i.e. it gets stuck attempting to recongnize `w`) 239 | 1. if in an *non-accepting state* back track to last-visited accepting state 240 | 1. if *NO accepting state*s have been visited, ERROR - there is no token 241 | 2. Otherwise, output token of that accepting state and *RESET DFA* to start state 242 | 2. If in an accepting state, output token of that accepting state and *RESET DFA* to start state 243 | - However extended maximal munch is also not perfect 244 | 245 | - Example 246 | ``` C 247 | int i = 4, j = 5; 248 | i +++ j; 249 | // is this: (i++) + j = 9 or i + (++j) = 10 ? 250 | // maxiaml munch returns the first result 251 | 252 | i +++++ j; 253 | // we know this should be (i++) + (++j) 254 | // maximal much tokenizes as {++, ++, +} which prodcues an ERROR in C 255 | ``` 256 | 257 | ``` C++ 258 | vector> v; // (pre-C++11) 259 | // ^~ 260 | // error in C++11 261 | // maximal much scans as {vector, <, vector, <, int, >>} 262 | ``` 263 | 264 | 265 | [page1]: https://imagehosting-50cd6.firebaseapp.com/IMG_1166.JPG "page 1" 266 | [page2]: https://imagehosting-50cd6.firebaseapp.com/IMG_1167.JPG "page 2" 267 | [page3]: https://imagehosting-50cd6.firebaseapp.com/IMG_1168.JPG "page 3" 268 | [page4]: https://imagehosting-50cd6.firebaseapp.com/IMG_1173.JPG "page 4" 269 | [page5]: https://imagehosting-50cd6.firebaseapp.com/IMG_1174.JPG "page 5" 270 | [page6]: https://imagehosting-50cd6.firebaseapp.com/IMG_1175.JPG "page 6" 271 | [page7]: https://imagehosting-50cd6.firebaseapp.com/IMG_1176.JPG "page 7" 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | --------------------------------------------------------------------------------