diff --git a/PLANNING.md b/PLANNING.md index 355ddc7..f667013 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -103,13 +103,45 @@ _Academic first semester 2024-2025_ - :hammer: Lab 5b (2/2): Monday 18/11/2024, 13h30-15h30. Room E001 (Samuel Humeau & Emma Nardino) +- :book: Course: Thursday 21/11/2024, 10h15-12h15. Amphi J (Yannick Zakowski) + + * Functions: code generation [slides in english](course/cap_cours08_func_codegen.pdf). + # Week 10: - :notebook: TD: Monday 25/11/2024, 13h30-15h30. Room E001 (Samuel Humeau & Emma Nardino) +- :book: Course: Thursday 28/11/2024, 10h15-12h15. Amphi B (Yannick Zakowski) + + * Functions: semantics [slides in english](course/cap_cour309_func_semantics.pdf). + # Week 11: - :hammer: Choice Lab (1/3): Monday 02/12/2024, 8h00-13h30. Room E001 (Samuel Humeau & Emma Nardino) * Optimisations under SSA form [TP5c](TP05/tp5c.pdf), code in [MiniC/TPoptim/](MiniC/TPoptim/). - * Other possibilities next week. + * Parsing and typechecking functions [TP06a](TP06/tp6a.pdf), code in [MiniC/](MiniC/). + * Code generation for functions [TP06b](TP06/tp6b.pdf), code in [MiniC/](MiniC/). + +- :book: Course: Thursday 05/12/2024, 10h15-12h15. Amphi B (Yannick Zakowski) + + * On parallelism [slides in english](course/cap_cours10_parallelism.pdf). + +# Week 12: + +- :hammer: Choice Lab (2/3): Thursday 09/12/2024, 13h30-15h30. Room E001 (Samuel Humeau & Emma Nardino) + + * Optimisations under SSA form [TP5c](TP05/tp5c.pdf), code in [MiniC/TPoptim/](MiniC/TPoptim/). + * Parsing and typechecking functions [TP06a](TP06/tp6a.pdf), code in [MiniC/](MiniC/). + * Code generation for functions [TP06b](TP06/tp6b.pdf), code in [MiniC/](MiniC/). + * Going Parallel with futures [TPfutures](TPfutures/tpfutures.pdf), code in [TPfutures/MiniC-futures/](TPfutures/MiniC-futures/). + +- :book: Course: Thursday 12/12/2024, 10h15-12h15. Amphi B (Yannick Zakowski) + + * Verified Compilation [introduction slides](course/cap_cours11_verified.pdf). + * Course on black board + * Additional resources: [Xavier Leroy's class at Collège de France](https://www.college-de-france.fr/en/agenda/lecture/mechanized-semantics-when-the-machine-reasons-about-its-languages) + +# Week 13: + +- :hammer: Choice Lab (3/3): Monday 16/12/2024, 13h30-15h30. Room E001 (Samuel Humeau & Emma Nardino) diff --git a/TP06/tp6a.pdf b/TP06/tp6a.pdf new file mode 100644 index 0000000..82dcdf0 Binary files /dev/null and b/TP06/tp6a.pdf differ diff --git a/TP06/tp6b.pdf b/TP06/tp6b.pdf new file mode 100644 index 0000000..264b123 Binary files /dev/null and b/TP06/tp6b.pdf differ diff --git a/TPfutures/MiniC-futures/.gitignore b/TPfutures/MiniC-futures/.gitignore new file mode 100644 index 0000000..eb94920 --- /dev/null +++ b/TPfutures/MiniC-futures/.gitignore @@ -0,0 +1,15 @@ +/MiniCLexer.py +/MiniCParser.py +/MiniCVisitor.py +/MiniCListener.py +*.dot +*.dot.pdf +*.riscv +*-naive.s +*-gcc.s +*-all_in_mem.s +*-smart.s +*.cfut +*.o +/MiniC.interp +/MiniCLexer.interp diff --git a/TPfutures/MiniC-futures/Errors.py b/TPfutures/MiniC-futures/Errors.py new file mode 100644 index 0000000..3ff6768 --- /dev/null +++ b/TPfutures/MiniC-futures/Errors.py @@ -0,0 +1,14 @@ +class MiniCRuntimeError(Exception): + pass + + +class MiniCInternalError(Exception): + pass + + +class MiniCUnsupportedError(Exception): + pass + + +class MiniCTypeError(Exception): + pass diff --git a/TPfutures/MiniC-futures/Makefile b/TPfutures/MiniC-futures/Makefile new file mode 100644 index 0000000..2b5b19f --- /dev/null +++ b/TPfutures/MiniC-futures/Makefile @@ -0,0 +1,56 @@ +PACKAGE = MiniC +# Example: stop at the first failed test: +# make PYTEST_OPTS=-x tests +PYTEST_OPTS = +# Run the whole test infrastructure for a subset of test files e.g. +# make TEST_FILES='TP03/**/bad*.c' tests +ifdef TEST_FILES +export TEST_FILES +endif + +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 + +main-deps: MiniCLexer.py MiniCParser.py MiniCTypingVisitor.py MiniCPPListener.py + +%.o: %.c $(HEADERS) + gcc -g -c -Wall -Wextra $< -o $@ + +$(TESTFILE:%.c=%.o): $(TESTFILE:%.c=%.cfut) $(HEADERS) + gcc -g -c -Iinclude -Wall -Wextra -x c $(TESTFILE:%.c=%.cfut) -o $(TESTFILE:%.c=%.o) + +$(TESTFILE:%.c=%.out): $(TESTFILE:%.c=%.o) lib/futurelib.o + gcc -g $(TESTFILE:%.c=%.o) lib/futurelib.o -o $(TESTFILE:%.c=%.out) -lpthread + +$(TESTFILE:%.c=%.cfut): main-deps + python3 MiniCC.py $(TESTFILE) + +run: $(TESTFILE:%.c=%.out) + $(TESTFILE:%.c=%.out) + +test: test_futures.py main-deps + python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_futures.py + +tar: clean + dir=$$(basename "$$PWD") && cd .. && \ + tar cvfz "$$dir.tgz" --exclude="*.riscv" --exclude=".git" --exclude=".pytest_cache" \ + --exclude="htmlcov" "$$dir" + @echo "Created ../$$(basename "$$PWD").tgz" + +clean-tests: + cd tests && \ + find . \( -iname "*.cfut" -or -iname "*.out" \) -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" -or -iname "*.o" \) -print0 | xargs -0 rm -rf \; + rm -rf *~ $(PACKAGE)Parser.py $(PACKAGE)Lexer.py $(PACKAGE)Visitor.py $(PACKAGE)Listener.py .coverage .benchmarks diff --git a/TPfutures/MiniC-futures/MiniC.g4 b/TPfutures/MiniC-futures/MiniC.g4 new file mode 100644 index 0000000..b744fc1 --- /dev/null +++ b/TPfutures/MiniC-futures/MiniC.g4 @@ -0,0 +1,164 @@ +grammar MiniC; + +prog: include* function* EOF #progRule; + +//include statements reduced to string +include: INCLUDE STRING #includestat ; + +function + : typee ID OPAR param_l? CPAR OBRACE vardecl_l block RETURN expr SCOL CBRACE #funcDef + | typee ID OPAR param_l? CPAR SCOL #funcDecl + ; + +vardecl_l: vardecl* #varDeclList; + +vardecl: typee id_l SCOL #varDecl; + +param: typee ID #paramDecl; + +param_l + : param #paramListBase + | param COM param_l #paramList + ; + +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 + : PRINTINT OPAR expr CPAR SCOL #printlnintStat + | PRINTFLOAT OPAR expr CPAR SCOL #printlnfloatStat + | PRINTBOOL OPAR expr CPAR SCOL #printlnboolStat + | PRINTSTRING OPAR expr CPAR SCOL #printstringStat + ; + +expr_l + : expr #exprListBase + | expr COM expr_l #exprList + ; + + +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 + | ID OPAR expr_l? CPAR #funcCall + | GET OPAR expr CPAR #getCall + | ASYNC OPAR ID COM expr_l? CPAR #asyncFuncCall + | atom #atomExpr + ; + +atom + : OPAR expr CPAR #parExpr + | INT #intAtom + | FLOAT #floatAtom + | (TRUE | FALSE) #booleanAtom + | ID #idAtom + | STRING #stringAtom + ; + +typee + : mytype=(INTTYPE|FLOATTYPE|BOOLTYPE|STRINGTYPE|FUTINTTYPE) #basicType + ; + + +ASYNC : 'Async'; +GET : 'Get'; +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'; +PRINTINT : 'println_int'; +PRINTBOOL : 'println_bool'; +PRINTSTRING : 'println_string'; +PRINTFLOAT : 'println_float'; + +FUTINTTYPE: 'futint'; +INTTYPE: 'int'; +FLOATTYPE: 'float'; +STRINGTYPE: 'string'; +BOOLTYPE : 'bool'; +INCLUDE : '#include'; + +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 +// BUT now we want to keep includes => # removed + : '//' ~[\r\n]* -> skip + ; + +SPACE +// here is a little for code rewriting. + : [ \t\r\n] -> channel(HIDDEN) + ; + diff --git a/TPfutures/MiniC-futures/MiniCC.py b/TPfutures/MiniC-futures/MiniCC.py new file mode 100644 index 0000000..d99ab90 --- /dev/null +++ b/TPfutures/MiniC-futures/MiniCC.py @@ -0,0 +1,112 @@ +#! /usr/bin/env python3 +""" +MiniC-futures Lab. Language Extension for MiniC with future primitives +Usage: + python3 Main.py + python3 Main.py --help +""" +import traceback +from MiniCLexer import MiniCLexer +from MiniCParser import MiniCParser +from MiniCTypingVisitor import MiniCTypingVisitor, MiniCTypeError +from MiniCPPListener import MiniCPPListener +from Errors import MiniCUnsupportedError, MiniCInternalError + +import argparse + +from antlr4 import FileStream, CommonTokenStream, ParseTreeWalker +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 occured + 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, + typecheck=True, typecheck_only=False, stdout=False, output_name=None, debug=False): + (basename, rest) = os.path.splitext(inputname) + if not typecheck_only: + if stdout: + output_name = None + elif output_name is None: + output_name = basename + ".cfut" + + 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 --disable-codegen.") + return + + pw = ParseTreeWalker() + extractor = MiniCPPListener(stream) + pw.walk(extractor, tree) + with open(output_name, 'w') if output_name else sys.stdout as output: + extractor.printrw(output) + + +# 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('--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('--disable-typecheck', action='store_true', + default=False, + help="Don't run the typechecker before generating code") + parser.add_argument('--disable-codegen', 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() + + try: + main(args.filename, + not args.disable_typecheck, args.disable_codegen, + args.stdout, args.output, args.debug, + ) + except MiniCUnsupportedError as e: + print(e) + exit(5) + except (MiniCInternalError): + traceback.print_exc() + exit(4) diff --git a/TPfutures/MiniC-futures/MiniCPPListener.py b/TPfutures/MiniC-futures/MiniCPPListener.py new file mode 100644 index 0000000..4b39491 --- /dev/null +++ b/TPfutures/MiniC-futures/MiniCPPListener.py @@ -0,0 +1,27 @@ +from MiniCListener import MiniCListener + +from antlr4.TokenStreamRewriter import TokenStreamRewriter + + +class MiniCPPListener(MiniCListener): + def __init__(self, t): + self.rewriter = TokenStreamRewriter(tokens=t) + + def printrw(self, output): + output.write(self.rewriter.getText('default', 0, 1000)) + + def enterProgRule(self, ctx): + """Adds an include futurelib.h at the beginning of program.""" + indexprog = ctx.start.tokenIndex + self.rewriter.insertBeforeIndex(indexprog, '#include \"futurelib.h\"\n') + + def exitFuncDef(self, ctx): + """Adds a call to freeAllFutures at the end of the body of the main function.""" + (indexret, endret) = ctx.RETURN().getSourceInterval() + if ctx.ID().getText() == "main": + self.rewriter.insertBeforeIndex(indexret, 'freeAllFutures();\n') + + def enterAsyncFuncCall(self, ctx): + """Adds a & for getting a function pointer to the asynchronous called function.""" + indexfunid = ctx.start.tokenIndex # token of async + self.rewriter.insertBeforeIndex(indexfunid + 2, '&') diff --git a/TPfutures/MiniC-futures/MiniCTypingVisitor.py b/TPfutures/MiniC-futures/MiniCTypingVisitor.py new file mode 100644 index 0000000..2bb737a --- /dev/null +++ b/TPfutures/MiniC-futures/MiniCTypingVisitor.py @@ -0,0 +1,17 @@ +# Visitor to *typecheck* MiniC files +from typing import List +from MiniCVisitor import MiniCVisitor +from MiniCParser import MiniCParser +from Lib.Errors import MiniCInternalError, MiniCTypeError + +from enum import Enum + + +# NEW: ADD FutInteger +class BaseType(Enum): + Float, Integer, Boolean, String, FutInteger = range(5) + + +class MiniCTypingVisitor(MiniCVisitor): + # TODO Add your own typer here + pass diff --git a/TPfutures/MiniC-futures/README.md b/TPfutures/MiniC-futures/README.md new file mode 100644 index 0000000..a9de5e8 --- /dev/null +++ b/TPfutures/MiniC-futures/README.md @@ -0,0 +1,25 @@ +# MiniC Compiler +LAB8 (Futures) CAP 2022-23 + +# Authors + +YOUR NAME HERE + +# Contents + +TODO: +- Explain any design choices you may have made. +- Did you implement a bonus? + +# Howto + +`make run TESTFILE=tests/provided/test_fut0.c`: launch the compiler, then GCC and run a single file. + +# Test design + +TODO: explain your tests. + +# Known bugs + +TODO: bugs you could not fix (if any) and limitations. + diff --git a/TPfutures/MiniC-futures/include/compat.h b/TPfutures/MiniC-futures/include/compat.h new file mode 100644 index 0000000..59ea9c8 --- /dev/null +++ b/TPfutures/MiniC-futures/include/compat.h @@ -0,0 +1,12 @@ +#include "stdio.h" + +typedef char * string; +typedef int bool; +static const int true = 1; +static const int false = 0; + +void print_int(int i) {printf("%i",i);} +void println_int(int i) {printf("%i\n",i);} + +void print_string(string); +void println_string(string); diff --git a/TPfutures/MiniC-futures/include/futurelib.h b/TPfutures/MiniC-futures/include/futurelib.h new file mode 100644 index 0000000..2e14d17 --- /dev/null +++ b/TPfutures/MiniC-futures/include/futurelib.h @@ -0,0 +1,34 @@ +#include + +typedef struct // a struct for storing a future to int +{ + int Id; + int Value; + int resolved; + pthread_t tid; +} FutureInt; + +typedef FutureInt* futint; // a typedef to deal with the future type of MiniCFut + +FutureInt *fresh_future_malloc(); // allocates (malloc) a fresh future and initializes its field + +void print_futureInt(FutureInt *fut); // for debug purposes: print a fut int status + +void free_future(FutureInt *fut); // frees the pointer allocated by fresh_future + +void resolve_future(FutureInt *fut, int val); // function called when an async call is finished + +int Get(FutureInt *fut); +// called by the main program: +// checks that the future is resolved and +// returns the value stored in the future + +FutureInt *Async(int (*fun)(int), int p); +// asynchronous function call: +// takes a function pointer as parameter and the fun call parameter as second parameter +// returns an unresolved future +// creates a thread to perform the asynchronous function call + +void freeAllFutures(); +// called at the end of the main block: waits for the resolution of all futures +// and frees all future pointers created by fresh_future diff --git a/TPfutures/MiniC-futures/lib/futurelib.c b/TPfutures/MiniC-futures/lib/futurelib.c new file mode 100644 index 0000000..b7215a0 --- /dev/null +++ b/TPfutures/MiniC-futures/lib/futurelib.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include "../include/futurelib.h" + +FutureInt *All[70]; +// A table of future pointers to store all the futures created +// In a realistic scenario this would be a dynamic structure +int NbAll = 0; +// Number of futures created + +typedef struct +// A structure for the argument of thread creation: function pointer and parameter +{ + FutureInt *fut; + int (*fun)(int); + int param; +} arg_struct; + +FutureInt *fresh_future_malloc() +{ + // TODO Exercise 4.2 + // Use malloc(sizeof(FutureInt)) and reference created futures +} + +void print_futureInt(FutureInt *f) +{ + // TODO + // For debug purposes only +} + +void free_future(FutureInt *fut) +{ + free(fut); +} + +void resolve_future(FutureInt *fut, int val) +{ + // TODO Exercise 5.1 + // Fill fut accordingly +} + +int Get(FutureInt *fut) +{ + // TODO Exercise 5.2 + // Wait until future is resolved (do a sleep(1) between two checks) + // Do not forget to do a pthread_join(fut->tid, NULL); + return 0; +} + +void *runTask(void *param) +{ + // TODO Exercise 4.1 + // function that is launched by the created thread: should call the function and + // deal with the future, using the function resolve_future + // param can be cast to (arg_struct *) + // this function should free the pointer param + return NULL; +} + +FutureInt *Async(int (*fun)(int), int p) +{ + // TODO Exercise 4.3 + // Main system call should be: int err = pthread_create(&fut->tid, NULL, &runTask, (args)); + // Allocate a future and space for arguments: args = malloc(sizeof(arg_struct)); + // Do not forget to populate args + return NULL; +} + +void freeAllFutures() +{ + // TODO Exercises 4.4 & 5.3 + // 1 - Wait for all futures (Get) to avoid dangling threads (Exercise 5.3) + // 2 - Call free_future for all futures (Exercise 4.4) +} diff --git a/TPfutures/MiniC-futures/test_expect_pragma.py b/TPfutures/MiniC-futures/test_expect_pragma.py new file mode 100644 index 0000000..730a53e --- /dev/null +++ b/TPfutures/MiniC-futures/test_expect_pragma.py @@ -0,0 +1,193 @@ +import collections +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) + + +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] + + +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 " where + 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 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 skip_if_partial_match(self, actual, expect, ignore_error_message): + if not ignore_error_message: + return False + + # TODO: Deal with undefined behavior here? + + 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) diff --git a/TPfutures/MiniC-futures/test_futures.py b/TPfutures/MiniC-futures/test_futures.py new file mode 100755 index 0000000..b21d8d5 --- /dev/null +++ b/TPfutures/MiniC-futures/test_futures.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python3 + +import os +import sys +import pytest +import glob +import subprocess +import re +from test_expect_pragma import ( + TestExpectPragmas, cat, testinfo, + env_bool_variable, env_str_variable + ) + +""" +Usage: + python3 test_futur.py +(or make test) +""" + +""" +CAP, 2020 +Unit test infrastructure for testing futures: +1) compare the actual output to the expected one (in comments) +2) compare the actual output to the one obtained by simulation +""" + +DISABLE_TYPECHECK = False +TYPECHECK_ONLY = False + +HERE = os.path.dirname(os.path.realpath(__file__)) +if HERE == os.path.realpath('.'): + HERE = '.' +TEST_DIR = HERE +IMPLEM_DIR = HERE +MINIC_FUT = os.path.join(IMPLEM_DIR, 'MiniCC.py') + +ALL_FILES = glob.glob(os.path.join(TEST_DIR, 'tests/**/[a-zA-Z]*.c'), + recursive=True) + +GCC = 'gcc' + +if 'TEST_FILES' in os.environ: + ALL_FILES = glob.glob(os.environ['TEST_FILES'], recursive=True) + + +class TestFuture(TestExpectPragmas): + + # Not in test_expect_pragma to get assertion rewritting + def assert_equal(self, actual, expected): + if TYPECHECK_ONLY and expected.exitcode == 0: + # Compiler does not fail => no output expected + assert actual.output == "", \ + ("Compiler unexpectedly generated some" + "output with --disable-codegen") + assert actual.exitcode == 0, \ + "Compiler unexpectedly failed with --disable-codegen" + return + if DISABLE_TYPECHECK and expected.exitcode != 0: + # Test should fail at typecheck, and we don't do + # typechecking => nothing to check. + pytest.skip("Test that doesn't typecheck with --disable-typecheck") + if expected.output is not None and actual.output is not None: + assert actual.output == expected.output, \ + "Output of the program is incorrect." + assert actual.exitcode == expected.exitcode, \ + "Exit code of the compiler is incorrect" + assert actual.execcode == expected.execcode, \ + "Exit code of the execution is incorrect" + + def c2c(self, file): + return self.run_command(['python3', MINIC_FUT, file]) + + def compile_with_gcc(self, file, output_name): + print("Compiling with GCC...") + result = self.run_command( + [GCC, '-Iinclude', '-Ilib', '-x', 'c', file, "lib/futurelib.c", + '--output=' + output_name, '-lpthread']) + print(result.output) + print("Compiling with GCC... DONE") + return result + + def compile_and_run(self, file): + basename, _ = os.path.splitext(file) + rw_name = basename + '.cfut' + exec_name = basename + '.out' + print("File: " + rw_name) + resgcc = self.compile_with_gcc(rw_name, exec_name) + if resgcc.exitcode != 0: + return resgcc._replace(exitcode=1, output=None) + res2 = self.run_command(exec_name, scope="runtime") + return res2 + + @pytest.mark.parametrize('filename', ALL_FILES) + def test_future(self, filename): + expect = self.get_expect(filename) + c2csuccess = self.c2c(filename) + if c2csuccess.exitcode == 0: + actual = self.compile_and_run(filename) + else: + actual = c2csuccess + self.assert_equal(actual, expect) + + +if __name__ == '__main__': + pytest.main(sys.argv) diff --git a/TPfutures/MiniC-futures/tests/provided/bad_type_fut1.c b/TPfutures/MiniC-futures/tests/provided/bad_type_fut1.c new file mode 100644 index 0000000..17b4b56 --- /dev/null +++ b/TPfutures/MiniC-futures/tests/provided/bad_type_fut1.c @@ -0,0 +1,38 @@ +#include "compat.h" + +// Call future +int slow(int x) +{ + int i; + i=0; + while (i<1000) { i=i+1 ; x=2*i+x;} + return x; +} + +int summ(int x) +{ + int ret,i; + if (x == 1) + ret=1; + else + { + ret = x + summ(x - 1); + i=0; + while (i<100) { i=i+1 ; x=slow(2);} + } + return ret; +} +int main() +{ + int val; + + val=Async(summ,1); + println_int(val); + + return 0; +} + +// EXITCODE 2 +// EXPECTED +// In function main: Line 29 col 4: type mismatch for val: integer and futinteger + diff --git a/TPfutures/MiniC-futures/tests/provided/test_fut0.c b/TPfutures/MiniC-futures/tests/provided/test_fut0.c new file mode 100644 index 0000000..a24631e --- /dev/null +++ b/TPfutures/MiniC-futures/tests/provided/test_fut0.c @@ -0,0 +1,20 @@ +#include "compat.h" + +// Call future + +int functi(int x){ + int y; + y=x; + y = 42; + println_int(0); + return y; +} + +int main(){ + futint fval; + fval = Async(functi,123); + return 0; +} + +// EXPECTED +// 0 diff --git a/TPfutures/MiniC-futures/tests/provided/test_fut1.c b/TPfutures/MiniC-futures/tests/provided/test_fut1.c new file mode 100644 index 0000000..57fc7d7 --- /dev/null +++ b/TPfutures/MiniC-futures/tests/provided/test_fut1.c @@ -0,0 +1,22 @@ +#include "compat.h" + +// Call future + +int functi(int x){ + int y; + y=x; + y = 42; + return y; +} + +int main(){ + futint fval; + int val; + fval = Async(functi,123); + val = Get(fval); + println_int(val); + return 0; +} + +// EXPECTED +// 42 diff --git a/TPfutures/MiniC-futures/tests/provided/test_fut2.c b/TPfutures/MiniC-futures/tests/provided/test_fut2.c new file mode 100644 index 0000000..75a4471 --- /dev/null +++ b/TPfutures/MiniC-futures/tests/provided/test_fut2.c @@ -0,0 +1,41 @@ +#include "compat.h" + +// Call future + +int summ(int x) +{ + int ret; + if (x == 1) + ret=1; + else + { + ret=x + summ(x - 1); + } + return ret; +} + +int useFuture(futint f) +{ + int x; + x=Get(f); + return x+1; +} + + +int main() +{ + int val,x; + futint f,g; + + f=Async(summ,15); + g=Async(summ,16); + + val=Get(f)+Get(g)+useFuture(g); + println_int(val); + + return 0; +} + + +// EXPECTED +// 393 diff --git a/TPfutures/MiniC-futures/tests/provided/test_fut4.c b/TPfutures/MiniC-futures/tests/provided/test_fut4.c new file mode 100644 index 0000000..5a8ab7d --- /dev/null +++ b/TPfutures/MiniC-futures/tests/provided/test_fut4.c @@ -0,0 +1,56 @@ +#include "compat.h" + +// Call future + +int slow(int x) +{ + int i,t; + i=0; + t=0; + while (i