Commit a95f99cf authored by Matthieu Moy's avatar Matthieu Moy
Browse files

Lab 4 online

parent 7ed2cadc
......@@ -11,3 +11,4 @@
/TP*/**/*.dot.pdf
/MiniC-stripped.g4
.coverage
htmlcov/
......@@ -9,6 +9,10 @@ ifdef TEST_FILES
export TEST_FILES
endif
ifdef SSA
export ENABLE_SSA=1
endif
PYTEST_BASE_OPTS=-vv -rs --failed-first --cov="$(PWD)" --cov-report=term --cov-report=html
ifndef ANTLR4
......@@ -26,8 +30,8 @@ main-deps: MiniCLexer.py MiniCParser.py TP03/MiniCInterpretVisitor.py TP03/MiniC
.PHONY: tests tests-interpret tests-codegen tests-regalloc clean clean-tests tar
tests: tests-pyright tests-interpret
tests: tests-pyright tests-interpret tests-codegen tests-regalloc
tests-pyright: antlr
pyright .
......
#! /usr/bin/env python3
"""
Code generation lab, main file. Code Generation with Smart IRs.
Usage:
python3 MiniCC.py <filename>
python3 MiniCC.py --help
"""
import traceback
from MiniCLexer import MiniCLexer
from MiniCParser import MiniCParser
from TP04.MiniCCodeGen3AVisitor import MiniCCodeGen3AVisitor
from TP03.MiniCTypingVisitor import MiniCTypingVisitor, MiniCTypeError
from Errors import MiniCUnsupportedError, MiniCInternalError, AllocationError
from TP04.SimpleAllocations import (
NaiveAllocator, AllInMemAllocator
)
try: # Common part for TP05 and TP05a
from TP05.CFG import CFG # type: ignore[import]
except ModuleNotFoundError:
pass
try: # Common part for TP05 and TP05b
from TP05.SmartAllocation import SmartAllocator # type: ignore[import]
except ModuleNotFoundError:
pass
try: # Liveness for TP05 (M1IF08)
from TP05.LivenessDataFlow import LivenessDataFlow # type: ignore[import]
except ModuleNotFoundError:
pass
try: # SSA for TP05a (CAP)
from TP05.SSA import (enter_ssa, exit_ssa) # type: ignore[import]
except ModuleNotFoundError:
pass
try: # Liveness for TP05b (CAP)
from TP05.LivenessSSA import LivenessSSA # type: ignore[import]
except ModuleNotFoundError:
pass
import argparse
from antlr4 import FileStream, CommonTokenStream
from antlr4.error.ErrorListener import ErrorListener
import os
import sys
class CountErrorListener(ErrorListener):
"""Count number of errors.
Parser provides getNumberOfSyntaxErrors(), but the Lexer
apparently doesn't provide an easy way to know if an error occurred
after the fact. Do the counting ourserves with a listener.
"""
def __init__(self):
super(CountErrorListener, self).__init__()
self.count = 0
def syntaxError(self, recognizer, offending_symbol, line, column, msg, e):
self.count += 1
def main(inputname, reg_alloc, enable_ssa=False,
typecheck=True, typecheck_only=False, stdout=False, output_name=None, debug=False,
debug_graphs=False, ssa_graphs=False):
(basename, rest) = os.path.splitext(inputname)
if not typecheck_only:
if stdout:
output_name = None
print("Code will be generated on standard output")
elif output_name is None:
output_name = basename + ".s"
print("Code will be generated in file " + output_name)
input_s = FileStream(inputname, encoding='utf-8')
lexer = MiniCLexer(input_s)
counter = CountErrorListener()
lexer._listeners.append(counter)
stream = CommonTokenStream(lexer)
parser = MiniCParser(stream)
parser._listeners.append(counter)
tree = parser.prog()
if counter.count > 0:
exit(3) # Syntax or lexicography errors occurred, don't try to go further.
if typecheck:
typing_visitor = MiniCTypingVisitor()
try:
typing_visitor.visit(tree)
except MiniCTypeError as e:
print(e.args[0])
exit(2)
if typecheck_only:
if debug:
print("Not running code generation because of --typecheck-only.")
return
# Codegen 3@ CFG Visitor, first argument is debug mode
visitor3 = MiniCCodeGen3AVisitor(debug, parser)
# dump generated code on stdout or file.
with open(output_name, 'w') if output_name else sys.stdout as output:
visitor3.visit(tree)
for function in visitor3.get_functions():
# Allocation part
cfg = CFG(function)
if debug_graphs:
s = "{}.{}.dot".format(basename, cfg._name)
print("Output", s)
cfg.print_dot(s)
if enable_ssa:
DF = enter_ssa(cfg, basename, debug, ssa_graphs)
if ssa_graphs:
s = "{}.{}.ssa.dot".format(basename, cfg._name)
print("Output", s)
cfg.print_dot(s, DF, True)
allocator = None
if reg_alloc == "naive":
allocator = NaiveAllocator(cfg)
comment = "naive allocation"
elif reg_alloc == "all_in_mem":
allocator = AllInMemAllocator(cfg)
comment = "all-in-memory allocation"
elif reg_alloc == "smart":
liveness = None
if enable_ssa:
try:
liveness = LivenessSSA(cfg, debug=debug)
except NameError:
form = "CFG in SSA form"
raise ValueError("Invalid dataflow form: \
liveness file not found for {}.".format(form))
else:
try:
liveness = LivenessDataFlow(cfg, debug=debug)
except NameError:
form = "CFG not in SSA form"
raise ValueError("Invalid dataflow form: \
liveness file not found for {}.".format(form))
allocator = SmartAllocator(cfg, basename, liveness,
debug, debug_graphs)
comment = "smart allocation with graph coloring"
elif reg_alloc == "none":
comment = "non executable 3-Address instructions"
else:
raise ValueError("Invalid allocation strategy:" + reg_alloc)
if allocator:
allocator.prepare()
if enable_ssa:
exit_ssa(cfg)
comment += " with SSA"
if allocator:
allocator.rewriteCode(cfg)
if enable_ssa and ssa_graphs:
s = "{}.{}.exitssa.dot".format(basename, cfg._name)
print("Output", s)
cfg.print_dot(s, view=True)
cfg.print_code(output, comment=comment)
if debug:
visitor3.printSymbolTable()
# command line management
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Generate code for .c file')
parser.add_argument('filename', type=str,
help='Source file.')
parser.add_argument('--reg-alloc', type=str,
choices=['none', 'naive', 'all_in_mem', 'smart'],
help='Allocation to perform')
parser.add_argument('--ssa', action='store_true',
default=False,
help='Enable SSA form')
parser.add_argument('--stdout', action='store_true',
help='Generate code to stdout')
parser.add_argument('--debug', action='store_true',
default=False,
help='Emit verbose debug output')
parser.add_argument('--graphs', action='store_true',
default=False,
help='Display graphs (CFG, conflict graph).')
parser.add_argument('--ssa-graphs', action='store_true',
default=False,
help='Display SSA graphs (DT, DF).')
parser.add_argument('--disable-typecheck', action='store_true',
default=False,
help="Don't run the typechecker before generating code")
parser.add_argument('--typecheck-only', action='store_true',
default=False,
help="Run only the typechecker, don't try generating code.")
parser.add_argument('--output', type=str,
help='Generate code to outfile')
args = parser.parse_args()
if args.reg_alloc is None and not args.typecheck_only:
print("error: the following arguments is required: --reg-alloc")
exit(1)
try:
main(args.filename, args.reg_alloc, args.ssa,
not args.disable_typecheck, args.typecheck_only,
args.stdout, args.output, args.debug,
args.graphs, args.ssa_graphs)
except MiniCUnsupportedError as e:
print(e)
exit(5)
except (MiniCInternalError, AllocationError):
traceback.print_exc()
exit(4)
# MiniC Compiler
LAB5a (Control Flow Graph in SSA Form) & LAB5b (Smart Register Allocation), CAP 2021-22
# Authors
YOUR NAME HERE
# Contents
TODO:
- Did you solve (partly or entirely) the bonus of lab5a? If yes, explain your ideas for question 3.
- Explain any design choices you may have made.
- Do not forget to remove all debug traces from your code!
# Howto
## For lab5a:
`python3 MiniCC.py --reg-alloc none --ssa --ssa-graphs TP05/tests/provided/dataflow/df00.c`:
Launch the compiler and obtain a RISCV code with temp.
`python3 MiniCC.py --reg-alloc all_in_mem --ssa --ssa-graphs TP05/tests/provided/dataflow/df00.c`:
Launch the compiler and obtain an executable RISCV code.
Both generate:
- `TP05/tests/provided/dataflow/df00.main.ssa.DT.dot` the domination tree of the original CFG ;
- `TP05/tests/provided/dataflow/df00.main.ssa.dot` the CFG in SSA form, and its dominance frontiers ;
- `TP05/tests/provided/dataflow/df00.main.exitssa.dot` the CFG after exiting SSA form;
To run the test suite, type `make tests-notsmart` (exercise 4), or `make tests-notsmart SSA=1` (once
you have fully implemented SSA).
## For lab5b:
`python3 MiniCC.py --reg-alloc smart --ssa TP05/tests/provided/dataflow/df04.c`:
Launch the compiler and obtain an executable RISCV code, using SSA and the smart allocator.
Options `--debug`, `--graphs` and `--ssa-graphs` can help you to check your implementation.
`make tests SSA=1` to launch the testsuite with smart code generation.
# Test design
TODO: give the main objectives of your tests.
# Known bugs
TODO: bugs you could not fix (if any).
# MiniC Compiler
LAB5 (smart code generation), MIF08 / CAP 2021-22
# Authors
YOUR NAME HERE
# Contents
TODO for STUDENTS : Say a bit about the code infrastructure ...
# Howto
`make run-trace ALLOC=smart TESTFILE=TP04/tests/provided/step1/test00.c`: launch the compiler, then the RISCV assembler and simulators to run a single file. In this example, should print 42.
`make tests-smart` to launch the testsuite (smart code generation).
# Test design
TODO: explain your tests
# Design choices
TODO: explain your choices
# Known bugs
TODO: Bugs and limitations.
# MiniC Compiler
LAB4 (simple code generation), MIF08 / CAP 2021-22
# Authors
YOUR NAME HERE
# Contents
TODO for STUDENTS : Say a bit about the code infrastructure ...
# Howto
`python3 MiniCC.py TP04/tests/provided/step1/test00.c --reg-alloc=naive`: launch the compiler and obtain a RISCV code with temp.
`make TEST_FILES="TP04/tests/provided/step1/*.c" tests-naive`: check expected and compile with the naive allocation.
`make TEST_FILES="TP04/tests/provided/step1/*.c" tests-notsmart`: check expected and compile with the naive allocation and the all in memory allocation.
# Test design
TODO: explain your tests
# Design choices
TODO: explain your choices
# Known bugs
TODO: Bugs and limitations.
# Checklists
A check ([X]) means that the feature is implemented
and *tested* with appropriate test cases.
## Code generation
- [ ] Number Atom
- [ ] Boolean Atom
- [ ] Id Atom
- [ ] Additive expression
- [ ] Multiplicative expr
- [ ] UnaryMinus expr
- [ ] Or expression
- [ ] And expression
- [ ] Equality expression
- [ ] Relational expression (! many cases -> many tests)
- [ ] Not expression
## Statements
- [ ] Prog, assignements
- [ ] While
- [ ] Cond Block
- [ ] If
- [ ] Nested ifs
- [ ] Nested whiles
## Allocation
- [ ] Naive allocation
- [ ] All in memory allocation
- [ ] Massive tests of memory allocation
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from Errors import MiniCInternalError
from .Operands import (
Condition, Immediate, Offset, Temporary,
TemporaryPool, Function,
A0,
ZERO)
from .Instruction3A import (
Instru3A, Jump, CondJump, Comment, Label
)
"""
MIF08, CAP, CodeGeneration, RiscV API
Classes for a RiscV program: constructors, allocation, dump.
"""
class LinearCode:
"""Representation of a RiscV program as a list of instructions."""
def __init__(self, name):
self._listIns = []
self._nblabel = -1
self._dec = 0
self._pool = TemporaryPool()
self._name = name
self._start = None
self._label_div_by_zero = self.new_label("div_by_zero")
self._stacksize = 0
def add_instruction(self, i, link_with_succ=True):
"""Utility function to add an instruction in the program.
"""
self._listIns.append(i)
return i
def iter_instructions(self, f):
"""Iterate over instructions.
For each real instruction (not label or comment), call f,
which must return either None or a list of instruction. If it
returns None, nothing happens. If it returns a list, then the
instruction is replaced by this list.
"""
i = 0
while i < len(self._listIns):
old_i = self._listIns[i]
if not old_i.is_instruction():
i += 1
continue
new_i_list = f(old_i)
if new_i_list is None:
i += 1
continue
del self._listIns[i]
self._listIns.insert(i, Comment(str(old_i)))
i += 1
for new_i in new_i_list:
self._listIns.insert(i, new_i)
i += 1
self._listIns.insert(i, Comment("end " + str(old_i)))
i += 1
def get_instructions(self):
return self._listIns
def new_tmp(self) -> Temporary:
"""
Return a new fresh temporary (temp)
+ add in list
"""
return self._pool.new_tmp()
def new_offset(self, base):
"""
Return a new offset in the memory stack
"""
self._dec = self._dec + 1
return Offset(base, -8 * self._dec)
def get_offset(self):
return self._dec
def new_name(self, name):
"""
Return a new label name
"""
self._nblabel = self._nblabel + 1
return name + "_" + str(self._nblabel) + "_" + self._name
def new_label(self, name):
"""Return a new label"""
return Label(self.new_name(name))
def get_label_div_by_zero(self):
return self._label_div_by_zero
# each instruction has its own "add in list" version
def add_label(self, s):
return self.add_instruction(s)
def add_comment(self, s):
self.add_instruction(Comment(s))
def add_instruction_PRINTLN_INT(self, reg):
"""Print integer value, with newline. (see Expand)"""
# a print instruction generates the temp it prints.
ins = Instru3A("mv", A0, reg)
self.add_instruction(ins)
self.add_instruction_CALL('println_int')
# Other printing instructions are not implemented.
def add_instruction_CALL(self, function):
if isinstance(function, str):
function = Function(function)
assert isinstance(function, Function)
self.add_instruction(Instru3A('call', function))
# Unconditional jump to label.
def add_instruction_JUMP(self, label):
assert isinstance(label, Label)
i = Jump(label)
self.add_instruction(i)
return i
# Conditional jump
def add_instruction_cond_JUMP(self, label, op1, c, op2):
"""Add a conditional jump to the code.
This is a wrapper around bge, bgt, beq, ... c is a Condition, like
Condition('bgt'), Condition(MiniCParser.EQ), ...
"""
assert isinstance(label, Label)
assert isinstance(c, Condition)
if op2 != 0:
ins = CondJump(c.__str__(), op1, op2, label)
else:
ins = CondJump(c.__str__(), op1, ZERO, label)
self.add_instruction(ins)
return ins
def add_instruction_ADD(self, dr, sr1, sr2orimm7):
if isinstance(sr2orimm7, Immediate):
ins = Instru3A("addi", dr, sr1, sr2orimm7)
else:
ins = Instru3A("add", dr, sr1, sr2orimm7)
self.add_instruction(ins)
def add_instruction_MUL(self, dr, sr1, sr2orimm7):
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant multiply by an immediate")
else:
ins = Instru3A("mul", dr, sr1, sr2orimm7)
self.add_instruction(ins)
def add_instruction_DIV(self, dr, sr1, sr2orimm7):
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant divide by an immediate")
else:
ins = Instru3A("div", dr, sr1, sr2orimm7)
self.add_instruction(ins)
def add_instruction_REM(self, dr, sr1, sr2orimm7):
if isinstance(sr2orimm7, Immediate):
raise MiniCInternalError("Cant divide by an immediate")
else:
ins = Instru3A("rem", dr, sr1, sr2orimm7)
self.add_instruction(ins)
def add_instruction_NOT(self, dr, sr):
ins = Instru3A("not", dr, sr)
self.add_instruction(ins)
def add_instruction_SUB(self, dr, sr1, sr2orimm7):
assert not isinstance(sr2orimm7, Immediate)
ins = Instru3A("sub", dr, sr1, sr2orimm7)
self.add_instruction(ins)
def add_instruction_AND(self, dr, sr1, sr2orimm7):
ins = Instru3A("and", dr, sr1, sr2orimm7)
self.add_instruction(ins)
def add_instruction_OR(self, dr, sr1, sr2orimm7):
ins = Instru3A("or", dr, sr1, sr2orimm7)
self.add_instruction(ins)
# Copy values (immediate or in register)
def add_instruction_LI(self, dr, imm7):
ins = Instru3A("li", dr, imm7)
self.add_instruction(ins)
def add_instruction_MV(self, dr, sr):
ins = Instru3A("mv", dr, sr)
self.add_instruction(ins)
def add_instruction_LD(self, dr, mem):
ins = Instru3A("ld", dr, mem)
self.add_instruction(ins)
def add_instruction_SD(self, sr, mem):
ins = Instru3A("sd", sr, mem)
self.add_instruction(ins)
def __str__(self):
return '\n'.join(map(str, self._listIns))
def print_code(self, output, comment=None):
# compute size for the local stack - do not forget to align by 16
fo = self._stacksize # allocate enough memory for stack
cardoffset = 8 * (fo + (0 if fo % 2 == 0 else 1)) + 16
output.write(
"##Automatically generated RISCV code, MIF08 & CAP\n")
if comment is not None:
output.write("##{} version\n".format(comment))
output.write("\n\n##prelude\n")
output.write("""
.text
.globl {0}
{0}:
addi sp, sp, -{1}
sd ra, 0(sp)
sd fp, 8(sp)
addi fp, sp, {1}
""".format(self._name, cardoffset))
# Stack in RiscV is managed with SP
output.write("\n\n##Generated Code\n")
for i in self._listIns:
i.printIns(output)
output.write("\n\n##postlude\n")
output.write("""
ld ra, 0(sp)
ld fp, 8(sp)
addi sp, sp, {0}