automated commit TP4 + Doc
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
This file defines the base class :py:class:`Allocator`
|
||||
and the naïve implementation :py:class:`NaiveAllocator`.
|
||||
"""
|
||||
|
||||
from Lib.Operands import Temporary, Operand, DataLocation, GP_REGS
|
||||
from Lib.Statement import Instruction
|
||||
from Lib.Errors import AllocationError
|
||||
from Lib.FunctionData import FunctionData
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
class Allocator():
|
||||
"""General base class for Naive, AllInMem and Smart Allocators.
|
||||
Replace all temporaries in the code with actual data locations.
|
||||
|
||||
Allocation is done in two steps:
|
||||
|
||||
- First, :py:meth:`prepare` is responsible for calling
|
||||
:py:meth:`Lib.Operands.TemporaryPool.set_temp_allocation`
|
||||
with a mapping from temporaries to where they should actually be stored
|
||||
(in registers or in memory).
|
||||
- Then, :py:meth:`replace` is called for each instruction in order to
|
||||
replace the temporary operands with the previously assigned locations
|
||||
(and possibly add some instructions before or after).
|
||||
Concretely, it returns a list of instructions that should replace the original
|
||||
instruction. The actual iteration over all the instructions is handled transparently
|
||||
by :py:meth:`Lib.LinearCode.LinearCode.iter_statements`.
|
||||
"""
|
||||
|
||||
_fdata: FunctionData
|
||||
|
||||
def __init__(self, fdata: FunctionData):
|
||||
self._fdata = fdata
|
||||
|
||||
def prepare(self) -> None: # pragma: no cover
|
||||
pass
|
||||
|
||||
def replace(self, instr: Instruction) -> List[Instruction]:
|
||||
"""Transform an instruction with temporaries into a list of instructions."""
|
||||
return [instr]
|
||||
|
||||
def rewriteCode(self, listcode) -> None:
|
||||
"""Modify the code to replace temporaries with
|
||||
registers or memory locations.
|
||||
"""
|
||||
listcode.iter_statements(self.replace)
|
||||
|
||||
|
||||
class NaiveAllocator(Allocator):
|
||||
"""Naive Allocator: try to assign a register to each temporary,
|
||||
fails if there are more temporaries than registers.
|
||||
"""
|
||||
|
||||
def replace(self, old_instr: Instruction) -> List[Instruction]:
|
||||
"""Replace Temporary operands with the corresponding allocated Register."""
|
||||
subst: Dict[Operand, Operand] = {}
|
||||
for arg in old_instr.args():
|
||||
if isinstance(arg, Temporary):
|
||||
subst[arg] = arg.get_alloced_loc()
|
||||
new_instr = old_instr.substitute(subst)
|
||||
return [new_instr]
|
||||
|
||||
def prepare(self) -> None:
|
||||
"""Allocate all temporaries to registers.
|
||||
Fail if there are too many temporaries."""
|
||||
regs = list(GP_REGS) # Get a writable copy
|
||||
temp_allocation: Dict[Temporary, DataLocation] = dict()
|
||||
for tmp in self._fdata._pool.get_all_temps():
|
||||
try:
|
||||
reg = regs.pop()
|
||||
except IndexError:
|
||||
raise AllocationError(
|
||||
"Too many temporaries ({}) for the naive allocation, sorry."
|
||||
.format(len(self._fdata._pool.get_all_temps())))
|
||||
temp_allocation[tmp] = reg
|
||||
self._fdata._pool.set_temp_allocation(temp_allocation)
|
||||
@@ -0,0 +1,179 @@
|
||||
"""
|
||||
This file defines the base class :py:class:`FunctionData`,
|
||||
containing metadata on a RiscV function, as well as utility
|
||||
functions common to the different intermediate representations.
|
||||
"""
|
||||
|
||||
from typing import (List, Callable, TypeVar)
|
||||
from Lib.Errors import AllocationError
|
||||
from Lib.Operands import (
|
||||
Offset, Temporary, TemporaryPool,
|
||||
S, T, FP)
|
||||
from Lib.Statement import (Statement, Instruction, Label, Comment)
|
||||
|
||||
|
||||
class FunctionData:
|
||||
"""
|
||||
Stores some metadata on a RiscV function:
|
||||
name of the function, label names, temporary variables
|
||||
(using :py:class:`Lib.Operands.TemporaryPool`),
|
||||
and div_by_zero label.
|
||||
|
||||
This class is usually used indirectly through the
|
||||
different intermediate representations we work with,
|
||||
such as :py:attr:`Lib.LinearCode.LinearCode.fdata`.
|
||||
"""
|
||||
|
||||
_nblabel: int
|
||||
_dec: int
|
||||
_pool: TemporaryPool
|
||||
_name: str
|
||||
_label_div_by_zero: Label
|
||||
|
||||
def __init__(self, name: str):
|
||||
self._nblabel = -1
|
||||
self._dec = 0
|
||||
self._pool = TemporaryPool()
|
||||
self._name = name
|
||||
self._label_div_by_zero = self.fresh_label("div_by_zero")
|
||||
|
||||
def get_name(self) -> str:
|
||||
"""Return the name of the function."""
|
||||
return self._name
|
||||
|
||||
def fresh_tmp(self) -> Temporary:
|
||||
"""
|
||||
Return a new fresh Temporary,
|
||||
which is added to the pool.
|
||||
"""
|
||||
return self._pool.fresh_tmp()
|
||||
|
||||
def fresh_offset(self) -> Offset:
|
||||
"""
|
||||
Return a new offset in the memory stack.
|
||||
Offsets are decreasing relative to FP.
|
||||
"""
|
||||
self._dec = self._dec + 1
|
||||
# For ld or sd, an offset on 12 signed bits is expected
|
||||
# Raise an error if the offset is too big
|
||||
if -8 * self._dec < - 2 ** 11:
|
||||
raise AllocationError(
|
||||
"Offset given by the allocation too big to be manipulated ({}), sorry."
|
||||
.format(self._dec))
|
||||
return Offset(FP, -8 * self._dec)
|
||||
|
||||
def get_offset(self) -> int:
|
||||
"""
|
||||
Return the current offset in the memory stack.
|
||||
"""
|
||||
return self._dec
|
||||
|
||||
def _fresh_label_name(self, name) -> str:
|
||||
"""
|
||||
Return a new unique label name based on the given string.
|
||||
"""
|
||||
self._nblabel = self._nblabel + 1
|
||||
return name + "_" + str(self._nblabel) + "_" + self._name
|
||||
|
||||
def fresh_label(self, name) -> Label:
|
||||
"""
|
||||
Return a new label, with a unique name based on the given string.
|
||||
"""
|
||||
return Label(self._fresh_label_name(name))
|
||||
|
||||
def get_label_div_by_zero(self) -> Label:
|
||||
return self._label_div_by_zero
|
||||
|
||||
|
||||
_T = TypeVar("_T", bound=Statement)
|
||||
|
||||
|
||||
def _iter_statements(
|
||||
listIns: List[_T], f: Callable[[_T], List[_T]]) -> List[_T | Comment]:
|
||||
"""Iterate over instructions.
|
||||
For each real instruction i (not label or comment), replace it
|
||||
with the list of instructions given by f(i).
|
||||
"""
|
||||
newListIns: List[_T | Comment] = []
|
||||
for old_i in listIns:
|
||||
# Do nothing for label or comment
|
||||
if not isinstance(old_i, Instruction):
|
||||
newListIns.append(old_i)
|
||||
continue
|
||||
new_i_list = f(old_i)
|
||||
# Otherwise, replace the instruction by the list
|
||||
# returned by f, with comments giving the replacement
|
||||
newListIns.append(Comment("Replaced " + str(old_i)))
|
||||
newListIns.extend(new_i_list)
|
||||
return newListIns
|
||||
|
||||
|
||||
def _print_code(listIns: List, fdata: FunctionData, output,
|
||||
init_label=None, fin_label=None, fin_div0=False, comment=None) -> None:
|
||||
"""
|
||||
Please use print_code from LinearCode or CFG, not directly this one.
|
||||
|
||||
Print the instructions from listIns, forming fdata, on output.
|
||||
If init_label is given, add an initial jump to it before the generated code.
|
||||
If fin_label is given, add it after the generated code.
|
||||
If fin_div0 is given equal to true, add the code for returning an
|
||||
error when dividing by 0, at the very end.
|
||||
"""
|
||||
# compute size for the local stack - do not forget to align by 16
|
||||
fo = fdata.get_offset() # 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")
|
||||
# We put an li t0, cardoffset in case it is greater than 2**11
|
||||
# We use t0 because it is caller-saved
|
||||
output.write("""
|
||||
.text
|
||||
.globl {0}
|
||||
{0}:
|
||||
li t0, {1}
|
||||
sub sp, sp, t0
|
||||
sd ra, 0(sp)
|
||||
sd fp, 8(sp)
|
||||
add fp, sp, t0
|
||||
""".format(fdata.get_name(), cardoffset))
|
||||
# Stack in RiscV is managed with SP
|
||||
if init_label is not None:
|
||||
# Add a jump to init_label before the generated code.
|
||||
output.write("""
|
||||
j {0}
|
||||
""".format(init_label))
|
||||
output.write("\n\n##Generated Code\n")
|
||||
# Generated code
|
||||
for i in listIns:
|
||||
i.printIns(output)
|
||||
output.write("\n\n##postlude\n")
|
||||
if fin_label is not None:
|
||||
# Add fin_label after the generated code.
|
||||
output.write("""
|
||||
{0}:
|
||||
""".format(fin_label))
|
||||
# We put an li t0, cardoffset in case it is greater than 2**11
|
||||
# We use t0 because it is caller-saved
|
||||
output.write("""
|
||||
ld ra, 0(sp)
|
||||
ld fp, 8(sp)
|
||||
li t0, {0}
|
||||
add sp, sp, t0
|
||||
ret
|
||||
""".format(cardoffset))
|
||||
if fin_div0:
|
||||
# Add code for division by 0 at the end.
|
||||
output.write("""
|
||||
{0}:
|
||||
la a0, {0}_msg
|
||||
call println_string
|
||||
li a0, 1
|
||||
call exit
|
||||
""".format(fdata._label_div_by_zero))
|
||||
# Add the data for the message of the division by 0
|
||||
output.write("""
|
||||
{0}_msg: .string "Division by 0"
|
||||
""".format(fdata._label_div_by_zero))
|
||||
@@ -0,0 +1,101 @@
|
||||
"""
|
||||
CAP, CodeGeneration, LinearCode API
|
||||
Classes for a RiscV linear code.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from Lib.Operands import (A0, Function, DataLocation)
|
||||
from Lib.Statement import (
|
||||
Instru3A, AbsoluteJump, ConditionalJump, Comment, Label
|
||||
)
|
||||
from Lib.RiscV import (mv, call)
|
||||
from Lib.FunctionData import (FunctionData, _iter_statements, _print_code)
|
||||
|
||||
|
||||
CodeStatement = Comment | Label | Instru3A | AbsoluteJump | ConditionalJump
|
||||
|
||||
|
||||
class LinearCode:
|
||||
"""
|
||||
Representation of a RiscV program as a list of instructions.
|
||||
|
||||
:py:meth:`add_instruction` is repeatedly called in the codegen visitor
|
||||
to build a complete list of RiscV instructions for the source program.
|
||||
|
||||
The :py:attr:`fdata` member variable contains some meta-information
|
||||
on the program, for instance to allocate a new temporary.
|
||||
See :py:class:`Lib.FunctionData.FunctionData`.
|
||||
|
||||
For debugging purposes, :py:meth:`print_code` allows to print
|
||||
the RiscV program to a file.
|
||||
"""
|
||||
|
||||
"""
|
||||
The :py:attr:`fdata` member variable contains some meta-information
|
||||
on the program, for instance to allocate a new temporary.
|
||||
See :py:class:`Lib.FunctionData.FunctionData`.
|
||||
"""
|
||||
fdata: FunctionData
|
||||
|
||||
_listIns: List[CodeStatement]
|
||||
|
||||
def __init__(self, name: str):
|
||||
self._listIns = []
|
||||
self.fdata = FunctionData(name)
|
||||
|
||||
def add_instruction(self, i: CodeStatement) -> None:
|
||||
"""
|
||||
Utility function to add an instruction in the program.
|
||||
|
||||
See also :py:mod:`Lib.RiscV` to generate relevant instructions.
|
||||
"""
|
||||
self._listIns.append(i)
|
||||
|
||||
def iter_statements(self, f) -> None:
|
||||
"""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.
|
||||
"""
|
||||
self._listIns = _iter_statements(self._listIns, f)
|
||||
|
||||
def get_instructions(self) -> List[CodeStatement]:
|
||||
"""Return the list of instructions of the program."""
|
||||
return self._listIns
|
||||
|
||||
# each instruction has its own "add in list" version
|
||||
def add_label(self, s: Label) -> None:
|
||||
"""Add a label in the program."""
|
||||
return self.add_instruction(s)
|
||||
|
||||
def add_comment(self, s: str) -> None:
|
||||
"""Add a comment in the program."""
|
||||
self.add_instruction(Comment(s))
|
||||
|
||||
def add_instruction_PRINTLN_INT(self, reg: DataLocation) -> None:
|
||||
"""Print integer value, with newline. (see Expand)"""
|
||||
# a print instruction generates the temp it prints.
|
||||
self.add_instruction(mv(A0, reg))
|
||||
self.add_instruction(call(Function('println_int')))
|
||||
|
||||
def __str__(self):
|
||||
return '\n'.join(map(str, self._listIns))
|
||||
|
||||
def print_code(self, output, comment=None) -> None:
|
||||
"""Outputs the RiscV program as text to a file at the given path."""
|
||||
_print_code(self._listIns, self.fdata, output, init_label=None,
|
||||
fin_label=None, fin_div0=True, comment=comment)
|
||||
|
||||
def print_dot(self, filename: str, DF=None, view=False) -> None: # pragma: no cover
|
||||
"""Outputs the RiscV program as graph to a file at the given path."""
|
||||
# import graphviz here so that students who don't have it can still work on lab4
|
||||
from graphviz import Digraph
|
||||
graph = Digraph()
|
||||
# nodes
|
||||
content = ""
|
||||
for i in self._listIns:
|
||||
content += str(i) + "\\l"
|
||||
graph.node("Code", label=content, shape='rectangle')
|
||||
# no edges
|
||||
graph.render(filename, view=view)
|
||||
@@ -0,0 +1,265 @@
|
||||
"""
|
||||
This file defines the base class :py:class:`Operand`
|
||||
and its subclasses for different operands: :py:class:`Condition`,
|
||||
:py:class:`DataLocation` and :py:class:`Function`.
|
||||
|
||||
The class :py:class:`DataLocation` itself has subclasses:
|
||||
:py:class:`Register`, :py:class:`Offset` for address in memory,
|
||||
:py:class:`Immediate` for constants and :py:class:`Temporary`
|
||||
for location not yet allocated.
|
||||
|
||||
This file also define shortcuts for registers in RISCV.
|
||||
"""
|
||||
|
||||
from typing import Dict, List
|
||||
from MiniCParser import MiniCParser
|
||||
from Lib.Errors import MiniCInternalError
|
||||
|
||||
|
||||
class Operand():
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# signed version for riscv
|
||||
all_ops = ['blt', 'bgt', 'beq', 'bne', 'ble', 'bge', 'beqz', 'bnez']
|
||||
opdict = {MiniCParser.LT: 'blt', MiniCParser.GT: 'bgt',
|
||||
MiniCParser.LTEQ: 'ble', MiniCParser.GTEQ: 'bge',
|
||||
MiniCParser.NEQ: 'bne', MiniCParser.EQ: 'beq'}
|
||||
opnot_dict = {'bgt': 'ble',
|
||||
'bge': 'blt',
|
||||
'blt': 'bge',
|
||||
'ble': 'bgt',
|
||||
'beq': 'bne',
|
||||
'bne': 'beq',
|
||||
'beqz': 'bnez',
|
||||
'bnez': 'beqz'}
|
||||
|
||||
|
||||
class Condition(Operand):
|
||||
"""Condition, i.e. comparison operand for a CondJump.
|
||||
|
||||
Example usage :
|
||||
|
||||
- Condition('beq') = branch if equal.
|
||||
- Condition(MiniCParser.LT) = branch if lower than.
|
||||
- ...
|
||||
|
||||
The constructor's argument shall be a string in the list all_ops, or a
|
||||
comparison operator in MiniCParser.LT, MiniCParser.GT, ... (one of the keys
|
||||
in opdict).
|
||||
|
||||
A 'negate' method allows getting the negation of this condition.
|
||||
"""
|
||||
|
||||
_op: str
|
||||
|
||||
def __init__(self, optype):
|
||||
if optype in opdict:
|
||||
self._op = opdict[optype]
|
||||
elif str(optype) in all_ops:
|
||||
self._op = str(optype)
|
||||
else:
|
||||
raise MiniCInternalError("Unsupported comparison operator %s", optype)
|
||||
|
||||
def negate(self) -> 'Condition':
|
||||
"""Return the opposite condition."""
|
||||
return Condition(opnot_dict[self._op])
|
||||
|
||||
def __str__(self):
|
||||
return self._op
|
||||
|
||||
|
||||
class Function(Operand):
|
||||
"""Operand for build-in function call."""
|
||||
|
||||
_name: str
|
||||
|
||||
def __init__(self, name: str):
|
||||
self._name = name
|
||||
|
||||
def __str__(self):
|
||||
return self._name
|
||||
|
||||
|
||||
class DataLocation(Operand):
|
||||
""" A Data Location is either a register, a temporary
|
||||
or a place in memory (offset).
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
# map for register shortcuts
|
||||
reg_map = dict([(0, 'zero'), (1, 'ra'), (2, 'sp')] + # no (3, 'gp') nor (4, 'tp')
|
||||
[(i+5, 't'+str(i)) for i in range(3)] +
|
||||
[(8, 'fp'), (9, 's1')] +
|
||||
[(i+10, 'a'+str(i)) for i in range(8)] +
|
||||
[(i+18, 's'+str(i+2)) for i in range(10)] +
|
||||
[(i+28, 't'+str(i+3)) for i in range(4)])
|
||||
|
||||
|
||||
class Register(DataLocation):
|
||||
""" A (physical) register."""
|
||||
|
||||
_number: int
|
||||
|
||||
def __init__(self, number: int):
|
||||
self._number = number
|
||||
|
||||
def __repr__(self):
|
||||
if self._number not in reg_map:
|
||||
raise Exception("Register number %d should not be used", self._number)
|
||||
else:
|
||||
return ("{}".format(reg_map[self._number]))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Register) and self._number == other._number
|
||||
|
||||
def __hash__(self):
|
||||
return self._number
|
||||
|
||||
|
||||
# Shortcuts for registers in RISCV
|
||||
# Only integer registers
|
||||
ZERO = Register(0)
|
||||
RA = Register(1)
|
||||
SP = Register(2)
|
||||
GP = Register(3) # Register not used for this course
|
||||
TP = Register(4) # Register not used for this course
|
||||
A = tuple(Register(i + 10) for i in range(8))
|
||||
S = tuple(Register(i + 8) for i in range(2)) + tuple(Register(i + 18) for i in range(10))
|
||||
T = tuple(Register(i + 5) for i in range(3)) + tuple(Register(i + 28) for i in range(4))
|
||||
A0 = A[0] # function args/return Values: A0, A1
|
||||
A1 = A[1]
|
||||
FP = S[0] # Frame Pointer = Saved register 0
|
||||
|
||||
# General purpose registers, usable for the allocator
|
||||
GP_REGS = S[4:] + T # s0, s1, s2 and s3 are special
|
||||
|
||||
|
||||
class Offset(DataLocation):
|
||||
""" Offset = address in memory computed with base + offset."""
|
||||
|
||||
_basereg: Register
|
||||
_offset: int
|
||||
|
||||
def __init__(self, basereg: Register, offset: int):
|
||||
self._basereg = basereg
|
||||
self._offset = offset
|
||||
|
||||
def __repr__(self):
|
||||
return ("{}({})".format(self._offset, self._basereg))
|
||||
|
||||
def get_offset(self) -> int:
|
||||
"""Return the value of the offset."""
|
||||
return self._offset
|
||||
|
||||
|
||||
class Immediate(DataLocation):
|
||||
"""Immediate operand (integer)."""
|
||||
|
||||
_val: int
|
||||
|
||||
def __init__(self, val):
|
||||
self._val = val
|
||||
|
||||
def __str__(self):
|
||||
return str(self._val)
|
||||
|
||||
|
||||
class Temporary(DataLocation):
|
||||
"""Temporary, a location that has not been allocated yet.
|
||||
It will later be mapped to a physical register (Register) or to a memory location (Offset).
|
||||
"""
|
||||
|
||||
_number: int
|
||||
_pool: 'TemporaryPool'
|
||||
|
||||
def __init__(self, number: int, pool: 'TemporaryPool'):
|
||||
self._number = number
|
||||
self._pool = pool
|
||||
|
||||
def __repr__(self):
|
||||
return ("temp_{}".format(str(self._number)))
|
||||
|
||||
def get_alloced_loc(self) -> DataLocation:
|
||||
"""Return the DataLocation allocated to this Temporary."""
|
||||
return self._pool.get_alloced_loc(self)
|
||||
|
||||
|
||||
class TemporaryPool:
|
||||
"""Manage a pool of temporaries."""
|
||||
|
||||
_all_temps: List[Temporary]
|
||||
_current_num: int
|
||||
_allocation: Dict[Temporary, DataLocation]
|
||||
|
||||
def __init__(self):
|
||||
self._all_temps = []
|
||||
self._current_num = 0
|
||||
self._allocation = dict()
|
||||
|
||||
def get_all_temps(self) -> List[Temporary]:
|
||||
"""Return all the temporaries of the pool."""
|
||||
return self._all_temps
|
||||
|
||||
def get_alloced_loc(self, t: Temporary) -> DataLocation:
|
||||
"""Get the actual DataLocation allocated for the temporary t."""
|
||||
return self._allocation[t]
|
||||
|
||||
def add_tmp(self, t: Temporary):
|
||||
"""Add a temporary to the pool."""
|
||||
self._all_temps.append(t)
|
||||
self._allocation[t] = t # While no allocation, return the temporary itself
|
||||
|
||||
def set_temp_allocation(self, allocation: Dict[Temporary, DataLocation]) -> None:
|
||||
"""Give a mapping from temporaries to actual registers.
|
||||
The argument allocation must be a dict from Temporary to
|
||||
DataLocation other than Temporary (typically Register or Offset).
|
||||
Typing enforces that keys are Temporary and values are Datalocation.
|
||||
We check the values are indeed not Temporary.
|
||||
"""
|
||||
for v in allocation.values():
|
||||
assert not isinstance(v, Temporary), (
|
||||
"Incorrect allocation scheme: value " +
|
||||
str(v) + " is a Temporary.")
|
||||
self._allocation = allocation
|
||||
|
||||
def fresh_tmp(self) -> Temporary:
|
||||
"""Give a new fresh Temporary and add it to the pool."""
|
||||
t = Temporary(self._current_num, self)
|
||||
self._current_num += 1
|
||||
self.add_tmp(t)
|
||||
return t
|
||||
|
||||
|
||||
class Renamer:
|
||||
"""Manage a renaming of temporaries."""
|
||||
|
||||
_pool: TemporaryPool
|
||||
_env: Dict[Temporary, Temporary]
|
||||
|
||||
def __init__(self, pool: TemporaryPool):
|
||||
self._pool = pool
|
||||
self._env = dict()
|
||||
|
||||
def fresh(self, t: Temporary) -> Temporary:
|
||||
"""Give a fresh rename for a Temporary."""
|
||||
new_t = self._pool.fresh_tmp()
|
||||
self._env[t] = new_t
|
||||
return new_t
|
||||
|
||||
def replace(self, t: Temporary) -> Temporary:
|
||||
"""Give the rename for a Temporary (which is itself if it is not renamed)."""
|
||||
return self._env.get(t, t)
|
||||
|
||||
def defined(self, t: Temporary) -> bool:
|
||||
"""True if the Temporary is renamed."""
|
||||
return t in self._env
|
||||
|
||||
def copy(self):
|
||||
"""Give a copy of the Renamer."""
|
||||
r = Renamer(self._pool)
|
||||
r._env = self._env.copy()
|
||||
return r
|
||||
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
MIF08, CAP, CodeGeneration, RiscV API
|
||||
Functions to define instructions.
|
||||
"""
|
||||
|
||||
from Lib.Errors import MiniCInternalError
|
||||
from Lib.Operands import (
|
||||
Condition, Immediate, Operand, Function, ZERO)
|
||||
from Lib.Statement import (Instru3A, AbsoluteJump, ConditionalJump, Label)
|
||||
|
||||
|
||||
def call(function: Function) -> Instru3A:
|
||||
"""Function call."""
|
||||
return Instru3A('call', function)
|
||||
|
||||
|
||||
def jump(label: Label) -> AbsoluteJump:
|
||||
"""Unconditional jump to label."""
|
||||
return AbsoluteJump(label)
|
||||
|
||||
|
||||
def conditional_jump(label: Label, op1: Operand, cond: Condition, op2: Operand):
|
||||
"""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), ...
|
||||
"""
|
||||
op2 = op2 if op2 != Immediate(0) else ZERO
|
||||
return ConditionalJump(cond=cond, op1=op1, op2=op2, label=label)
|
||||
|
||||
|
||||
def add(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
if isinstance(sr2orimm7, Immediate):
|
||||
return Instru3A("addi", dr, sr1, sr2orimm7)
|
||||
else:
|
||||
return Instru3A("add", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def mul(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
if isinstance(sr2orimm7, Immediate):
|
||||
raise MiniCInternalError("Cant multiply by an immediate")
|
||||
else:
|
||||
return Instru3A("mul", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def div(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
if isinstance(sr2orimm7, Immediate):
|
||||
raise MiniCInternalError("Cant divide by an immediate")
|
||||
else:
|
||||
return Instru3A("div", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def rem(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
if isinstance(sr2orimm7, Immediate):
|
||||
raise MiniCInternalError("Cant divide by an immediate")
|
||||
return Instru3A("rem", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def sub(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
if isinstance(sr2orimm7, Immediate):
|
||||
raise MiniCInternalError("Cant substract by an immediate")
|
||||
return Instru3A("sub", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def land(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
return Instru3A("and", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def lor(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A:
|
||||
return Instru3A("or", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def xor(dr: Operand, sr1: Operand, sr2orimm7: Operand) -> Instru3A: # pragma: no cover
|
||||
return Instru3A("xor", dr, sr1, sr2orimm7)
|
||||
|
||||
|
||||
def li(dr: Operand, imm7: Immediate) -> Instru3A:
|
||||
return Instru3A("li", dr, imm7)
|
||||
|
||||
|
||||
def mv(dr: Operand, sr: Operand) -> Instru3A:
|
||||
return Instru3A("mv", dr, sr)
|
||||
|
||||
|
||||
def ld(dr: Operand, mem: Operand) -> Instru3A:
|
||||
return Instru3A("ld", dr, mem)
|
||||
|
||||
|
||||
def sd(sr: Operand, mem: Operand) -> Instru3A:
|
||||
return Instru3A("sd", sr, mem)
|
||||
@@ -0,0 +1,242 @@
|
||||
"""
|
||||
The base class for RISCV ASM statements is :py:class:`Statement`.
|
||||
It is inherited by :py:class:`Comment`, :py:class:`Label`
|
||||
and :py:class:`Instruction`. In turn, :py:class:`Instruction`
|
||||
is inherited by :py:class:`Instru3A`
|
||||
(for regular non-branching 3-address instructions),
|
||||
:py:class:`AbsoluteJump` and :py:class:`ConditionalJump`.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import (List, Dict, TypeVar)
|
||||
from Lib.Operands import (Operand, Renamer, Temporary, Condition)
|
||||
from Lib.Errors import MiniCInternalError
|
||||
|
||||
|
||||
def regset_to_string(registerset):
|
||||
"""Utility function: pretty-prints a set of locations."""
|
||||
return "{" + ",".join(str(x) for x in registerset) + "}"
|
||||
|
||||
|
||||
# Temporary until we can use Typing.Self in python 3.11
|
||||
TStatement = TypeVar("TStatement", bound="Statement")
|
||||
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
class Statement:
|
||||
"""A Statement, which is an instruction, a comment or a label."""
|
||||
|
||||
def defined(self) -> List[Operand]:
|
||||
return []
|
||||
|
||||
def used(self) -> List[Operand]:
|
||||
return []
|
||||
|
||||
def substitute(self: TStatement, subst: Dict[Operand, Operand]) -> TStatement:
|
||||
raise Exception(
|
||||
"substitute: Operands {} are not present in instruction {}"
|
||||
.format(subst, self))
|
||||
|
||||
def printIns(self, stream):
|
||||
"""
|
||||
Print the statement on the output.
|
||||
Should never be called on the base class.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
class Comment(Statement):
|
||||
"""A comment."""
|
||||
comment: str
|
||||
|
||||
def __str__(self): # use only for print_dot !
|
||||
return "# {}".format(self.comment)
|
||||
|
||||
def printIns(self, stream):
|
||||
print(' # ' + self.comment, file=stream)
|
||||
|
||||
|
||||
@dataclass(unsafe_hash=True)
|
||||
class Label(Statement, Operand):
|
||||
"""A label is both a Statement and an Operand."""
|
||||
name: str
|
||||
|
||||
def __str__(self):
|
||||
return ("lbl_{}".format(self.name))
|
||||
|
||||
def __repr__(self):
|
||||
return ("{}".format(self.name))
|
||||
|
||||
def printIns(self, stream):
|
||||
print(str(self) + ':', file=stream)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Instruction(Statement):
|
||||
ins: str
|
||||
_read_only: bool
|
||||
|
||||
def is_read_only(self):
|
||||
"""
|
||||
True if the instruction only reads from its operands.
|
||||
|
||||
Otherwise, the first operand is considered as the destination
|
||||
and others are source.
|
||||
"""
|
||||
return self._read_only
|
||||
|
||||
def rename(self, renamer: Renamer) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
def args(self) -> List[Operand]:
|
||||
raise NotImplementedError
|
||||
|
||||
def defined(self):
|
||||
if self.is_read_only():
|
||||
defs = []
|
||||
else:
|
||||
defs = [self.args()[0]]
|
||||
return defs
|
||||
|
||||
def used(self):
|
||||
if self.is_read_only():
|
||||
uses = self.args()
|
||||
else:
|
||||
uses = self.args()[1:]
|
||||
return uses
|
||||
|
||||
def __str__(self):
|
||||
s = self.ins
|
||||
first = True
|
||||
for arg in self.args():
|
||||
if first:
|
||||
s += ' ' + str(arg)
|
||||
first = False
|
||||
else:
|
||||
s += ', ' + str(arg)
|
||||
return s
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.ins, *self.args()))
|
||||
|
||||
def printIns(self, stream):
|
||||
"""Print the instruction on the output."""
|
||||
print(' ', str(self), file=stream)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Instru3A(Instruction):
|
||||
_args: List[Operand]
|
||||
|
||||
def __init__(self, ins, *args: Operand):
|
||||
# convention is to use lower-case in RISCV
|
||||
self.ins = ins.lower()
|
||||
self._args = list(args)
|
||||
self._read_only = (self.ins == "call"
|
||||
or self.ins == "ld"
|
||||
or self.ins == "lw"
|
||||
or self.ins == "lb")
|
||||
if (self.ins.startswith("b") or self.ins == "j"):
|
||||
raise MiniCInternalError
|
||||
|
||||
def args(self):
|
||||
return self._args
|
||||
|
||||
def rename(self, renamer: Renamer):
|
||||
old_replaced = dict()
|
||||
for i, arg in enumerate(self._args):
|
||||
if isinstance(arg, Temporary):
|
||||
if i == 0 and not self.is_read_only():
|
||||
old_replaced[arg] = renamer.replace(arg)
|
||||
new_t = renamer.fresh(arg)
|
||||
elif arg in old_replaced.keys():
|
||||
new_t = old_replaced[arg]
|
||||
else:
|
||||
new_t = renamer.replace(arg)
|
||||
self._args[i] = new_t
|
||||
|
||||
def substitute(self, subst: Dict[Operand, Operand]):
|
||||
for op in subst:
|
||||
if op not in self.args():
|
||||
raise Exception(
|
||||
"substitute: Operand {} is not present in instruction {}"
|
||||
.format(op, self))
|
||||
args = [subst.get(arg, arg)
|
||||
if isinstance(arg, Temporary) else arg
|
||||
for arg in self.args()]
|
||||
return Instru3A(self.ins, *args)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(super)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class AbsoluteJump(Instruction):
|
||||
""" An Absolute Jump is a specific kind of instruction"""
|
||||
ins = "j"
|
||||
label: Label
|
||||
_read_only = True
|
||||
|
||||
def __init__(self, label: Label):
|
||||
self.label = label
|
||||
|
||||
def args(self):
|
||||
return [self.label]
|
||||
|
||||
def rename(self, renamer: Renamer):
|
||||
pass
|
||||
|
||||
def substitute(self, subst: Dict[Operand, Operand]):
|
||||
if subst != {}:
|
||||
raise Exception(
|
||||
"substitute: No possible substitution on instruction {}"
|
||||
.format(self))
|
||||
return self
|
||||
|
||||
def __hash__(self):
|
||||
return hash(super)
|
||||
|
||||
def targets(self) -> List[Label]:
|
||||
return [self.label]
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class ConditionalJump(Instruction):
|
||||
""" A Conditional Jump is a specific kind of instruction"""
|
||||
cond: Condition
|
||||
label: Label
|
||||
op1: Operand
|
||||
op2: Operand
|
||||
_read_only = True
|
||||
|
||||
def __init__(self, cond: Condition, op1: Operand, op2: Operand, label: Label):
|
||||
self.cond = cond
|
||||
self.label = label
|
||||
self.op1 = op1
|
||||
self.op2 = op2
|
||||
self.ins = str(self.cond)
|
||||
|
||||
def args(self):
|
||||
return [self.op1, self.op2, self.label]
|
||||
|
||||
def rename(self, renamer: Renamer):
|
||||
if isinstance(self.op1, Temporary):
|
||||
self.op1 = renamer.replace(self.op1)
|
||||
if isinstance(self.op2, Temporary):
|
||||
self.op2 = renamer.replace(self.op2)
|
||||
|
||||
def substitute(self, subst: Dict[Operand, Operand]):
|
||||
for op in subst:
|
||||
if op not in self.args():
|
||||
raise Exception(
|
||||
"substitute: Operand {} is not present in instruction {}"
|
||||
.format(op, self))
|
||||
op1 = subst.get(self.op1, self.op1) if isinstance(self.op1, Temporary) \
|
||||
else self.op1
|
||||
op2 = subst.get(self.op2, self.op2) if isinstance(self.op2, Temporary) \
|
||||
else self.op2
|
||||
return ConditionalJump(self.cond, op1, op2, self.label)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(super)
|
||||
+12
-9
@@ -9,12 +9,9 @@ ifdef TEST_FILES
|
||||
export TEST_FILES
|
||||
endif
|
||||
|
||||
ifdef SSA
|
||||
MINICC_OPTS+=--ssa
|
||||
endif
|
||||
|
||||
ifdef SSA_OPTIM
|
||||
MINICC_OPTS+=--ssa-optim
|
||||
# code generation mode
|
||||
ifdef MODE
|
||||
MINICC_OPTS+=--mode $(MODE)
|
||||
endif
|
||||
|
||||
ifdef TYPECHECK_ONLY
|
||||
@@ -45,8 +42,8 @@ main-deps: MiniCLexer.py MiniCParser.py TP03/MiniCInterpretVisitor.py TP03/MiniC
|
||||
.PHONY: test test-interpret test-codegen clean clean-tests tar antlr
|
||||
|
||||
|
||||
test: test-interpret
|
||||
|
||||
test: test-interpret test-codegen
|
||||
|
||||
test-pyright: antlr
|
||||
pyright .
|
||||
@@ -57,10 +54,16 @@ test-interpret: test-pyright test_interpreter.py main-deps
|
||||
|
||||
# Test for naive allocator (also runs test_expect to check // EXPECTED directives):
|
||||
test-naive: test-pyright antlr
|
||||
ifndef MODE
|
||||
export MINICC_OPTS="${MINICC_OPTS} --mode codegen-linear"
|
||||
endif
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'naive or expect'
|
||||
|
||||
# Test for all but the smart allocator, i.e. everything that lab4 should pass:
|
||||
test-notsmart: test-pyright antlr
|
||||
test-lab4: test-pyright antlr
|
||||
ifndef MODE
|
||||
export MINICC_OPTS="${MINICC_OPTS} --mode codegen-linear"
|
||||
endif
|
||||
python3 -m pytest $(PYTEST_BASE_OPTS) $(PYTEST_OPTS) ./test_codegen.py -k 'not smart'
|
||||
|
||||
# Test just the smart allocator (quicker than tests)
|
||||
@@ -83,7 +86,7 @@ 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")):
|
||||
for s in ("{}-{}.s".format(f[:-2], test) for test in ("naive", "smart", "gcc", "all-in-mem")):
|
||||
try:
|
||||
os.remove(s)
|
||||
print("Removed {}".format(s))
|
||||
|
||||
+12
-12
@@ -1,8 +1,8 @@
|
||||
#! /usr/bin/env python3
|
||||
"""
|
||||
Code generation lab, main file. Code Generation with Smart IRs.
|
||||
Evaluation and code generation labs, main file.
|
||||
Usage:
|
||||
python3 MiniCC.py <filename>
|
||||
python3 MiniCC.py --mode <mode> <filename>
|
||||
python3 MiniCC.py --help
|
||||
"""
|
||||
import traceback
|
||||
@@ -58,7 +58,7 @@ def valid_modes():
|
||||
return modes
|
||||
|
||||
try:
|
||||
import TP05c.OptimSSA # type: ignore[import]
|
||||
import TPoptim.OptimSSA # type: ignore[import]
|
||||
modes.append('codegen-optim')
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -146,9 +146,9 @@ def main(inputname, reg_alloc, mode,
|
||||
from TP04.BuildCFG import build_cfg # type: ignore[import]
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
code = build_cfg(function)
|
||||
assert(isinstance(code, CFG))
|
||||
assert (isinstance(code, CFG))
|
||||
if debug_graphs:
|
||||
s = "{}.{}.dot".format(basename, code.fdata._name)
|
||||
s = "{}.{}.dot".format(basename, code.fdata.get_name())
|
||||
print("CFG:", s)
|
||||
code.print_dot(s, view=True)
|
||||
if mode.value >= Mode.SSA.value:
|
||||
@@ -157,14 +157,14 @@ def main(inputname, reg_alloc, mode,
|
||||
|
||||
DF = enter_ssa(cast(CFG, code), basename, debug, ssa_graphs)
|
||||
if ssa_graphs:
|
||||
s = "{}.{}.ssa.dot".format(basename, code.fdata._name)
|
||||
s = "{}.{}.ssa.dot".format(basename, code.fdata.get_name())
|
||||
print("SSA:", s)
|
||||
code.print_dot(s, DF, True)
|
||||
if mode == Mode.OPTIM:
|
||||
from TP05c.OptimSSA import OptimSSA # type: ignore[import]
|
||||
from TPoptim.OptimSSA import OptimSSA # type: ignore[import]
|
||||
OptimSSA(cast(CFG, code), debug=debug)
|
||||
if ssa_graphs:
|
||||
s = "{}.{}.optimssa.dot".format(basename, code.fdata._name)
|
||||
s = "{}.{}.optimssa.dot".format(basename, code.fdata.get_name())
|
||||
print("SSA after optim:", s)
|
||||
code.print_dot(s, view=True)
|
||||
allocator = None
|
||||
@@ -178,7 +178,7 @@ def main(inputname, reg_alloc, mode,
|
||||
comment = "all-in-memory allocation"
|
||||
elif reg_alloc == "smart":
|
||||
liveness = None
|
||||
if mode == Mode.SSA:
|
||||
if mode.value >= Mode.SSA.value:
|
||||
from TP05.LivenessSSA import LivenessSSA # type: ignore[import]
|
||||
try:
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
@@ -205,15 +205,15 @@ liveness file not found for {}.".format(form))
|
||||
raise ValueError("Invalid allocation strategy:" + reg_alloc)
|
||||
if allocator:
|
||||
allocator.prepare()
|
||||
if mode == Mode.SSA:
|
||||
if mode.value >= Mode.SSA.value:
|
||||
from Lib.CFG import CFG # type: ignore[import]
|
||||
from TP05.SSA import exit_ssa # type: ignore[import]
|
||||
exit_ssa(cast(CFG, code))
|
||||
comment += " with SSA"
|
||||
if allocator:
|
||||
allocator.rewriteCode(code)
|
||||
if mode == Mode.SSA and ssa_graphs:
|
||||
s = "{}.{}.exitssa.dot".format(basename, code.fdata._name)
|
||||
if mode.value >= Mode.SSA.value and ssa_graphs:
|
||||
s = "{}.{}.exitssa.dot".format(basename, code.fdata.get_name())
|
||||
print("CFG after SSA:", s)
|
||||
code.print_dot(s, view=True)
|
||||
code.print_code(output, comment=comment)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
# MiniC Compiler
|
||||
LAB4 (simple code generation), MIF08 / CAP 2022-23
|
||||
|
||||
# Authors
|
||||
|
||||
YOUR NAME HERE
|
||||
|
||||
# Contents
|
||||
|
||||
TODO for STUDENTS : Say a bit about the code infrastructure ...
|
||||
|
||||
# Test design
|
||||
|
||||
TODO: explain your tests
|
||||
|
||||
# Design choices
|
||||
|
||||
TODO: explain your choices. How did you implement boolean not? Did you implement an extension?
|
||||
|
||||
# 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 expression
|
||||
- [ ] UnaryMinus expression
|
||||
- [ ] 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
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
from Lib import RiscV
|
||||
from Lib.Operands import Temporary, Operand, S
|
||||
from Lib.Statement import Instruction
|
||||
from Lib.Allocator import Allocator
|
||||
from typing import List, Dict
|
||||
|
||||
|
||||
class AllInMemAllocator(Allocator):
|
||||
|
||||
def replace(self, old_instr: Instruction) -> List[Instruction]:
|
||||
"""Replace Temporary operands with the corresponding allocated
|
||||
memory location. FP points to the stack."""
|
||||
numreg = 1
|
||||
before: List[Instruction] = []
|
||||
after: List[Instruction] = []
|
||||
subst: Dict[Operand, Operand] = {}
|
||||
# TODO (Exercise 7): compute before,after,args.
|
||||
# TODO (Exercise 7): iterate over old_args, check which argument
|
||||
# TODO (Exercise 7): is a temporary (e.g. isinstance(..., Temporary)),
|
||||
# TODO (Exercise 7): and if so, generate ld/sd accordingly. Replace the
|
||||
# TODO (Exercise 7): temporary with S[1], S[2] or S[3] physical registers.
|
||||
new_instr = old_instr.substitute(subst)
|
||||
return before + [new_instr] + after
|
||||
|
||||
def prepare(self):
|
||||
"""Allocate all temporaries to memory.
|
||||
Invariants:
|
||||
- Expanded instructions can use s2 and s3
|
||||
(to store the values of temporaries before the actual instruction).
|
||||
"""
|
||||
self._fdata._pool.set_temp_allocation(
|
||||
{temp: self._fdata.fresh_offset()
|
||||
for temp in self._fdata._pool.get_all_temps()})
|
||||
@@ -0,0 +1,194 @@
|
||||
from typing import List, Tuple
|
||||
from MiniCVisitor import MiniCVisitor
|
||||
from MiniCParser import MiniCParser
|
||||
from Lib.LinearCode import LinearCode
|
||||
from Lib import RiscV
|
||||
from Lib.RiscV import Condition
|
||||
from Lib import Operands
|
||||
from antlr4.tree.Trees import Trees
|
||||
from Lib.Errors import MiniCInternalError, MiniCUnsupportedError
|
||||
|
||||
"""
|
||||
CAP, MIF08, three-address code generation + simple alloc
|
||||
This visitor constructs an object of type "LinearCode".
|
||||
"""
|
||||
|
||||
|
||||
class MiniCCodeGen3AVisitor(MiniCVisitor):
|
||||
|
||||
_current_function: LinearCode
|
||||
|
||||
def __init__(self, debug, parser):
|
||||
super().__init__()
|
||||
self._parser = parser
|
||||
self._debug = debug
|
||||
self._functions = []
|
||||
self._lastlabel = ""
|
||||
|
||||
def get_functions(self) -> List[LinearCode]:
|
||||
return self._functions
|
||||
|
||||
def printSymbolTable(self): # pragma: no cover
|
||||
print("--variables to temporaries map--")
|
||||
for keys, values in self._symbol_table.items():
|
||||
print(keys + '-->' + str(values))
|
||||
|
||||
# handle variable decl
|
||||
|
||||
def visitVarDecl(self, ctx) -> None:
|
||||
type_str = ctx.typee().getText()
|
||||
vars_l = self.visit(ctx.id_l())
|
||||
for name in vars_l:
|
||||
if name in self._symbol_table:
|
||||
raise MiniCInternalError(
|
||||
"Variable {} has already been declared".format(name))
|
||||
else:
|
||||
tmp = self._current_function.fdata.fresh_tmp()
|
||||
self._symbol_table[name] = tmp
|
||||
if type_str not in ("int", "bool"):
|
||||
raise MiniCUnsupportedError("Unsupported type " + type_str)
|
||||
# Initialization to 0 or False, both represented with 0
|
||||
self._current_function.add_instruction(
|
||||
RiscV.li(tmp, Operands.Immediate(0)))
|
||||
|
||||
def visitIdList(self, ctx) -> Operands.Temporary:
|
||||
t = self.visit(ctx.id_l())
|
||||
t.append(ctx.ID().getText())
|
||||
return t
|
||||
|
||||
def visitIdListBase(self, ctx) -> List[str]:
|
||||
return [ctx.ID().getText()]
|
||||
|
||||
# expressions
|
||||
|
||||
def visitParExpr(self, ctx) -> Operands.Temporary:
|
||||
return self.visit(ctx.expr())
|
||||
|
||||
def visitIntAtom(self, ctx) -> Operands.Temporary:
|
||||
val = Operands.Immediate(int(ctx.getText()))
|
||||
dest_temp = self._current_function.fdata.fresh_tmp()
|
||||
self._current_function.add_instruction(RiscV.li(dest_temp, val))
|
||||
return dest_temp
|
||||
|
||||
def visitFloatAtom(self, ctx) -> Operands.Temporary:
|
||||
raise MiniCUnsupportedError("float literal")
|
||||
|
||||
def visitBooleanAtom(self, ctx) -> Operands.Temporary:
|
||||
# true is 1 false is 0
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
|
||||
def visitIdAtom(self, ctx) -> Operands.Temporary:
|
||||
try:
|
||||
# get the temporary associated to id
|
||||
return self._symbol_table[ctx.getText()]
|
||||
except KeyError: # pragma: no cover
|
||||
raise MiniCInternalError(
|
||||
"Undefined variable {}, this should have failed to typecheck."
|
||||
.format(ctx.getText())
|
||||
)
|
||||
|
||||
def visitStringAtom(self, ctx) -> Operands.Temporary:
|
||||
raise MiniCUnsupportedError("string atom")
|
||||
|
||||
# now visit expressions
|
||||
|
||||
def visitAtomExpr(self, ctx) -> Operands.Temporary:
|
||||
return self.visit(ctx.atom())
|
||||
|
||||
def visitAdditiveExpr(self, ctx) -> Operands.Temporary:
|
||||
assert ctx.myop is not None
|
||||
raise NotImplementedError() # TODO (Exercise 2)
|
||||
|
||||
def visitOrExpr(self, ctx) -> Operands.Temporary:
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
|
||||
def visitAndExpr(self, ctx) -> Operands.Temporary:
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
|
||||
def visitEqualityExpr(self, ctx) -> Operands.Temporary:
|
||||
return self.visitRelationalExpr(ctx)
|
||||
|
||||
def visitRelationalExpr(self, ctx) -> Operands.Temporary:
|
||||
assert ctx.myop is not None
|
||||
c = Condition(ctx.myop.type)
|
||||
if self._debug:
|
||||
print("relational expression:")
|
||||
print(Trees.toStringTree(ctx, None, self._parser))
|
||||
print("Condition:", c)
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
|
||||
def visitMultiplicativeExpr(self, ctx) -> Operands.Temporary:
|
||||
assert ctx.myop is not None
|
||||
div_by_zero_lbl = self._current_function.fdata.get_label_div_by_zero()
|
||||
raise NotImplementedError() # TODO (Exercise 8)
|
||||
|
||||
def visitNotExpr(self, ctx) -> Operands.Temporary:
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
|
||||
def visitUnaryMinusExpr(self, ctx) -> Operands.Temporary:
|
||||
raise NotImplementedError("unaryminusexpr") # TODO (Exercise 2)
|
||||
|
||||
def visitProgRule(self, ctx) -> None:
|
||||
self.visitChildren(ctx)
|
||||
|
||||
def visitFuncDef(self, ctx) -> None:
|
||||
funcname = ctx.ID().getText()
|
||||
self._current_function = LinearCode(funcname)
|
||||
self._symbol_table = dict()
|
||||
|
||||
self.visit(ctx.vardecl_l())
|
||||
self.visit(ctx.block())
|
||||
self._current_function.add_comment("Return at end of function:")
|
||||
# This skeleton doesn't deal properly with functions, and
|
||||
# hardcodes a "return 0;" at the end of function. Generate
|
||||
# code for this "return 0;".
|
||||
self._current_function.add_instruction(
|
||||
RiscV.li(Operands.A0, Operands.Immediate(0)))
|
||||
self._functions.append(self._current_function)
|
||||
del self._current_function
|
||||
|
||||
def visitAssignStat(self, ctx) -> None:
|
||||
if self._debug:
|
||||
print("assign statement, rightexpression is:")
|
||||
print(Trees.toStringTree(ctx.expr(), None, self._parser))
|
||||
expr_temp = self.visit(ctx.expr())
|
||||
name = ctx.ID().getText()
|
||||
self._current_function.add_instruction(RiscV.mv(self._symbol_table[name], expr_temp))
|
||||
|
||||
def visitIfStat(self, ctx) -> None:
|
||||
if self._debug:
|
||||
print("if statement")
|
||||
end_if_label = self._current_function.fdata.fresh_label("end_if")
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
self._current_function.add_label(end_if_label)
|
||||
|
||||
def visitWhileStat(self, ctx) -> None:
|
||||
if self._debug:
|
||||
print("while statement, condition is:")
|
||||
print(Trees.toStringTree(ctx.expr(), None, self._parser))
|
||||
print("and block is:")
|
||||
print(Trees.toStringTree(ctx.stat_block(), None, self._parser))
|
||||
raise NotImplementedError() # TODO (Exercise 5)
|
||||
# visit statements
|
||||
|
||||
def visitPrintlnintStat(self, ctx) -> None:
|
||||
expr_loc = self.visit(ctx.expr())
|
||||
if self._debug:
|
||||
print("print_int statement, expression is:")
|
||||
print(Trees.toStringTree(ctx.expr(), None, self._parser))
|
||||
self._current_function.add_instruction_PRINTLN_INT(expr_loc)
|
||||
|
||||
def visitPrintlnboolStat(self, ctx) -> None:
|
||||
expr_loc = self.visit(ctx.expr())
|
||||
self._current_function.add_instruction_PRINTLN_INT(expr_loc)
|
||||
|
||||
def visitPrintlnfloatStat(self, ctx) -> None:
|
||||
raise MiniCUnsupportedError("Unsupported type float")
|
||||
|
||||
def visitPrintlnstringStat(self, ctx) -> None:
|
||||
raise MiniCUnsupportedError("Unsupported type string")
|
||||
|
||||
def visitStatList(self, ctx) -> None:
|
||||
for stat in ctx.stat():
|
||||
self._current_function.add_comment(Trees.toStringTree(stat, None, self._parser))
|
||||
self.visit(stat)
|
||||
@@ -0,0 +1,9 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
println_int(42);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 42
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x,y;
|
||||
x=4;
|
||||
y=12+x;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int a,n;
|
||||
n=1;
|
||||
a=n+12;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int n;
|
||||
n=6;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
println_int(43);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 43
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x;
|
||||
x = 42;
|
||||
println_int(x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 42
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x;
|
||||
x = 42;
|
||||
println_int(x + x);
|
||||
println_int(x + 1);
|
||||
println_int(1 + x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 84
|
||||
// 43
|
||||
// 43
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x, y;
|
||||
x = 42;
|
||||
y = 66;
|
||||
println_int(x + y);
|
||||
x = 1;
|
||||
println_int(x + y);
|
||||
y = 2;
|
||||
println_int(x + y);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 108
|
||||
// 67
|
||||
// 3
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x,y;
|
||||
|
||||
x = 9;
|
||||
if (x < 2)
|
||||
y=7;
|
||||
else
|
||||
y=12;
|
||||
x = y;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int n;
|
||||
bool a,b;
|
||||
n=1;
|
||||
a=true;
|
||||
b=(a==(n<6));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,13 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x,y;
|
||||
x=3;
|
||||
if (x<5) {
|
||||
y=x+1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,16 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x,y,z;
|
||||
x=2;
|
||||
if (x<3) {
|
||||
y=7;
|
||||
} else {
|
||||
y=8;
|
||||
}
|
||||
z=y+1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,20 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int x,y,z,u;
|
||||
x=3;
|
||||
if (x < 4) {
|
||||
z=4;
|
||||
}
|
||||
else if ( x < 5) {
|
||||
z=5;
|
||||
}
|
||||
else {
|
||||
z=6 ;
|
||||
}
|
||||
u=z+1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
bool b;
|
||||
b = false;
|
||||
println_bool(b);
|
||||
b = true;
|
||||
println_bool(b);
|
||||
println_bool(true);
|
||||
println_bool(false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 0
|
||||
// 1
|
||||
// 1
|
||||
// 0
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
println_bool(3 >= 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 1
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
if (10 == 10) {
|
||||
println_int(12);
|
||||
} else if (10 == 10) {
|
||||
println_int(15);
|
||||
} else {
|
||||
println_int(13);
|
||||
}
|
||||
println_int(14);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 12
|
||||
// 14
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int n;
|
||||
|
||||
n = 9;
|
||||
while (n > 0) {
|
||||
n = n-1 ;
|
||||
println_int(n) ;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 8
|
||||
// 7
|
||||
// 6
|
||||
// 5
|
||||
// 4
|
||||
// 3
|
||||
// 2
|
||||
// 1
|
||||
// 0
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "printlib.h"
|
||||
|
||||
int main() {
|
||||
|
||||
int a,n;
|
||||
|
||||
n = 1;
|
||||
a = 7;
|
||||
while (n < a) {
|
||||
n = n+1;
|
||||
}
|
||||
println_int(n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EXPECTED
|
||||
// 7
|
||||
@@ -0,0 +1,9 @@
|
||||
int main() {
|
||||
float f;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SKIP TEST EXPECTED
|
||||
// EXITCODE 5
|
||||
// EXPECTED
|
||||
// Unsupported type float
|
||||
@@ -0,0 +1,9 @@
|
||||
int main() {
|
||||
println_float(0.0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SKIP TEST EXPECTED
|
||||
// EXITCODE 5
|
||||
// EXPECTED
|
||||
// Unsupported type float
|
||||
@@ -0,0 +1,9 @@
|
||||
int main() {
|
||||
println_string("Hello");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SKIP TEST EXPECTED
|
||||
// EXITCODE 5
|
||||
// EXPECTED
|
||||
// Unsupported type string
|
||||
@@ -0,0 +1,9 @@
|
||||
int main() {
|
||||
string b;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// SKIP TEST EXPECTED
|
||||
// EXITCODE 5
|
||||
// EXPECTED
|
||||
// Unsupported type string
|
||||
@@ -0,0 +1 @@
|
||||
Add your own tests in this directory.
|
||||
Binary file not shown.
@@ -0,0 +1,241 @@
|
||||
#! /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_str_variable
|
||||
)
|
||||
|
||||
"""
|
||||
Usage:
|
||||
python3 test_codegen.py
|
||||
(or make test)
|
||||
"""
|
||||
|
||||
"""
|
||||
MIF08 and CAP, 2019
|
||||
Unit test infrastructure for testing code generation:
|
||||
1) compare the actual output to the expected one (in comments)
|
||||
2) compare the actual output to the one obtained by simulation
|
||||
3) for different allocation algorithms
|
||||
"""
|
||||
|
||||
MINICC_OPTS = []
|
||||
if "MINICC_OPTS" in os.environ and os.environ["MINICC_OPTS"]:
|
||||
MINICC_OPTS = os.environ["MINICC_OPTS"].split()
|
||||
else:
|
||||
MINICC_OPTS = ["--mode=codegen-cfg"]
|
||||
|
||||
DISABLE_TYPECHECK = "--disable-typecheck" in MINICC_OPTS
|
||||
|
||||
HERE = os.path.dirname(os.path.realpath(__file__))
|
||||
if HERE == os.path.realpath('.'):
|
||||
HERE = '.'
|
||||
TEST_DIR = HERE
|
||||
IMPLEM_DIR = HERE
|
||||
MINIC_COMPILE = os.path.join(IMPLEM_DIR, 'MiniCC.py')
|
||||
|
||||
ALL_FILES = glob.glob(os.path.join(TEST_DIR, 'TP04/tests/**/[a-zA-Z]*.c'), recursive=True)
|
||||
|
||||
ALLOC_FILES = glob.glob(os.path.join(HERE, 'TP05/tests/**/*.c'), recursive=True)
|
||||
|
||||
ASM = 'riscv64-unknown-elf-gcc'
|
||||
SIMU = 'spike'
|
||||
|
||||
SKIP_NOT_IMPLEMENTED = False
|
||||
if 'SKIP_NOT_IMPLEMENTED' in os.environ:
|
||||
SKIP_NOT_IMPLEMENTED = True
|
||||
|
||||
if 'TEST_FILES' in os.environ:
|
||||
ALL_FILES = glob.glob(os.environ['TEST_FILES'], recursive=True)
|
||||
|
||||
MINIC_EVAL = os.path.join(
|
||||
HERE, '..', '..', 'TP03', 'MiniC-type-interpret', 'Main.py')
|
||||
|
||||
# if 'COMPIL_MINIC_EVAL' in os.environ:
|
||||
# MINIC_EVAL = os.environ['COMPIL_MINIC_EVAL']
|
||||
# else:
|
||||
# MINIC_EVAL = os.path.join(
|
||||
# HERE, '..', '..', 'TP03', 'MiniC-type-interpret', 'Main.py')
|
||||
|
||||
# Avoid duplicates
|
||||
ALL_IN_MEM_FILES = list(set(ALL_FILES) | set(ALLOC_FILES))
|
||||
ALL_IN_MEM_FILES.sort()
|
||||
ALL_FILES = list(set(ALL_FILES))
|
||||
ALL_FILES.sort()
|
||||
|
||||
if 'TEST_FILES' in os.environ:
|
||||
ALLOC_FILES = ALL_FILES
|
||||
ALL_IN_MEM_FILES = ALL_FILES
|
||||
|
||||
|
||||
class TestCodeGen(TestExpectPragmas):
|
||||
# Not in test_expect_pragma to get assertion rewritting
|
||||
def assert_equal(self, actual, expected):
|
||||
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 (spike) is incorrect"
|
||||
|
||||
def naive_alloc(self, file, info):
|
||||
return self.compile_and_simulate(file, info, reg_alloc='naive')
|
||||
|
||||
def all_in_mem(self, file, info):
|
||||
return self.compile_and_simulate(file, info, reg_alloc='all-in-mem')
|
||||
|
||||
def smart_alloc(self, file, info):
|
||||
return self.compile_and_simulate(file, info, reg_alloc='smart')
|
||||
|
||||
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_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, MINIC_COMPILE,
|
||||
alloc_opt, out_opt]
|
||||
cmd += 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")
|
||||
elif reg_alloc == 'all-in-mem':
|
||||
pytest.skip("Too big for the all in memory allocator")
|
||||
else:
|
||||
raise Exception("AllocationError should only happen "
|
||||
"for reg_alloc='naive' or reg_alloc='all_in_mem'")
|
||||
elif ("NotImplementedError" in result.output and
|
||||
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
|
||||
assert(os.path.isfile(output_name))
|
||||
print("Compiling ... OK")
|
||||
return result
|
||||
|
||||
def link_and_run(self, output_name, exec_name, info):
|
||||
self.remove(exec_name)
|
||||
cmd = [
|
||||
ASM, output_name, '../TP01/riscv/libprint.s',
|
||||
'-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)
|
||||
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_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 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)
|
||||
|
||||
@pytest.mark.parametrize('filename', ALL_FILES)
|
||||
def test_expect(self, filename):
|
||||
"""Test the EXPECTED annotations in test files by launching the
|
||||
program with GCC."""
|
||||
expect = self.get_expect(filename)
|
||||
if expect.skip_test_expected:
|
||||
pytest.skip("Skipping test because it 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)
|
||||
|
||||
@pytest.mark.parametrize('filename', ALL_FILES)
|
||||
def test_naive_alloc(self, filename):
|
||||
expect = self.get_expect(filename)
|
||||
naive = self.naive_alloc(filename, expect)
|
||||
self.assert_equal(naive, expect)
|
||||
|
||||
@pytest.mark.parametrize('filename', ALL_IN_MEM_FILES)
|
||||
def test_alloc_mem(self, filename):
|
||||
expect = self.get_expect(filename)
|
||||
actual = self.all_in_mem(filename, expect)
|
||||
self.assert_equal(actual, expect)
|
||||
|
||||
@pytest.mark.parametrize('filename', ALLOC_FILES)
|
||||
def test_smart_alloc(self, filename):
|
||||
"""Generate code with smart allocation."""
|
||||
expect = self.get_expect(filename)
|
||||
actual = self.smart_alloc(filename, expect)
|
||||
self.assert_equal(actual, expect)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pytest.main(sys.argv)
|
||||
Reference in New Issue
Block a user