Ajout TP3

This commit is contained in:
BuildTools 2024-09-29 09:58:25 +02:00
parent 3ccd91c1ec
commit 390ca18660
54 changed files with 2458 additions and 0 deletions

11
MiniC/.coveragerc Normal file
View File

@ -0,0 +1,11 @@
[report]
exclude_lines =
pragma: no cover
raise MiniCInternalError
raise MiniCUnsupportedError
if debug:
if debug_graphs:
if self._debug:
if self.debug:
if self._debug_graphs:
if self.debug_graphs:

20
MiniC/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
/MiniCLexer.py
/MiniCParser.py
/MiniCVisitor.py
/TP*/tests/**/*.s
/TP*/**/*.riscv
/TP*/**/*-naive.s
/TP*/**/*-all_in_mem.s
/TP*/**/*-smart.s
/TP*/**/*.trace
/TP*/**/*.dot
/TP*/**/*.dot.pdf
/MiniC-stripped.g4
.coverage
htmlcov/
doc/api
doc/_build
log-*.txt
.eval-done.txt
*.py.diff
*.g4.diff

18
MiniC/Lib/Errors.py Normal file
View File

@ -0,0 +1,18 @@
class MiniCRuntimeError(Exception):
pass
class MiniCInternalError(Exception):
pass
class MiniCUnsupportedError(Exception):
pass
class MiniCTypeError(Exception):
pass
class AllocationError(Exception):
pass

123
MiniC/Makefile Normal file
View File

@ -0,0 +1,123 @@
MYNAME = JohnDoe
PACKAGE = MiniC
# Example: stop at the first failed test:
# make PYTEST_OPTS=-x test
PYTEST_OPTS =
# Run the whole test infrastructure for a subset of test files e.g.
# make FILTER='TP03/**/bad*.c' test
ifdef FILTER
export FILTER
endif
# code generation mode
ifdef MODE
MINICC_OPTS+=--mode $(MODE)
endif
export MINICC_OPTS
PYTEST_BASE_OPTS=-vv -rs --failed-first --cov="$(PWD)" --cov-report=term --cov-report=html
ifndef ANTLR4
abort:
$(error variable ANTLR4 is not set)
endif
all: antlr
.PHONY: antlr
antlr MiniCLexer.py MiniCParser.py: $(PACKAGE).g4
$(ANTLR4) $< -Dlanguage=Python3 -visitor -no-listener
main-deps: MiniCLexer.py MiniCParser.py TP03/MiniCInterpretVisitor.py TP03/MiniCTypingVisitor.py
doc: antlr
sphinx-apidoc -e -f -o doc/api . TP* replace_* *Wrapper* MiniC* conf* test*
make -C doc html
test: test-interpret
test-pyright: antlr
pyright .
test-parse: test-pyright antlr
MINICC_OPTS="$(MINICC_OPTS) --mode=parse" python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'naive'
test-typecheck: test-pyright antlr
MINICC_OPTS="$(MINICC_OPTS) --mode=typecheck" python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'naive'
test-interpret: test-pyright test_interpreter.py main-deps
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) test_interpreter.py
ifndef MODE
# The export must be on the same line as the command (note the ';'), because
# make starts a new shell for each line.
LINEAR=export MINICC_OPTS="${MINICC_OPTS} --mode codegen-linear";
else
LINEAR=
endif
# Test for naive allocator (also runs test_expect to check // EXPECTED directives):
test-naive: test-pyright antlr
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_naive or test_expect'
test-mem: test-pyright antlr
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_alloc_mem'
test-hybrid: test-pyright antlr
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_alloc_hybrid'
# Test for all but the smart allocator, i.e. everything that lab4 should pass:
test-lab4: test-pyright antlr
$(LINEAR) python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'not test_smart'
# Test just the smart allocator (quicker than tests)
test-smart: test-pyright antlr
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'test_smart'
# Complete testsuite (should pass for lab5):
test-codegen: test-pyright antlr
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py
NAME_CLEAN = ${shell printf '%s' "$(MYNAME)" | tr -cd '[0-9a-zA-Z]'}
BASEDIR=$$(basename "$$PWD")
tar: clean
dir=$$(basename "$$PWD") && cd .. && \
tar cvfz "$$dir"-$(NAME_CLEAN).tgz --exclude=".git" --exclude=".pytest_cache" \
--exclude="htmlcov" "$$dir"
@echo "Created ../$$(basename "$$PWD")-$(NAME_CLEAN).tgz"
# Remove any assembly file that was created by a test.
# Don't just find -name \*.s -exec rm {} \; because there may be legitimate .s files in the testsuite.
define CLEAN
import glob
import os
for f in glob.glob("**/tests/**/*.c", recursive=True):
for s in ("{}-{}.s".format(f[:-2], test) for test in ("naive", "smart", "gcc", "all-in-mem", "hybrid")):
try:
os.remove(s)
print("Removed {}".format(s))
except OSError:
pass
endef
export CLEAN
clean-tests:
@python3 -c "$$CLEAN"
find . -iname "*.riscv" -print0 | xargs -0 rm -rf \;
clean: clean-tests
find . \( -iname "*~" -or -iname ".cache*" -or -iname "*.diff" -or -iname "log*.txt" -or -iname "__pycache__" -or -iname "*.tokens" -or -iname "*.interp" \) -print0 | xargs -0 rm -rf \;
rm -rf *~ $(PACKAGE)Parser.py $(PACKAGE)Lexer.py $(PACKAGE)Visitor.py .coverage .benchmarks
.PHONY: install-deps
install-deps:
python3 -m pip install antlr4-python3-runtime==4.13.1 pytest pytest-cov pytest-xdist coverage graphviz networkx pygraphviz
.PHONY: test test-interpret test-codegen clean clean-tests tar antlr
# multiple invocations of pytest in parallel would share temporary file names
# and sometimes break. Parallelism can be achieved within pytest with
# pytest-xdist, which is efficient and safe.
.NOTPARALLEL:

138
MiniC/MiniC.g4 Normal file
View File

@ -0,0 +1,138 @@
grammar MiniC;
prog: function* EOF #progRule;
// For now, we don't have "real" functions, just the main() function
// that is the main program, with a hardcoded profile and final
// 'return 0' (actually a 'return INT' because we don't have a ZERO
// lexical token).
function: INTTYPE ID OPAR CPAR OBRACE vardecl_l block
RETURN INT SCOL CBRACE #funcDef;
vardecl_l: vardecl* #varDeclList;
vardecl: typee id_l SCOL #varDecl;
id_l: ID #idListBase
| ID COM id_l #idList
;
block: stat* #statList;
stat: assignment SCOL
| if_stat
| while_stat
| print_stat
;
assignment: ID ASSIGN expr #assignStat;
if_stat: IF OPAR expr CPAR then_block=stat_block
(ELSE else_block=stat_block)? #ifStat;
stat_block: OBRACE block CBRACE
| stat
;
while_stat: WHILE OPAR expr CPAR body=stat_block #whileStat;
print_stat
: PRINTLN_INT OPAR expr CPAR SCOL #printlnintStat
| PRINTLN_FLOAT OPAR expr CPAR SCOL #printlnfloatStat
| PRINTLN_BOOL OPAR expr CPAR SCOL #printlnboolStat
| PRINTLN_STRING OPAR expr CPAR SCOL #printlnstringStat
;
expr: MINUS expr #unaryMinusExpr
| NOT expr #notExpr
| expr myop=(MULT|DIV|MOD) expr #multiplicativeExpr
| expr myop=(PLUS|MINUS) expr #additiveExpr
| expr myop=(GT|LT|GTEQ|LTEQ) expr #relationalExpr
| expr myop=(EQ|NEQ) expr #equalityExpr
| expr AND expr #andExpr
| expr OR expr #orExpr
| atom #atomExpr
;
atom
: OPAR expr CPAR #parExpr
| INT #intAtom
| FLOAT #floatAtom
| (TRUE | FALSE) #booleanAtom
| ID #idAtom
| STRING #stringAtom
;
typee
: mytype=(INTTYPE|FLOATTYPE|BOOLTYPE|STRINGTYPE) #basicType
;
OR : '||';
AND : '&&';
EQ : '==';
NEQ : '!=';
GT : '>';
LT : '<';
GTEQ : '>=';
LTEQ : '<=';
PLUS : '+';
MINUS : '-';
MULT : '*';
DIV : '/';
MOD : '%';
NOT : '!';
COL: ':';
SCOL : ';';
COM : ',';
ASSIGN : '=';
OPAR : '(';
CPAR : ')';
OBRACE : '{';
CBRACE : '}';
TRUE : 'true';
FALSE : 'false';
IF : 'if';
ELSE : 'else';
WHILE : 'while';
RETURN : 'return';
PRINTLN_INT : 'println_int';
PRINTLN_BOOL : 'println_bool';
PRINTLN_STRING : 'println_string';
PRINTLN_FLOAT : 'println_float';
INTTYPE: 'int';
FLOATTYPE: 'float';
STRINGTYPE: 'string';
BOOLTYPE : 'bool';
ID
: [a-zA-Z_] [a-zA-Z_0-9]*
;
INT
: [0-9]+
;
FLOAT
: [0-9]+ '.' [0-9]*
| '.' [0-9]+
;
STRING
: '"' (~["\r\n] | '""')* '"'
;
COMMENT
// # is a comment in Mini-C, and used for #include in real C so that we ignore #include statements
: ('#' | '//') ~[\r\n]* -> skip
;
SPACE
: [ \t\r\n] -> skip
;

333
MiniC/MiniCC.py Executable file
View File

@ -0,0 +1,333 @@
#! /usr/bin/python3
"""
Evaluation and code generation labs, main file.
Usage:
python3 MiniCC.py --mode <mode> <filename>
python3 MiniCC.py --help
"""
from __future__ import annotations
from typing import cast
from enum import Enum
from MiniCLexer import MiniCLexer
from MiniCParser import MiniCParser
from TP03.MiniCTypingVisitor import MiniCTypingVisitor, MiniCTypeError
from TP03.MiniCInterpretVisitor import MiniCInterpretVisitor
from Lib.Errors import (MiniCUnsupportedError, MiniCInternalError,
MiniCRuntimeError, AllocationError)
import antlr4
from antlr4.error.ErrorListener import ErrorListener
from argparse import ArgumentParser
from traceback import print_exc
import os
import sys
class Mode(Enum):
PARSE = 0
EVAL = 1
LINEAR = 2
CFG = 3
SSA = 4
OPTIM = 5
def is_codegen(self) -> bool:
return self.value >= Mode.LINEAR.value
def is_ssa(self) -> bool:
return self.value >= Mode.SSA.value
def valid_modes():
modes = ['parse', 'typecheck', 'eval']
try:
import TP04.MiniCCodeGen3AVisitor # pyright: ignore # noqa: F401, type: ignore
modes.append('codegen-linear')
except ModuleNotFoundError:
return modes
try:
import Lib.CFG # pyright: ignore # noqa: F401, type: ignore
modes.append('codegen-cfg')
except ModuleNotFoundError:
return modes
try:
import TP05.EnterSSA # pyright: ignore # noqa: F401, type: ignore
modes.append('codegen-ssa')
except ModuleNotFoundError:
return modes
try:
import TPoptim.OptimSSA # pyright: ignore # noqa: F401, type: ignore
modes.append('codegen-optim')
except ModuleNotFoundError:
pass
return modes
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, offendingSymbol, line, column, msg, e):
self.count += 1
def main(inputname, reg_alloc, mode,
typecheck=True, stdout=False, output_name=None, debug=False,
debug_graphs=False, ssa_graphs=False, dom_graphs=False):
(basename, rest) = os.path.splitext(inputname)
if mode.is_codegen():
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 = antlr4.FileStream(inputname, encoding='utf-8')
lexer = MiniCLexer(input_s)
counter = CountErrorListener()
lexer._listeners.append(counter)
stream = antlr4.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 mode == Mode.EVAL:
# interpret Visitor
interpreter_visitor = MiniCInterpretVisitor()
try:
interpreter_visitor.visit(tree)
except MiniCRuntimeError as e:
print(e.args[0])
exit(1)
except MiniCInternalError as e:
print(e.args[0], file=sys.stderr)
exit(4)
return
if not mode.is_codegen():
if debug:
print("Not running code generation because of --typecheck-only.")
return
# Codegen 3@ CFG Visitor, first argument is debug mode
from TP04.MiniCCodeGen3AVisitor import MiniCCodeGen3AVisitor # type: ignore[import]
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():
fdata = function.fdata
# Allocation part
if mode == Mode.LINEAR:
code = function
else:
from TP04.BuildCFG import build_cfg # type: ignore[import]
code = build_cfg(function)
if debug_graphs:
s = "{}.{}.dot".format(basename, code.fdata.get_name())
print("CFG:", s)
code.print_dot(s, view=True)
if mode.is_ssa():
from TP05.EnterSSA import enter_ssa # type: ignore[import]
from Lib.CFG import CFG # type: ignore[import]
enter_ssa(cast(CFG, code), dom_graphs, basename)
if ssa_graphs:
s = "{}.{}.enterssa.dot".format(basename, code.fdata.get_name())
print("SSA:", s)
code.print_dot(s, view=True)
if mode == Mode.OPTIM:
from TPoptim.OptimSSA import OptimSSA # type: ignore[import]
OptimSSA(cast(CFG, code), debug=debug)
if ssa_graphs:
s = "{}.{}.optimssa.dot".format(basename, code.fdata.get_name())
print("SSA after optim:", s)
code.print_dot(s, view=True)
allocator = None
if reg_alloc == "naive":
from Lib.Allocator import NaiveAllocator # type: ignore[import]
allocator = NaiveAllocator(fdata)
comment = "naive allocation"
elif reg_alloc == "all-in-mem":
from TP04.AllInMemAllocator import AllInMemAllocator # type: ignore[import]
allocator = AllInMemAllocator(fdata)
comment = "all-in-memory allocation"
elif reg_alloc == "hybrid":
from TP04.HybridNaiveAllocator import HybridNaiveAllocator # type: ignore[import]
allocator = HybridNaiveAllocator(fdata)
comment = "hybrid, naive allocation"
elif reg_alloc == "smart":
liveness = None
if mode.is_ssa():
from TP05.LivenessSSA import LivenessSSA # type: ignore[import]
try:
from Lib.CFG import CFG # type: ignore[import]
liveness = LivenessSSA(cast(CFG, code), debug=debug)
except NameError:
form = "CFG in SSA form"
raise ValueError("Invalid dataflow form: \
liveness file not found for {}.".format(form))
else:
try:
from TP05.LivenessDataFlow import LivenessDataFlow # type: ignore[import]
liveness = LivenessDataFlow(code, debug=debug)
except NameError:
form = "CFG not in SSA form"
raise ValueError("Invalid dataflow form: \
liveness file not found for {}.".format(form))
from TP05.SmartAllocator import SmartAllocator # type: ignore[import]
allocator = SmartAllocator(fdata, 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 mode.is_ssa():
from Lib.CFG import CFG # type: ignore[import]
from TP05.ExitSSA import exit_ssa # type: ignore[import]
exit_ssa(cast(CFG, code), reg_alloc == 'smart')
comment += " with SSA"
if allocator:
allocator.rewriteCode(code)
if mode.is_ssa() and ssa_graphs:
s = "{}.{}.exitssa.dot".format(basename, code.fdata.get_name())
print("CFG after SSA:", s)
code.print_dot(s, view=True)
from Lib.LinearCode import LinearCode # type: ignore[import]
output.write(f"# Code generated by {os.path.realpath(sys.argv[0])}\n")
for v in os.environ:
if v.startswith("REPLACE_"):
output.write(f"# {v}={os.environ[v]}\n")
output.write(f"# Options: {' '.join(sys.argv[1:])}\n")
if isinstance(code, LinearCode):
code.print_code(output, comment=comment)
else:
from Lib.CFG import CFG # type: ignore[import]
from TP04.LinearizeCFG import linearize # type: ignore[import]
assert (isinstance(code, CFG))
code.print_code(output, linearize=linearize, comment=comment)
if debug:
visitor3.printSymbolTable()
# command line management
if __name__ == '__main__':
modes = valid_modes()
parser = ArgumentParser(description='CAP/MIF08 MiniCC compiler')
parser.add_argument('filename', type=str,
help='Source file.')
parser.add_argument('--mode', type=str,
choices=modes,
required=True,
help='Operation to perform on the input program')
parser.add_argument('--debug', action='store_true',
default=False,
help='Emit verbose debug output')
parser.add_argument('--disable-typecheck', action='store_true',
default=False,
help="Don't run the typechecker before evaluation or code generation")
if "codegen-linear" in modes:
parser.add_argument('--reg-alloc', type=str,
choices=['none', 'naive', 'all-in-mem', 'hybrid', 'smart'],
help='Register allocation to perform during code generation')
parser.add_argument('--stdout', action='store_true',
help='Generate code to stdout')
parser.add_argument('--output', type=str,
help='Generate code to outfile')
if "codegen-cfg" in modes:
parser.add_argument('--graphs', action='store_true',
default=False,
help='Display graphs (CFG, conflict graph).')
if "codegen-ssa" in modes:
parser.add_argument('--ssa-graphs', action='store_true',
default=False,
help='Display the CFG at SSA entry and exit.')
parser.add_argument('--dom-graphs', action='store_true',
default=False,
help='Display dominance-related graphs (DT, DF).')
args = parser.parse_args()
reg_alloc = args.reg_alloc if "codegen-linear" in modes else None
to_stdout = args.stdout if "codegen-linear" in modes else False
outfile = args.output if "codegen-linear" in modes else None
graphs = args.graphs if "codegen-cfg" in modes else False
ssa_graphs = args.ssa_graphs if "codegen-ssa" in modes else False
dom_graphs = args.dom_graphs if "codegen-ssa" in modes else False
if reg_alloc is None and "codegen" in args.mode:
print("error: the following arguments is required: --reg-alloc")
exit(1)
elif reg_alloc is not None and "codegen" not in args.mode:
print("error: register allocation is only available in code generation mode")
exit(1)
typecheck = not args.disable_typecheck
if args.mode == "parse":
mode = Mode.PARSE
typecheck = False
elif args.mode == "typecheck":
mode = Mode.PARSE
elif args.mode == "eval":
mode = Mode.EVAL
elif args.mode == "codegen-linear":
mode = Mode.LINEAR
if reg_alloc == "smart":
print("error: smart register allocation is not compatible with linear code generation")
exit(1)
elif args.mode == "codegen-cfg":
mode = Mode.CFG
elif args.mode == "codegen-ssa":
mode = Mode.SSA
elif args.mode == "codegen-optim":
mode = Mode.OPTIM
else:
raise ValueError("Invalid mode:" + args.mode)
try:
main(args.filename, reg_alloc, mode,
typecheck,
to_stdout, outfile, args.debug,
graphs, ssa_graphs, dom_graphs)
except MiniCUnsupportedError as e:
print(e)
exit(5)
except (MiniCInternalError, AllocationError):
print_exc()
exit(4)

16
MiniC/MiniCLexer.pyi Normal file
View File

@ -0,0 +1,16 @@
from antlr4.error.ErrorListener import ErrorListener
from antlr4.Lexer import Lexer
from typing import Callable, Any, List
class MiniCLexer(Lexer):
_listeners: List[ErrorListener]
EQ: int
LT: int
LTEQ: int
GT: int
GTEQ: int
PLUS: int
MINUS: int
MULT: int
DIV: int
MOD: int

22
MiniC/MiniCParser.pyi Normal file
View File

@ -0,0 +1,22 @@
from antlr4.error.ErrorListener import ErrorListener
from antlr4.Parser import Parser
from typing import Callable, Any, List
class MiniCParser(Parser):
_listeners: List[ErrorListener]
prog: Callable[[], Any]
EQ: int
LT: int
LTEQ: int
GT: int
GTEQ: int
PLUS: int
MINUS: int
MULT: int
DIV: int
MOD: int
NEQ: int
INTTYPE: int
FLOATTYPE: int
BOOLTYPE: int
STRINGTYPE: int

View File

@ -0,0 +1,32 @@
# MiniC interpreter and typer
LAB3, MIF08 / CAP / CS444 2022-23
# Authors
TODO: YOUR NAME HERE
# Contents
TODO for STUDENTS : Say a bit about the code infrastructure ...
# Howto
`make test-interpret TEST_FILES='TP03/tests/provided/examples/test_print_int.c'` for a single run
`make test` to test all the files in `*/tests/*` according to `EXPECTED` results.
You can select the files you want to test by using `make test TEST_FILES='TP03/**/*bad*.c'` (`**` means
"any number of possibly nested directories").
# Test design
TODO: explain your tests. Do not repeat what test files already contain, just give the main objectives of the tests.
# Design choices
TODO: explain your choices - explain the limitations of your implementation.
# Known bugs
TODO: document any known bug and limitations. Did you do everything asked for? Did you implement an extension?

View File

@ -0,0 +1,185 @@
# Visitor to *interpret* MiniC files
from typing import (
Dict,
List)
from MiniCVisitor import MiniCVisitor
from MiniCParser import MiniCParser
from Lib.Errors import MiniCRuntimeError, MiniCInternalError, MiniCUnsupportedError
MINIC_VALUE = int | str | bool | float | List['MINIC_VALUE']
class MiniCInterpretVisitor(MiniCVisitor):
_memory: Dict[str, MINIC_VALUE]
def __init__(self):
self._memory = dict() # store all variable ids and values.
self.has_main = False
# visitors for variable declarations
def visitVarDecl(self, ctx) -> None:
# Initialise all variables in self._memory
type_str = ctx.typee().getText()
raise NotImplementedError(f"Initialization for type {type_str}")
def visitIdList(self, ctx) -> List[str]:
raise NotImplementedError()
def visitIdListBase(self, ctx) -> List[str]:
return [ctx.ID().getText()]
# visitors for atoms --> value
def visitParExpr(self, ctx) -> MINIC_VALUE:
return self.visit(ctx.expr())
def visitIntAtom(self, ctx) -> int:
return int(ctx.getText())
def visitFloatAtom(self, ctx) -> float:
return float(ctx.getText())
def visitBooleanAtom(self, ctx) -> bool:
return ctx.getText() == "true"
def visitIdAtom(self, ctx) -> MINIC_VALUE:
raise NotImplementedError()
def visitStringAtom(self, ctx) -> str:
return ctx.getText()[1:-1] # Remove the ""
# visit expressions
def visitAtomExpr(self, ctx) -> MINIC_VALUE:
return self.visit(ctx.atom())
def visitOrExpr(self, ctx) -> bool:
lval = self.visit(ctx.expr(0))
rval = self.visit(ctx.expr(1))
return lval | rval
def visitAndExpr(self, ctx) -> bool:
lval = self.visit(ctx.expr(0))
rval = self.visit(ctx.expr(1))
return lval & rval
def visitEqualityExpr(self, ctx) -> bool:
assert ctx.myop is not None
lval = self.visit(ctx.expr(0))
rval = self.visit(ctx.expr(1))
# be careful for float equality
if ctx.myop.type == MiniCParser.EQ:
return lval == rval
else:
return lval != rval
def visitRelationalExpr(self, ctx) -> bool:
assert ctx.myop is not None
lval = self.visit(ctx.expr(0))
rval = self.visit(ctx.expr(1))
if ctx.myop.type == MiniCParser.LT:
return lval < rval
elif ctx.myop.type == MiniCParser.LTEQ:
return lval <= rval
elif ctx.myop.type == MiniCParser.GT:
return lval > rval
elif ctx.myop.type == MiniCParser.GTEQ:
return lval >= rval
else:
raise MiniCInternalError(
f"Unknown comparison operator '{ctx.myop}'"
)
def visitAdditiveExpr(self, ctx) -> MINIC_VALUE:
assert ctx.myop is not None
lval = self.visit(ctx.expr(0))
rval = self.visit(ctx.expr(1))
if ctx.myop.type == MiniCParser.PLUS:
if any(isinstance(x, str) for x in (lval, rval)):
return '{}{}'.format(lval, rval)
else:
return lval + rval
elif ctx.myop.type == MiniCParser.MINUS:
return lval - rval
else:
raise MiniCInternalError(
f"Unknown additive operator '{ctx.myop}'")
def visitMultiplicativeExpr(self, ctx) -> MINIC_VALUE:
assert ctx.myop is not None
lval = self.visit(ctx.expr(0))
rval = self.visit(ctx.expr(1))
if ctx.myop.type == MiniCParser.MULT:
return lval * rval
elif ctx.myop.type == MiniCParser.DIV:
if rval == 0:
raise MiniCRuntimeError("Division by 0")
if isinstance(lval, int):
return lval // rval
else:
return lval / rval
elif ctx.myop.type == MiniCParser.MOD:
# TODO : interpret modulo
raise NotImplementedError()
else:
raise MiniCInternalError(
f"Unknown multiplicative operator '{ctx.myop}'")
def visitNotExpr(self, ctx) -> bool:
return not self.visit(ctx.expr())
def visitUnaryMinusExpr(self, ctx) -> MINIC_VALUE:
return -self.visit(ctx.expr())
# visit statements
def visitPrintlnintStat(self, ctx) -> None:
val = self.visit(ctx.expr())
print(val)
def visitPrintlnfloatStat(self, ctx) -> None:
val = self.visit(ctx.expr())
if isinstance(val, float):
val = f"{val:.2f}"
print(val)
def visitPrintlnboolStat(self, ctx) -> None:
val = self.visit(ctx.expr())
print('1' if val else '0')
def visitPrintlnstringStat(self, ctx) -> None:
val = self.visit(ctx.expr())
print(val)
def visitAssignStat(self, ctx) -> None:
raise NotImplementedError()
def visitIfStat(self, ctx) -> None:
raise NotImplementedError()
def visitWhileStat(self, ctx) -> None:
raise NotImplementedError()
# TOPLEVEL
def visitProgRule(self, ctx) -> None:
self.visitChildren(ctx)
if not self.has_main:
# A program without a main function is compilable (hence
# it's not a typing error per se), but not executable,
# hence we consider it a runtime error.
raise MiniCRuntimeError("No main function in file")
# Visit a function: ignore if non main!
def visitFuncDef(self, ctx) -> None:
funname = ctx.ID().getText()
if funname == "main":
self.has_main = True
self.visit(ctx.vardecl_l())
self.visit(ctx.block())
else:
raise MiniCUnsupportedError("Functions are not supported in evaluation mode")
def visitFuncCall(self, ctx) -> None: # pragma: no cover
raise MiniCUnsupportedError("Functions are not supported in evaluation mode")

View File

@ -0,0 +1,148 @@
# Visitor to *typecheck* MiniC files
from typing import List, NoReturn
from MiniCVisitor import MiniCVisitor
from MiniCParser import MiniCParser
from Lib.Errors import MiniCInternalError, MiniCTypeError
from enum import Enum
class BaseType(Enum):
Float, Integer, Boolean, String = range(4)
# Basic Type Checking for MiniC programs.
class MiniCTypingVisitor(MiniCVisitor):
def __init__(self):
self._memorytypes = dict() # id -> types
# For now, we don't have real functions ...
self._current_function = "main"
def _raise(self, ctx, for_what, *types):
raise MiniCTypeError(
'In function {}: Line {} col {}: invalid type for {}: {}'.format(
self._current_function,
ctx.start.line, ctx.start.column, for_what,
' and '.join(t.name.lower() for t in types)))
def _assertSameType(self, ctx, for_what, *types):
if not all(types[0] == t for t in types):
raise MiniCTypeError(
'In function {}: Line {} col {}: type mismatch for {}: {}'.format(
self._current_function,
ctx.start.line, ctx.start.column, for_what,
' and '.join(t.name.lower() for t in types)))
def _raiseNonType(self, ctx, message) -> NoReturn:
raise MiniCTypeError(
'In function {}: Line {} col {}: {}'.format(
self._current_function,
ctx.start.line, ctx.start.column, message))
# type declaration
def visitVarDecl(self, ctx) -> None:
raise NotImplementedError()
def visitBasicType(self, ctx):
assert ctx.mytype is not None
if ctx.mytype.type == MiniCParser.INTTYPE:
return BaseType.Integer
elif ctx.mytype.type == MiniCParser.FLOATTYPE:
return BaseType.Float
else: # TODO: same for other types
raise NotImplementedError()
def visitIdList(self, ctx) -> List[str]:
raise NotImplementedError()
def visitIdListBase(self, ctx) -> List[str]:
raise NotImplementedError()
# typing visitors for expressions, statements !
# visitors for atoms --> type
def visitParExpr(self, ctx):
return self.visit(ctx.expr())
def visitIntAtom(self, ctx):
return BaseType.Integer
def visitFloatAtom(self, ctx):
return BaseType.Float
def visitBooleanAtom(self, ctx):
raise NotImplementedError()
def visitIdAtom(self, ctx):
try:
return self._memorytypes[ctx.getText()]
except KeyError:
self._raiseNonType(ctx,
"Undefined variable {}".format(ctx.getText()))
def visitStringAtom(self, ctx):
return BaseType.String
# now visit expr
def visitAtomExpr(self, ctx):
return self.visit(ctx.atom())
def visitOrExpr(self, ctx):
raise NotImplementedError()
def visitAndExpr(self, ctx):
raise NotImplementedError()
def visitEqualityExpr(self, ctx):
raise NotImplementedError()
def visitRelationalExpr(self, ctx):
raise NotImplementedError()
def visitAdditiveExpr(self, ctx):
assert ctx.myop is not None
raise NotImplementedError()
def visitMultiplicativeExpr(self, ctx):
assert ctx.myop is not None
raise NotImplementedError()
def visitNotExpr(self, ctx):
raise NotImplementedError()
def visitUnaryMinusExpr(self, ctx):
raise NotImplementedError()
# visit statements
def visitPrintlnintStat(self, ctx):
etype = self.visit(ctx.expr())
if etype != BaseType.Integer:
self._raise(ctx, 'println_int statement', etype)
def visitPrintlnfloatStat(self, ctx):
etype = self.visit(ctx.expr())
if etype != BaseType.Float:
self._raise(ctx, 'println_float statement', etype)
def visitPrintlnboolStat(self, ctx):
etype = self.visit(ctx.expr())
if etype != BaseType.Boolean:
self._raise(ctx, 'println_bool statement', etype)
def visitPrintlnstringStat(self, ctx):
etype = self.visit(ctx.expr())
if etype != BaseType.String:
self._raise(ctx, 'println_string statement', etype)
def visitAssignStat(self, ctx):
raise NotImplementedError()
def visitWhileStat(self, ctx):
raise NotImplementedError()
def visitIfStat(self, ctx):
raise NotImplementedError()

View File

@ -0,0 +1,13 @@
#include "printlib.h"
int main(){
int n;
n=17;
m=n+3;
println_int(m);
return 0;
}
// EXPECTED
// EXITCODE 2
// In function main: Line 6 col 2: Undefined variable m

View File

@ -0,0 +1,10 @@
#include "printlib.h"
int main(){
int x;
x="blablabla";
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 5 col 2: type mismatch for x: integer and string

View File

@ -0,0 +1,14 @@
#include "printlib.h"
int main(){
int n;
string s;
n=17;
s="seventeen";
s = n*s;
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 8 col 6: invalid type for multiplicative operands: integer and string

View File

@ -0,0 +1,11 @@
#include "printlib.h"
int main(){
string x;
x=1;
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 5 col 2: type mismatch for x: string and integer

View File

@ -0,0 +1,10 @@
#include "printlib.h"
int main(){
int x;
x=34+f;
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 5 col 7: Undefined variable f

View File

@ -0,0 +1,11 @@
#include "printlib.h"
int main(){
println_int("foo");
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 4 col 2: invalid type for println_int statement: string

View File

@ -0,0 +1,9 @@
#include "printlib.h"
int main(){
println_bool(true+true);
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 4 col 15: invalid type for additive operands: boolean and boolean

View File

@ -0,0 +1,12 @@
#include "printlib.h"
int main(){
int x,y;
int z,x;
x=42;
return 0;
}
// EXITCODE 2
// EXPECTED
// In function main: Line 5 col 2: Variable x already declared

View File

@ -0,0 +1,12 @@
#include "printlib.h"
int toto(){
println_int(42);
return 0;
}
// SKIP TEST EXPECTED
// EXITCODE 5
// EXPECTED
// Functions are not supported in evaluation mode

View File

@ -0,0 +1,7 @@
#include "printlib.h"
// SKIP TEST EXPECTED
// EXECCODE 1
// EXPECTED
// No main function in file

View File

@ -0,0 +1,13 @@
#include "printlib.h"
int main()
{
int n,u;
n=17;
u=n;
println_int(n);
return 0;
}
// EXPECTED
// 17

View File

@ -0,0 +1,33 @@
#include "printlib.h"
int main() {
println_bool(1 == 1);
println_bool(1 == 2);
println_bool(1 != 1);
println_bool(1 != 2);
println_bool(1 < 2);
println_bool(1 < 0);
println_bool(1 <= 2);
println_bool(1 <= 0);
println_bool(1 > 2);
println_bool(1 > 0);
println_bool(1 >= 2);
println_bool(1 >= 0);
println_bool(!(1 < 2));
return 0;
}
// EXPECTED
// 1
// 0
// 0
// 1
// 1
// 0
// 1
// 0
// 0
// 1
// 0
// 1
// 0

View File

@ -0,0 +1,23 @@
#include "printlib.h"
int main() {
println_int(42);
println_int(3 - 2);
println_int(-42);
println_int(- -42);
println_int(10 / 3);
println_int(9 / 3);
println_int(8 / 3);
println_float(42.3);
return 0;
}
// EXPECTED
// 42
// 1
// -42
// 42
// 3
// 3
// 2
// 42.30

View File

@ -0,0 +1,47 @@
#include "printlib.h"
int main()
{
if (2 < 3)
{
println_int(1);
}
if (2 > 3)
{
println_int(2);
}
if (2 <= 2)
{
println_int(3);
}
if (2 >= 2)
{
println_int(4);
}
if (2 == 3)
{
println_int(10);
}
if (2 != 3)
{
println_int(20);
}
if (2 == 2)
{
println_int(30);
}
if (2 != 2)
{
println_int(40);
}
return 0;
}
// EXPECTED
// 1
// 3
// 4
// 20
// 30

View File

@ -0,0 +1,11 @@
#include "printlib.h"
int main() {
if ((1.0 + 2.0) * 3.0 == 9.0) {
println_string("OK");
}
return 0;
}
// EXPECTED
// OK

View File

@ -0,0 +1,15 @@
#include "printlib.h"
int main(){
println_int(3/2+45*(2/1));
println_int(23+19);
println_bool( (false || 3 != 77 ) && (42<=1515) );
println_string("coucou");
return 0;
}
// EXPECTED
// 91
// 42
// 1
// coucou

View File

@ -0,0 +1,9 @@
#include "printlib.h"
int main(){
println_int(42);
return 0;
}
// EXPECTED
// 42

View File

@ -0,0 +1,11 @@
#include "printlib.h"
int main(){
int x;
x="blabla";
return 0;
}
// EXPECTED
// In function main: Line 5 col 2: type mismatch for x: integer and string
// EXITCODE 2

View File

@ -0,0 +1,15 @@
#include "printlib.h"
int main(){
string x,y,z;
x = "ENS";
y = "De Lyon";
z = x + " " + y;
println_string(z);
return 0;
}
// SKIP TEST EXPECTED
// EXPECTED
// ENS De Lyon

View File

@ -0,0 +1,17 @@
#include "printlib.h"
int main(){
string n,m;
n = "foo";
m = "bar";
println_string(n);
println_string(m);
println_string(n + m); // non-standard C
return 0;
}
// SKIP TEST EXPECTED
// EXPECTED
// foo
// bar
// foobar

View File

@ -0,0 +1,14 @@
#include "printlib.h"
int main() {
string s;
println_string(s);
s = s + "Coucou";
println_string(s);
return 0;
}
// SKIP TEST EXPECTED
// EXPECTED
//
// Coucou

View File

@ -0,0 +1,17 @@
#include "printlib.h"
int main() {
bool x;
if (x) {
println_int(1);
}
x = !x;
if (x) {
println_int(2);
}
return 0;
}
// SKIP TEST EXPECTED
// EXPECTED
// 2

View File

@ -0,0 +1,13 @@
#include "printlib.h"
int main() {
float x;
println_float(x);
x = x + 1.0;
println_float(x);
return 0;
}
// EXPECTED
// 0.00
// 1.00

View File

@ -0,0 +1,13 @@
#include "printlib.h"
int main() {
int x;
println_int(x);
x = x + 1;
println_int(x);
return 0;
}
// EXPECTED
// 0
// 1

View File

@ -0,0 +1 @@
Add your own tests in this directory.

View File

@ -0,0 +1 @@
Add your own tests for the C-like 'for' loop in this directory (tests for the interpreter).

20
MiniC/doc/Makefile Normal file
View File

@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

61
MiniC/doc/conf.py Normal file
View File

@ -0,0 +1,61 @@
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('..'))
# -- Project information -----------------------------------------------------
project = 'MiniC'
copyright = '2023, compil-lyon'
author = 'compil-lyon'
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.viewcode',
]
autodoc_default_options = {
'member-order': 'bysource',
'members': True,
'undoc-members': True,
'ignore-module-all': True,
}
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

77
MiniC/doc/index.rst Normal file
View File

@ -0,0 +1,77 @@
.. MiniC documentation master file, created by
sphinx-quickstart on Thu Feb 3 16:47:38 2022.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to MiniC's documentation!
=================================
.. toctree::
:maxdepth: 4
:caption: Contents:
Base library - Errors <api/Lib.Errors>
Base library - Statement <api/Lib.Statement>
Base library - RISC-V instructions <api/Lib.RiscV>
Base library - Operands <api/Lib.Operands>
Base library - Function data <api/Lib.FunctionData>
Base library - Graphs <api/Lib.Graphes>
Linear intermediate representation <api/Lib.LinearCode>
Temporary allocation <api/Lib.Allocator>
Control Flow Graph - CFG and Basic blocks <api/Lib.CFG>
Control Flow Graph - Terminators <api/Lib.Terminator>
SSA form - Dominance frontier <api/Lib.Dominators>
SSA form - Phi Nodes <api/Lib.PhiNode>
These pages document the various Python sources in the Lib/
folder of MiniC. You should not have to edit them *at all*.
Base library
------------
The :doc:`api/Lib.Statement` defines various classes that represent
RISC-V assembly statements, such as labels or 3-address instructions.
We won't create objects of these classes directly very often.
Instead, to easily create such statements for standard RISC-V assembly instructions
and pseudo-instructions, we give you the :doc:`api/Lib.RiscV`.
RISC-V instructions take arguments of various kinds,
as defined in the :doc:`api/Lib.Operands`.
At some point, we will need some basic functions about oriented and non oriented graphs,
those are present in :doc:`api/Lib.Graphes`.
Linear Intermediate representation
----------------------------------
The :doc:`api/Lib.LinearCode` allows to work with assembly source code
modeled as a list of statements.
Temporary allocation
--------------------
Before implementing the all-in-memory allocator of lab 4a,
you should understand the naive allocator in the :doc:`api/Lib.Allocator`.
Control Flow Graph Intermediate representation
----------------------------------------------
The classes for the CFG and its basic blocks are in the :doc:`api/Lib.CFG`.
Each block ends with a terminator, as documented in the :doc:`api/Lib.Terminator`.
SSA form
--------
The translation of the CFG into SSA form makes use of dominance frontiers.
Functions to work with dominance are defined in the :doc:`api/Lib.Dominators`.
Phi nodes, a special kind of statement that appears in CFGs in SSA form,
are defined in the :doc:`api/Lib.PhiNode`.
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

251
MiniC/libprint.s Normal file
View File

@ -0,0 +1,251 @@
.globl println_int
println_int:
.globl println_bool
println_bool:
addi sp,sp,-8
sd ra, 0(sp)
call print_int
call newline
ld ra, 0(sp)
addi sp,sp,8
ret
.globl println_char
println_char:
addi sp,sp,-8
sd ra, 0(sp)
call print_char
call newline
ld ra, 0(sp)
addi sp,sp,8
ret
.text
.align 1
.globl println_string
.type println_string, @function
println_string: #address stored in a0
addi sp,sp,-152
sd ra,24(sp)
sd s0,16(sp)
addi s0,sp,32
sd t0,32(sp)
sd t1,40(sp)
sd t2,48(sp)
sd t3,56(sp)
sd t4,64(sp)
sd t5,72(sp)
sd t6,80(sp)
sd a0,88(sp)
sd a1,96(sp)
sd a2,104(sp)
sd a3,112(sp)
sd a4,120(sp)
sd a5,128(sp)
sd a6,136(sp)
sd a7,144(sp)
## Argument is already in a0, just forward it to puts
call puts
ld ra,24(sp)
ld s0,16(sp)
ld t0,32(sp)
ld t1,40(sp)
ld t2,48(sp)
ld t3,56(sp)
ld t4,64(sp)
ld t5,72(sp)
ld t6,80(sp)
ld a0,88(sp)
ld a1,96(sp)
ld a2,104(sp)
ld a3,112(sp)
ld a4,120(sp)
ld a5,128(sp)
ld a6,136(sp)
ld a7,144(sp)
addi sp,sp,152
jr ra
.size println_string, .-println_string
.section .rodata
.align 3
fmt_int:
.string "%ld"
str_empty:
.string ""
.text
.align 1
.globl print_int
.type print_int, @function
print_int: # print int stored in a0, saves/restores all scratch registers (except ft<n> which we don't use)
addi sp,sp,-152
sd ra,24(sp)
sd s0,16(sp)
sd t0,32(sp)
sd t1,40(sp)
sd t2,48(sp)
sd t3,56(sp)
sd t4,64(sp)
sd t5,72(sp)
sd t6,80(sp)
sd a0,88(sp)
sd a1,96(sp)
sd a2,104(sp)
sd a3,112(sp)
sd a4,120(sp)
sd a5,128(sp)
sd a6,136(sp)
sd a7,144(sp)
## first parameter of print_int is second parameter of printf
mv a1,a0
## first parameter of printf is the format string
la a0,fmt_int
call printf
ld ra,24(sp)
ld s0,16(sp)
ld t0,32(sp)
ld t1,40(sp)
ld t2,48(sp)
ld t3,56(sp)
ld t4,64(sp)
ld t5,72(sp)
ld t6,80(sp)
ld a0,88(sp)
ld a1,96(sp)
ld a2,104(sp)
ld a3,112(sp)
ld a4,120(sp)
ld a5,128(sp)
ld a6,136(sp)
ld a7,144(sp)
addi sp,sp,152
jr ra
.size print_int, .-print_int
.align 1
.globl newline
.type newline, @function
newline: # print int stored in a0, saves/restores all scratch registers (except ft<n> which we don't use)
addi sp,sp,-152
sd ra,24(sp)
sd s0,16(sp)
sd t0,32(sp)
sd t1,40(sp)
sd t2,48(sp)
sd t3,56(sp)
sd t4,64(sp)
sd t5,72(sp)
sd t6,80(sp)
sd a0,88(sp)
sd a1,96(sp)
sd a2,104(sp)
sd a3,112(sp)
sd a4,120(sp)
sd a5,128(sp)
sd a6,136(sp)
sd a7,144(sp)
## first parameter of printf is the format string
la a0,str_empty
call puts
ld ra,24(sp)
ld s0,16(sp)
ld t0,32(sp)
ld t1,40(sp)
ld t2,48(sp)
ld t3,56(sp)
ld t4,64(sp)
ld t5,72(sp)
ld t6,80(sp)
ld a0,88(sp)
ld a1,96(sp)
ld a2,104(sp)
ld a3,112(sp)
ld a4,120(sp)
ld a5,128(sp)
ld a6,136(sp)
ld a7,144(sp)
addi sp,sp,152
jr ra
.size newline, .-newline
.align 1
.globl print_char
.type print_char, @function
print_char: # print char stored in a0 (ascii code)
addi sp,sp,-152
sd ra,24(sp)
sd s0,16(sp)
addi s0,sp,32
sd t0,32(sp)
sd t1,40(sp)
sd t2,48(sp)
sd t3,56(sp)
sd t4,64(sp)
sd t5,72(sp)
sd t6,80(sp)
sd a0,88(sp)
sd a1,96(sp)
sd a2,104(sp)
sd a3,112(sp)
sd a4,120(sp)
sd a5,128(sp)
sd a6,136(sp)
sd a7,144(sp)
# call to putchar
mv a5,a0
sb a5,-17(s0)
lbu a5,-17(s0)
sext.w a5,a5
mv a0,a5
call putchar
#restore registers
ld ra,24(sp)
ld s0,16(sp)
ld t0,32(sp)
ld t1,40(sp)
ld t2,48(sp)
ld t3,56(sp)
ld t4,64(sp)
ld t5,72(sp)
ld t6,80(sp)
ld a0,88(sp)
ld a1,96(sp)
ld a2,104(sp)
ld a3,112(sp)
ld a4,120(sp)
ld a5,128(sp)
ld a6,136(sp)
ld a7,144(sp)
addi sp,sp,152
jr ra
.size print_char, .-print_char

21
MiniC/printlib.h Normal file
View File

@ -0,0 +1,21 @@
#include <stdio.h>
/**
* Compatibility layer with C (meant to be #included in MiniC source
* files). Defines types, constants and functions that are built-in
* MiniC, to allow compiling MiniC programs with GCC.
*/
typedef char * string;
typedef int bool;
static const int true = 1;
static const int false = 0;
void print_int(int);
void println_int(int);
void println_bool(int);
#define print_float(f) do { printf("%.2f", f); } while(0)
#define println_float(f) do { printf("%.2f\n", f); } while(0)
void print_string(string);
void println_string(string);

2
MiniC/pyproject.toml Normal file
View File

@ -0,0 +1,2 @@
[tool.pyright]
ignore = ["replace_cfgssa.py", "replace_smart_alloc.py", "replace_sequentialize_moves.py", "replace_target_api.py", "test_replace_mem.py", "MiniCCWrapper.py", "MiniCLexer.py", "MiniCParser.py", "MiniCVisitor.py", "*.diff.py", "**/*.diff.py"]

317
MiniC/test_expect_pragma.py Normal file
View File

@ -0,0 +1,317 @@
import collections
from pathlib import PurePath
import re
import os
import subprocess
import sys
import pytest
testinfo = collections.namedtuple(
'testinfo',
['exitcode', 'execcode', 'output', 'linkargs', 'skip_test_expected'])
default_testinfo = testinfo(
exitcode=0, execcode=0, output='', linkargs=[], skip_test_expected=False)
ASM = 'riscv64-unknown-elf-gcc'
SIMU = 'spike'
LIBPRINT = 'libprint.s'
if 'LIBPRINT' in os.environ:
LIBPRINT = os.environ['LIBPRINT']
def cat(filename):
with open(filename, "rb") as f:
for line in f:
sys.stdout.buffer.write(line)
def env_bool_variable(name, globals):
if name not in globals:
globals[name] = False
if name in os.environ:
globals[name] = True
def env_str_variable(name, globals):
if name in os.environ:
globals[name] = os.environ[name]
def filter_pathnames(pathlist, pattern):
return tuple(path for path in pathlist if PurePath(path).match(pattern))
class TestExpectPragmas(object):
"""Base class for tests that read the expected result as annotations
in test files.
get_expect(file) will parse the file, looking EXPECT and EXITCODE
pragmas.
run_command(command) is a wrapper around subprocess.check_output()
that extracts the output and exit code.
"""
def get_expect(self, filename):
"""Parse "filename" looking for EXPECT and EXITCODE annotations.
Look for a line "EXPECTED" (possibly with whitespaces and
comments). Text after this "EXPECTED" line is the expected
output.
The file may also contain a line like "EXITCODE <n>" where <n>
is an integer, and is the expected exitcode of the command.
The result is cached to avoid re-parsing the file multiple
times.
"""
if filename not in self.__expect:
self.__expect[filename] = self._extract_expect(filename)
return self.__expect[filename]
def skip_if_partial_match(self, actual, expect, ignore_error_message):
if not ignore_error_message:
return False
if expect.exitcode != actual.exitcode:
# Not the same exit code => something's wrong anyway
return False
if actual.exitcode == 3:
# There's a syntax error in both expected and actual,
# but the actual error may slightly differ if we don't
# have the exact same .g4.
return True
# Let the test pass with 'return True' if appropriate.
# Otherwise, continue to the full assertion for a
# complete diagnostic.
if actual.exitcode != 0 and expect.exitcode == actual.exitcode:
if expect.output == '':
# No output expected, but we know there must be an
# error. If there was a particular error message
# expected, we'd have written it in the output,
# hence just ignore the actual message.
return True
# Ignore difference in error message except in the
# line number (ignore the column too, it may
# slightly vary, eg. in "foo" / 4, the error may
# be considered on "foo" or on /):
if re.match(r'^In function [^ :]*: Line [0-9]* col [0-9]*:', actual.output):
out_loc = re.sub(r' col [0-9]*:.*$', '', actual.output)
exp_loc = re.sub(r' col [0-9]*:.*$', '', expect.output)
if out_loc == exp_loc:
return True
if any(x.output and (x.output.endswith('has no value yet!' + os.linesep)
or x.output.endswith(' by 0' + os.linesep))
for x in (actual, expect)):
# Ignore the error message when our compiler
# raises this error (either in actual or expect,
# depending on what we're testing).
return True
return False
__expect = {}
def _extract_expect(self, file):
exitcode = 0
execcode = 0
linkargs = []
inside_expected = False
skip_test_expected = False
expected_lines = []
expected_present = False
with open(file, encoding="utf-8") as f:
for line in f.readlines():
# Ignore non-comments
if not re.match(r'\s*//', line):
continue
# Cleanup comment start and whitespaces
line = re.sub(r'\s*//\s*', '', line)
line = re.sub(r'\s*$', '', line)
if line == 'END EXPECTED':
inside_expected = False
elif line.startswith('EXITCODE'):
words = line.split(' ')
assert len(words) == 2
exitcode = int(words[1])
elif line.startswith('EXECCODE'):
words = line.split(' ')
assert len(words) == 2
execcode = int(words[1])
elif line.startswith('LINKARGS'):
words = line.split(' ')
assert len(words) >= 2
linkargs += [w.replace("$dir", os.path.dirname(file))
for w in words[1:]]
elif line == 'EXPECTED':
inside_expected = True
expected_present = True
elif line == 'SKIP TEST EXPECTED':
skip_test_expected = True
elif inside_expected:
expected_lines.append(line)
if not expected_present:
pytest.fail("Missing EXPECTED directive in test file")
if expected_lines == []:
output = ''
else:
output = os.linesep.join(expected_lines) + os.linesep
return testinfo(exitcode=exitcode, execcode=execcode,
output=output, linkargs=linkargs,
skip_test_expected=skip_test_expected)
class TestCompiler(object):
DISABLE_CODEGEN: bool
SKIP_NOT_IMPLEMENTED: bool
MINIC_COMPILE: str
MINICC_OPTS: list[str]
def remove(self, file):
"""Like os.remove(), but ignore errors, e.g. don't complain if the
file doesn't exist.
"""
try:
os.remove(file)
except OSError:
pass
def run_command(self, cmd, scope="compile"):
"""Run the command cmd (given as [command, arg1, arg2, ...]), and
return testinfo(exitcode=..., output=...) containing the
exit code of the command it its standard output + standard error.
If scope="compile" (resp. "runtime"), then the exitcode (resp.
execcode) is set with the exit status of the command, and the
execcode (resp. exitcode) is set to 0.
"""
try:
output = subprocess.check_output(cmd, timeout=60,
stderr=subprocess.STDOUT)
status = 0
except subprocess.CalledProcessError as e:
output = e.output
status = e.returncode
if scope == "runtime":
return default_testinfo._replace(execcode=status,
output=output.decode())
else:
return default_testinfo._replace(exitcode=status,
output=output.decode())
def run_with_gcc(self, file, info):
return self.compile_and_simulate(file, info, reg_alloc='gcc', use_gcc=True)
def compile_with_gcc(self, file, output_name):
print("Compiling with GCC...")
result = self.run_command(
[ASM, '-S', '-I./',
'--output=' + output_name,
'-Werror',
'-Wno-div-by-zero', # We need to accept 1/0 at compile-time
file])
print(result.output)
print("Compiling with GCC... DONE")
return result
def compile_and_simulate(self, file, info, reg_alloc,
use_gcc=False):
basename, _ = os.path.splitext(file)
output_name = basename + '-' + reg_alloc + '.s'
if use_gcc:
result = self.compile_with_gcc(file, output_name)
if result.exitcode != 0:
# We don't consider the exact exitcode, and ignore the
# output (our error messages may be different from
# GCC's)
return result._replace(exitcode=1,
output=None)
else:
result = self.compile_with_ours(file, output_name, reg_alloc)
if (self.DISABLE_CODEGEN or
reg_alloc == 'none' or
info.exitcode != 0 or result.exitcode != 0):
# Either the result is meaningless, or we already failed
# and don't need to go any further:
return result
# Only executable code past this point.
exec_name = basename + '-' + reg_alloc + '.riscv'
return self.link_and_run(output_name, exec_name, info)
def link_and_run(self, output_name, exec_name, info):
self.remove(exec_name)
cmd = [
ASM, output_name, LIBPRINT,
'-o', exec_name
] + info.linkargs
print(info)
print("Assembling and linking " + output_name + ": " + ' '.join(cmd))
try:
subprocess.check_output(cmd, timeout=60, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
print("Assembling failed:\n")
print(e.output.decode())
print("Assembler code below:\n")
cat(output_name)
pytest.fail()
assert (os.path.isfile(exec_name))
sys.stdout.write("Assembling and linking ... OK\n")
try:
result = self.run_command(
[SIMU,
'-m100', # Limit memory usage to 100MB, more than enough and
# avoids crashing on a VM with <= 2GB RAM for example.
'pk',
exec_name],
scope="runtime")
output = re.sub(r'bbl loader\r?\n', '', result.output)
print("Output of the program:")
print(output)
return testinfo(execcode=result.execcode,
exitcode=result.exitcode,
output=output,
linkargs=[],
skip_test_expected=False)
except subprocess.TimeoutExpired:
pytest.fail("Timeout executing program. Infinite loop in generated code?")
def compile_with_ours(self, file, output_name, reg_alloc):
print("Compiling ...")
self.remove(output_name)
alloc_opt = '--reg-alloc=' + reg_alloc
out_opt = '--output=' + output_name
cmd = [sys.executable, self.MINIC_COMPILE]
if not self.DISABLE_CODEGEN:
cmd += [out_opt, alloc_opt]
cmd += self.MINICC_OPTS
cmd += [file]
result = self.run_command(cmd)
print(' '.join(cmd))
print("Exited with status:", result.exitcode)
print(result.output)
if result.exitcode == 4:
if "AllocationError" in result.output:
if reg_alloc == 'naive':
pytest.skip("Too big for the naive allocator")
else:
pytest.skip("Offsets too big to be manipulated")
elif ("NotImplementedError" in result.output and
self.SKIP_NOT_IMPLEMENTED):
pytest.skip("Feature not implemented in this compiler")
if result.exitcode != 0:
# May either be a failing test or a test with expected
# compilation failure (bad type, ...). Let the caller
# do the assertion and decide:
return result
if not self.DISABLE_CODEGEN:
assert os.path.isfile(output_name)
print("Compiling ... OK")
return result

100
MiniC/test_interpreter.py Executable file
View File

@ -0,0 +1,100 @@
#! /usr/bin/env python3
import pytest
import glob
import os
import sys
from test_expect_pragma import (
TestExpectPragmas, cat,
TestCompiler, filter_pathnames
)
HERE = os.path.dirname(os.path.realpath(__file__))
if HERE == os.path.realpath('.'):
HERE = '.'
TEST_DIR = HERE
IMPLEM_DIR = HERE
SKIP_EXPECT = False
if 'SKIP_EXPECT' in os.environ:
SKIP_EXPECT = True
DISABLE_TYPECHECK = False # True to skip typechecking
ALL_FILES = []
# tests for typing AND evaluation
ALL_FILES += glob.glob(os.path.join(TEST_DIR, 'TP03/tests/provided/**/*.c'),
recursive=True)
ALL_FILES += glob.glob(os.path.join(TEST_DIR, 'TP03/tests/students/**/*.c'),
recursive=True)
# Path setting
if 'TEST_FILES' in os.environ:
ALL_FILES = glob.glob(os.environ['TEST_FILES'], recursive=True)
MINIC_EVAL = os.path.join(IMPLEM_DIR, 'MiniCC.py')
if 'FILTER' in os.environ:
ALL_FILES = filter_pathnames(ALL_FILES, os.environ['FILTER'])
class TestInterpret(TestExpectPragmas, TestCompiler):
DISABLE_CODEGEN = False
def evaluate(self, file):
if not DISABLE_TYPECHECK:
res = self.run_command([sys.executable, MINIC_EVAL,
"--mode", "eval", file])
else:
res = self.run_command([sys.executable, MINIC_EVAL,
"--mode", "eval",
"--disable-typecheck", file])
if res.exitcode == 1:
# Execution can't distinguish exit code at runtime and static
# rejection of the program. But we know that an exit code of 1 is
# reserved for runtime errors, hence convert this exitcode
# into an execcode.
res = res._replace(exitcode=0, execcode=1)
return res
# Not in test_expect_pragma to get assertion rewritting
def assert_equal(self, actual, expected, compiler):
if expected.output is not None and actual.output is not None:
assert actual.output == expected.output, \
f"Output of the program is incorrect (using {compiler})."
assert actual.exitcode == expected.exitcode, \
f"Exit code of the compiler ({compiler}) is incorrect"
assert actual.execcode == expected.execcode, \
f"Exit code of the execution is incorrect (using {compiler})"
@pytest.mark.parametrize('filename', ALL_FILES)
def test_expect(self, filename):
"""Test the EXPECTED annotations in test files by launching the
program with GCC."""
if SKIP_EXPECT:
pytest.skip("Skipping all test_expect "
"because $SKIP_EXPECT is set.")
cat(filename) # For diagnosis
expect = self.get_expect(filename)
if expect.skip_test_expected:
pytest.skip("Skipping test_expect with GCC because "
"the test contains SKIP TEST EXPECTED")
if expect.exitcode != 0:
# GCC is more permissive than us, so trying to compile an
# incorrect program would bring us no information (it may
# compile, or fail with a different message...)
pytest.skip("Not testing the expected value for "
"tests expecting exitcode!=0")
gcc_result = self.run_with_gcc(filename, expect)
self.assert_equal(gcc_result, expect, "gcc")
@pytest.mark.parametrize('filename', ALL_FILES)
def test_eval(self, filename):
cat(filename) # For diagnosis
expect = self.get_expect(filename)
actual = self.evaluate(filename)
if expect:
self.assert_equal(actual, expect, "MiniCC")
if __name__ == '__main__':
pytest.main(sys.argv)

2
TP03/arith-visitor/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/AritListener.py
/AritVisitor.py

View File

@ -0,0 +1,33 @@
grammar Arit;
prog: statement+ EOF #statementList
;
statement
: expr SCOL #exprInstr
| 'set' ID '=' expr SCOL #assignInstr
;
expr: expr multop=(MULT | DIV) expr #multiplicationExpr
| expr addop=(PLUS | MINUS) expr #additiveExpr
| atom #atomExpr
;
atom: INT #numberAtom
| ID #idAtom
| '(' expr ')' #parens
;
SCOL : ';';
PLUS : '+';
MINUS : '-';
MULT : '*';
DIV : '/';
ID: [a-zA-Z_] [a-zA-Z_0-9]*;
INT: [0-9]+;
COMMENT: '#' ~[\r\n]* -> skip;
NEWLINE: '\r'? '\n' -> skip;
WS : (' '|'\t')+ -> skip;

View File

@ -0,0 +1,23 @@
PACKAGE = Arit
MAINFILE = arit
ifndef ANTLR4
abort:
$(error variable ANTLR4 is not set)
endif
all: $(PACKAGE).g4
$(ANTLR4) $^ -Dlanguage=Python3 -visitor
run: $(MAINFILE).py
python3 $^
ex: $(MAINFILE).py
python3 $^ < myexample
test: all
python3 ./test_arith_visitor.py
clean:
find . \( -iname "~" -or -iname "*.cache*" -or -iname "*.diff" -or -iname "log.txt" -or -iname "*.pyc" -or -iname "*.tokens" -or -iname "*.interp" \) -exec rm -rf '{}' \;
rm -rf $(PACKAGE)*.py

View File

@ -0,0 +1,58 @@
from AritVisitor import AritVisitor
from AritParser import AritParser
class UnknownIdentifier(Exception):
pass
class MyAritVisitor(AritVisitor):
"""Visitor that evaluates an expression. Derives and overrides methods
from ArithVisitor (generated by ANTLR4)."""
def __init__(self):
self._memory = dict() # store id -> values
def visitNumberAtom(self, ctx):
try:
value = int(ctx.getText())
return value
except ValueError:
return float(ctx.getText())
def visitIdAtom(self, ctx):
try:
return self._memory[ctx.getText()]
except KeyError:
raise UnknownIdentifier(ctx.getText())
def visitMultiplicationExpr(self, ctx):
# Recursive calls to children. The visitor will choose the
# appropriate method (visitSomething) automatically.
leftval = self.visit(ctx.expr(0))
rightval = self.visit(ctx.expr(1))
# an elegant way to match the token:
if ctx.multop.type == AritParser.MULT:
return leftval * rightval
else:
return leftval / rightval
def visitAdditiveExpr(self, ctx):
leftval = self.visit(ctx.expr(0))
rightval = self.visit(ctx.expr(1))
if ctx.addop.type == AritParser.PLUS:
return leftval + rightval
else:
return leftval - rightval
def visitExprInstr(self, ctx):
val = self.visit(ctx.expr())
print('The value is ' + str(val))
def visitParens(self, ctx):
return self.visit(ctx.expr())
def visitAssignInstr(self, ctx):
val = self.visit(ctx.expr())
name = ctx.ID().getText()
print('now ' + name + ' has value ' + str(val))
self._memory[name] = val

View File

@ -0,0 +1,28 @@
from AritLexer import AritLexer
from AritParser import AritParser
# from AritVisitor import AritVisitor
from MyAritVisitor import MyAritVisitor, UnknownIdentifier
from antlr4 import InputStream, CommonTokenStream
import sys
# example of use of visitors to parse arithmetic expressions.
# stops when the first SyntaxError is launched.
def main():
lexer = AritLexer(InputStream(sys.stdin.read()))
stream = CommonTokenStream(lexer)
parser = AritParser(stream)
tree = parser.prog()
print("Parsing : done.")
visitor = MyAritVisitor()
try:
visitor.visit(tree)
except UnknownIdentifier as exc:
print('Unknown identifier: {}'.format(exc.args[0]))
exit(-1)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,5 @@
1 ;
12 ;
1+2 ;
1+2*3+4;
(1+2)*(3+4);

View File

@ -0,0 +1,36 @@
#!/usr/bin/env python
from AritLexer import AritLexer
from AritParser import AritParser
import pytest
from MyAritVisitor import MyAritVisitor
from antlr4 import InputStream, CommonTokenStream
import sys
@pytest.mark.parametrize("input, expected", [
pytest.param('1+1;', 2),
pytest.param('2-1;', 1),
pytest.param('2*3;', 6),
pytest.param('6/2;', 3),
pytest.param('set x=42; x+1;', 43),
pytest.param('set x=42; set x=12; x+1;', 13)
])
def test_expr(input, expected):
lexer = AritLexer(InputStream(input))
stream = CommonTokenStream(lexer)
parser = AritParser(stream)
tree = parser.prog()
print("Parsing : done.")
visitor = MyAritVisitor()
def patched_visit(self, ctx):
self.last_expr = self.visit(ctx.expr())
visitor.visitExprInstr = patched_visit.__get__(visitor)
visitor.visit(tree)
assert visitor.last_expr == expected
if __name__ == '__main__':
pytest.main(sys.argv)

BIN
TP03/tp3.pdf Normal file

Binary file not shown.

16
TP03/tree/Tree.g4 Normal file
View File

@ -0,0 +1,16 @@
grammar Tree;
int_tree_top : int_tree EOF #top
;
int_tree: INT #leaf
| '(' INT int_tree+ ')' #node
;
INT: [0-9]+;
WS : (' '|'\t'|'\n')+ -> skip;