You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ChocoPy/src/main/java/chocopy/common/codegen/CodeGenBase.java

881 lines
36 KiB

package chocopy.common.codegen;
import chocopy.common.analysis.AbstractNodeAnalyzer;
import chocopy.common.analysis.SymbolTable;
import chocopy.common.analysis.types.Type;
import chocopy.common.analysis.types.ValueType;
import chocopy.common.astnodes.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static chocopy.common.Utils.*;
import static chocopy.common.codegen.RiscVBackend.Register.*;
/**
* The code generator for a ChocoPy program.
*
* <p>This class implements logic to analyze all declarations in a program and create descriptors
* for classes, functions, methods, variables (global and local), and attributes. This logic also
* builds symbol tables for globals and individual functions.
*
* <p>This class also implements logic to emit global variables, object prototypes and dispatch
* tables, as well as int/str/bool constants.
*
* <p>However, this class is abstract because it does not implement logic for emitting executable
* code in bodies of user-defined functions as well as in top-level statements. This class should be
* extended with implementations for such logic.
*
* <p>All non-public members of this class are `protected`, and can be overridden by sub-classes to
* extend change functionality.
*
* <p>The SymbolInfo classes can also be overridden. If say you want to use your own extended
* FuncInfo called MyFuncInfo (that extends FuncInfo), then override the makeFuncInfo() method of
* this class to `return new MyFuncInfo(...)` instead. This is probably not needed, though.
*/
public abstract class CodeGenBase {
/** The location of the text resources containing common library code. */
protected static final String LIBRARY_CODE_DIR = "chocopy/common/";
/** The backend that emits assembly. */
protected final RiscVBackend backend;
/** Convenience variable: the word size for the current back end. */
protected final int wordSize;
/** A counter for generating unique class type tags. */
protected int nextTypeTag = 0;
/** A counter used to generate unique local label names. */
protected int nextLabelSuffix = 0;
/**
* Predefined classes. The list "class" is a fake class; we use it only to emit a prototype
* object for empty lists.
*/
protected ClassInfo objectClass, intClass, boolClass, strClass, listClass;
/** Predefined functions. */
protected FuncInfo printFunc, lenFunc, inputFunc;
/** A list of global variables, whose initial values are emitted in the backend. */
protected final List<GlobalVarInfo> globalVars = new ArrayList<>();
/**
* A list of program classes, whose prototype objects and dispatch tables are emitted in the
* backend.
*/
protected final List<ClassInfo> classes = new ArrayList<>();
/**
* A list of functions (including methods and nested functions) whose bodies are emitted in the
* backend.
*/
protected final List<FuncInfo> functions = new ArrayList<>();
/** Label for built-in routine: alloc. */
protected final Label objectAllocLabel = new Label("alloc");
/** Label for built-in routine: alloc2. */
protected final Label objectAllocResizeLabel = new Label("alloc2");
/** Label for built-in routine: abort. */
protected final Label abortLabel = new Label("abort");
/** Label for built-in routine: heap.init. */
protected final Label heapInitLabel = new Label("heap.init");
/** Error codes. */
protected final int ERROR_ARG = 1,
ERROR_DIV_ZERO = 2,
ERROR_OOB = 3,
ERROR_NONE = 4,
ERROR_OOM = 5,
ERROR_NYI = 6;
/** Size of heap memory. */
protected final int HEAP_SIZE_BYTES = 1024 * 1024 * 32;
/** Ecall numbers for intrinsic routines. */
protected final int EXIT_ECALL = 10,
EXIT2_ECALL = 17,
PRINT_STRING_ECALL = 4,
PRINT_CHAR_ECALL = 11,
PRINT_INT_ECALL = 1,
READ_STRING_ECALL = 8,
FILL_LINE_BUFFER__ECALL = 18,
SBRK_ECALL = 9;
/**
* The symbol table that maps global names to information about the bound global variables,
* global functions, or classes.
*/
protected final SymbolTable<SymbolInfo> globalSymbols = new SymbolTable<>();
/** A utility for caching constants and generating labels for constants. */
protected final Constants constants = new Constants();
/** The object header size, in words (includes type tag, size, and dispatch table pointer). */
public static final int HEADER_SIZE = 3;
/**
* Initializes a code generator for ChocoPy that uses BACKEND to emit assembly code.
*
* <p>The constructor creates Info objects for predefined functions, classes, methods, and
* built-in routines.
*/
public CodeGenBase(RiscVBackend backend) {
this.backend = backend;
wordSize = backend.getWordSize();
initClasses();
initFunctions();
initAsmConstants();
}
/** Return a fresh type tag. */
protected int getNextTypeTag() {
return nextTypeTag++;
}
/** Returns the next unique label suffix. */
protected int getNextLabelSuffix() {
return nextLabelSuffix++;
}
/**
* Return a fresh label.
*
* <p>This label is guaranteed to be unique amongst labels generated by invoking this method.
* All such labels have a prefix of `label_`.
*
* <p>This is useful to generate local labels in function bodies (e.g. for targets of jumps),
* where the name does not matter in general.
*/
protected Label generateLocalLabel() {
return new Label(String.format("label_%d", getNextLabelSuffix()));
}
/**
* Generates assembly code for PROGRAM.
*
* <p>This is the main driver that calls internal methods for emitting DATA section (globals,
* constants, prototypes, etc) as well as the the CODE section (predefined functions, built-in
* routines, and user-defined functions).
*/
public void generate(Program program) {
analyzeProgram(program);
backend.startData();
for (ClassInfo classInfo : this.classes) {
emitPrototype(classInfo);
}
for (ClassInfo classInfo : this.classes) {
emitDispatchTable(classInfo);
}
for (GlobalVarInfo global : this.globalVars) {
backend.emitGlobalLabel(global.getLabel());
emitConstant(
global.getInitialValue(),
global.getVarType(),
String.format("Initial value of global var: %s", global.getVarName()));
}
backend.startCode();
Label mainLabel = new Label("main");
backend.emitGlobalLabel(mainLabel);
backend.emitLUI(A0, HEAP_SIZE_BYTES >> 12, "Initialize heap size (in multiples of 4KB)");
backend.emitADD(S11, S11, A0, "Save heap size");
backend.emitJAL(heapInitLabel, "Call heap.init routine");
backend.emitMV(GP, A0, "Initialize heap pointer");
backend.emitMV(S10, GP, "Set beginning of heap");
backend.emitADD(S11, S10, S11, "Set end of heap (= start of heap + heap size)");
backend.emitMV(RA, ZERO, "No normal return from main program.");
backend.emitMV(FP, ZERO, "No preceding frame.");
emitTopLevel(program.statements);
for (FuncInfo funcInfo : this.functions) {
funcInfo.emitBody();
}
emitStdFunc("alloc");
emitStdFunc("alloc2");
emitStdFunc("abort");
emitStdFunc("heap.init");
emitCustomCode();
backend.startData();
emitConstants();
}
/** Create descriptors and symbols for builtin classes and methods. */
protected void initClasses() {
FuncInfo objectInit =
makeFuncInfo(
"object.__init__",
0,
Type.NONE_TYPE,
globalSymbols,
null,
this::emitStdFunc);
objectInit.addParam(makeStackVarInfo("self", Type.OBJECT_TYPE, null, objectInit));
functions.add(objectInit);
objectClass = makeClassInfo("object", getNextTypeTag(), null);
objectClass.addMethod(objectInit);
classes.add(objectClass);
globalSymbols.put(objectClass.getClassName(), objectClass);
intClass = makeClassInfo("int", getNextTypeTag(), objectClass);
intClass.addAttribute(makeAttrInfo("__int__", null, null));
classes.add(intClass);
globalSymbols.put(intClass.getClassName(), intClass);
boolClass = makeClassInfo("bool", getNextTypeTag(), objectClass);
boolClass.addAttribute(makeAttrInfo("__bool__", null, null));
classes.add(boolClass);
globalSymbols.put(boolClass.getClassName(), boolClass);
strClass = makeClassInfo("str", getNextTypeTag(), objectClass);
strClass.addAttribute(
makeAttrInfo("__len__", Type.INT_TYPE, new IntegerLiteral(null, null, 0)));
strClass.addAttribute(makeAttrInfo("__str__", null, null));
classes.add(strClass);
globalSymbols.put(strClass.getClassName(), strClass);
listClass = makeClassInfo(".list", -1, objectClass);
listClass.addAttribute(
makeAttrInfo("__len__", Type.INT_TYPE, new IntegerLiteral(null, null, 0)));
classes.add(listClass);
listClass.dispatchTableLabel = null;
}
/** Create descriptors and symbols for builtin functions. */
protected void initFunctions() {
printFunc =
makeFuncInfo("print", 0, Type.NONE_TYPE, globalSymbols, null, this::emitStdFunc);
printFunc.addParam(makeStackVarInfo("arg", Type.OBJECT_TYPE, null, printFunc));
functions.add(printFunc);
globalSymbols.put(printFunc.getBaseName(), printFunc);
lenFunc = makeFuncInfo("len", 0, Type.INT_TYPE, globalSymbols, null, this::emitStdFunc);
lenFunc.addParam(makeStackVarInfo("arg", Type.OBJECT_TYPE, null, lenFunc));
functions.add(lenFunc);
globalSymbols.put(lenFunc.getBaseName(), lenFunc);
inputFunc = makeFuncInfo("input", 0, Type.STR_TYPE, globalSymbols, null, this::emitStdFunc);
functions.add(inputFunc);
globalSymbols.put(inputFunc.getBaseName(), inputFunc);
}
/* Symbolic assembler constants defined here (to add others, override
* initAsmConstants in an extension of CodeGenBase):
* ecalls:
* @sbrk
* @fill_line_buffer
* @read_string
* @print_string
* @print_char
* @print_int
* @exit2
* Exit codes:
* @error_div_zero: Division by 0.
* @error_arg: Bad argument.
* @error_oob: Out of bounds.
* @error_none: Attempt to access attribute of None.
* @error_oom: Out of memory.
* @error_nyi: Unimplemented operation.
* Data-structure byte offsets:
* @.__obj_size__: Offset of size of object.
* @.__len__: Offset of length in chars or words.
* @.__str__: Offset of string data.
* @.__elts__: Offset of first list item.
* @.__int__: Offset of integer value.
* @.__bool__: Offset of boolean (1/0) value.
*/
/** Define @-constants to be used in assembly code. */
protected void initAsmConstants() {
backend.defineSym("sbrk", SBRK_ECALL);
backend.defineSym("print_string", PRINT_STRING_ECALL);
backend.defineSym("print_char", PRINT_CHAR_ECALL);
backend.defineSym("print_int", PRINT_INT_ECALL);
backend.defineSym("exit2", EXIT2_ECALL);
backend.defineSym("read_string", READ_STRING_ECALL);
backend.defineSym("fill_line_buffer", FILL_LINE_BUFFER__ECALL);
backend.defineSym(".__obj_size__", 4);
backend.defineSym(".__len__", 12);
backend.defineSym(".__int__", 12);
backend.defineSym(".__bool__", 12);
backend.defineSym(".__str__", 16);
backend.defineSym(".__elts__", 16);
backend.defineSym("error_div_zero", ERROR_DIV_ZERO);
backend.defineSym("error_arg", ERROR_ARG);
backend.defineSym("error_oob", ERROR_OOB);
backend.defineSym("error_none", ERROR_NONE);
backend.defineSym("error_oom", ERROR_OOM);
backend.defineSym("error_nyi", ERROR_NYI);
}
/*-----------------------------------------------------------*/
/* */
/* FACTORY METHODS TO CREATE INFO OBJECTS */
/* */
/*-----------------------------------------------------------*/
/**
* A factory method that returns a descriptor for function or method FUNCNAME returning type
* RETURNTYPE at nesting level DEPTH in the region corresponding to PARENTSYMBOLTABLE.
*
* <p>PARENTFUNCINFO is a descriptor of the enclosing function and is null for global functions
* and methods.
*
* <p>EMITTER is a method that emits the function's body (usually a generic emitter for
* user-defined functions/methods, and a special emitter for pre-defined functions/methods).
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* FuncInfo with more functionality.
*/
protected FuncInfo makeFuncInfo(
String funcName,
int depth,
ValueType returnType,
SymbolTable<SymbolInfo> parentSymbolTable,
FuncInfo parentFuncInfo,
Consumer<FuncInfo> emitter) {
return new FuncInfo(
funcName, depth, returnType, parentSymbolTable, parentFuncInfo, emitter);
}
/**
* Return a descriptor for a class named CLASSNAME having type tag TYPETAG and superclass
* SUPERCLASSINFO (null for `object' only).
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* ClassInfo with more functionality.
*/
public ClassInfo makeClassInfo(String className, int typeTag, ClassInfo superClassInfo) {
return new ClassInfo(className, typeTag, superClassInfo);
}
/**
* A factory method that returns a descriptor for an attribute named ATTRNAME of type ATTRTYPE
* and with an initial value specified by INITIALVALUE, which may be null to indicate a default
* initialization.
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* AttrInfo with more functionality.
*/
public AttrInfo makeAttrInfo(String attrName, ValueType attrType, Literal initialValue) {
return new AttrInfo(attrName, attrType, initialValue);
}
/**
* A factory method that returns a descriptor for a local variable or parameter named VARNAME of
* type VARTYPE, whose initial value is specified by INITIALVALUE (if non-null) and which is
* defined immediately within the function given by FUNCINFO.
*
* <p>These variables are allocated on the stack in activation frames.
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* StackVarInfo with more functionality.
*/
public StackVarInfo makeStackVarInfo(
String varName, ValueType varType, Literal initialValue, FuncInfo funcInfo) {
return new StackVarInfo(varName, varType, initialValue, funcInfo);
}
/**
* A factory method that returns a descriptor for a global variable with name VARNAME and type
* VARTYPE, whose initial value is specified by INITIALVALUE (if non-null).
*
* <p>Sub-classes of CodeGenBase can override this method if they wish to use a sub-class of
* GlobalVarInfo with more functionality.
*/
public GlobalVarInfo makeGlobalVarInfo(
String varName, ValueType varType, Literal initialValue) {
return new GlobalVarInfo(varName, varType, initialValue);
}
/*-----------------------------------------------------------*
* *
* ANALYSIS OF AST INTO INFO OBJECTS *
* (Students can ignore these methods as all the work has *
* been done and does not need to be modified/extended) *
* *
*-----------------------------------------------------------*/
/** Analyze PROGRAM, creating Info objects for all symbols. Populate the global symbol table. */
protected void analyzeProgram(Program program) {
/* Proceed in phases:
* 1. Analyze all global variable declarations.
* Do this first so that global variables are in the symbol
* table before we encounter `global x` declarations.
* 2. Analyze classes and global functions now that global variables
* are in the symbol table.
*/
for (Declaration decl : program.declarations) {
if (decl instanceof VarDef) {
VarDef varDef = (VarDef) decl;
ValueType varType = ValueType.annotationToValueType(varDef.var.type);
GlobalVarInfo globalVar =
makeGlobalVarInfo(varDef.var.identifier.name, varType, varDef.value);
this.globalVars.add(globalVar);
this.globalSymbols.put(globalVar.getVarName(), globalVar);
}
}
for (Declaration decl : program.declarations) {
if (decl instanceof ClassDef) {
ClassDef classDef = (ClassDef) decl;
ClassInfo classInfo = analyzeClass(classDef);
this.classes.add(classInfo);
this.globalSymbols.put(classInfo.getClassName(), classInfo);
} else if (decl instanceof FuncDef) {
FuncDef funcDef = (FuncDef) decl;
FuncInfo funcInfo = analyzeFunction(null, funcDef, 0, globalSymbols, null);
this.functions.add(funcInfo);
this.globalSymbols.put(funcInfo.getBaseName(), funcInfo);
}
}
}
/**
* Analyze a class definition CLASSDEF and return the resulting Info object. Also creates Info
* objects for attributes/methods and stores them in the ClassInfo. Methods are recursively
* analyzed using analyzeFunction().
*/
protected ClassInfo analyzeClass(ClassDef classDef) {
String className = classDef.name.name;
String superClassName = classDef.superClass.name;
SymbolInfo superSymbolInfo = globalSymbols.get(superClassName);
assert superSymbolInfo instanceof ClassInfo
: "Semantic analysis should ensure that super-class is defined";
ClassInfo superClassInfo = (ClassInfo) superSymbolInfo;
ClassInfo classInfo = makeClassInfo(className, getNextTypeTag(), superClassInfo);
for (Declaration decl : classDef.declarations) {
if (decl instanceof VarDef) {
VarDef attrDef = (VarDef) decl;
ValueType attrType = ValueType.annotationToValueType(attrDef.var.type);
AttrInfo attrInfo =
makeAttrInfo(attrDef.var.identifier.name, attrType, attrDef.value);
classInfo.addAttribute(attrInfo);
} else if (decl instanceof FuncDef) {
FuncDef funcDef = (FuncDef) decl;
FuncInfo methodInfo = analyzeFunction(className, funcDef, 0, globalSymbols, null);
this.functions.add(methodInfo);
classInfo.addMethod(methodInfo);
}
}
return classInfo;
}
/**
* Analyze a function or method definition FUNCDEF at nesting depth DEPTH and return the
* resulting Info object. Analyze any nested functions recursively. The FuncInfo's symbol table
* is completely populated by analyzing all the params, local vars, global and nonlocal var
* declarations.
*
* <p>CONTAINER is the fully qualified name of the containing function/class, or null for global
* functions. PARENTSYMBOLTABLE symbol table contains symbols inherited from outer regions (that
* of the containing function/method for nested function definitions, and the global symbol
* table for global function / method definitions). PARENTFUNCINFO is the Info object for the
* parent function/method if this definition is nested, and otherwise null.
*/
protected FuncInfo analyzeFunction(
String container,
FuncDef funcDef,
int depth,
SymbolTable<SymbolInfo> parentSymbolTable,
FuncInfo parentFuncInfo) {
/* We proceed in three steps.
* 1. Create the FuncInfo object to be returned.
* 2. Populate it by analyzing all the parameters and local var
* definitions.
* 3. Now that the function's symbol table is built up, analyze
* nested function definitions.
* 4. Add the body to the function descriptor for code gen.
*/
String funcBaseName = funcDef.name.name;
String funcQualifiedName =
container != null ? String.format("%s.%s", container, funcBaseName) : funcBaseName;
FuncInfo funcInfo =
makeFuncInfo(
funcQualifiedName,
depth,
ValueType.annotationToValueType(funcDef.returnType),
parentSymbolTable,
parentFuncInfo,
this::emitUserDefinedFunction);
for (TypedVar param : funcDef.params) {
ValueType paramType = ValueType.annotationToValueType(param.type);
StackVarInfo paramInfo =
makeStackVarInfo(param.identifier.name, paramType, null, funcInfo);
funcInfo.addParam(paramInfo);
}
LocalDeclAnalyzer localDefs = new LocalDeclAnalyzer(funcInfo);
for (Declaration decl : funcDef.declarations) {
decl.dispatch(localDefs);
}
NestedFuncAnalyzer nestedFuncs = new NestedFuncAnalyzer(funcInfo);
for (Declaration decl : funcDef.declarations) {
decl.dispatch(nestedFuncs);
}
funcInfo.addBody(funcDef.statements);
return funcInfo;
}
/** Analyzer for local variable declarations in a function. */
protected class LocalDeclAnalyzer extends AbstractNodeAnalyzer<Void> {
/** The descriptor for the function being analyzed. */
private final FuncInfo funcInfo;
/** A new analyzer for a function with descriptor FUNCINFO0. */
protected LocalDeclAnalyzer(FuncInfo funcInfo0) {
funcInfo = funcInfo0;
}
@Override
public Void analyze(VarDef localVarDef) {
ValueType localVarType = ValueType.annotationToValueType(localVarDef.var.type);
StackVarInfo localVar =
makeStackVarInfo(
localVarDef.var.identifier.name,
localVarType,
localVarDef.value,
funcInfo);
funcInfo.addLocal(localVar);
return null;
}
@Override
public Void analyze(GlobalDecl decl) {
SymbolInfo symInfo = globalSymbols.get(decl.getIdentifier().name);
assert symInfo instanceof GlobalVarInfo
: "Semantic analysis should ensure that global var exists";
GlobalVarInfo globalVar = (GlobalVarInfo) symInfo;
funcInfo.getSymbolTable().put(globalVar.getVarName(), globalVar);
return null;
}
@Override
public Void analyze(NonLocalDecl decl) {
assert funcInfo.getSymbolTable().get(decl.getIdentifier().name) instanceof StackVarInfo
: "Semantic analysis should ensure nonlocal var exists";
return null;
}
}
/** Analyzer for nested function declarations in a function. */
protected class NestedFuncAnalyzer extends AbstractNodeAnalyzer<Void> {
/** Descriptor for the function being analyzed. */
private final FuncInfo funcInfo;
/** A new analyzer for a function with descriptor FUNCINFO0. */
protected NestedFuncAnalyzer(FuncInfo funcInfo0) {
funcInfo = funcInfo0;
}
@Override
public Void analyze(FuncDef nestedFuncDef) {
FuncInfo nestedFuncInfo =
analyzeFunction(
funcInfo.getFuncName(),
nestedFuncDef,
funcInfo.getDepth() + 1,
funcInfo.getSymbolTable(),
funcInfo);
functions.add(nestedFuncInfo);
funcInfo.getSymbolTable().put(nestedFuncInfo.getBaseName(), nestedFuncInfo);
return null;
}
}
/*------------------------------------------------------------*
* *
* EMITING DATA SECTION FOR GLOBALS+PROTOTYPES+CONSTANTS *
* (Students can ignore these methods as all the work has *
* been done and does not need to be modified/extended) *
* *
*------------------------------------------------------------*/
/** Emit code to align next data item to word boundary. */
protected void alignObject() {
int wordSizeLog2 = 31 - Integer.numberOfLeadingZeros(wordSize);
backend.alignNext(wordSizeLog2);
}
/** Emit the constant section containing the prototype FOR the class defined by CLASSINFO. */
protected void emitPrototype(ClassInfo classInfo) {
backend.emitGlobalLabel(classInfo.getPrototypeLabel());
backend.emitWordLiteral(
classInfo.getTypeTag(),
String.format("Type tag for class: %s", classInfo.getClassName()));
backend.emitWordLiteral(classInfo.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(classInfo.getDispatchTableLabel(), "Pointer to dispatch table");
for (VarInfo attr : classInfo.attributes) {
String cmnt = String.format("Initial value of attribute: %s", attr.getVarName());
emitConstant(attr.getInitialValue(), attr.getVarType(), cmnt);
}
alignObject();
}
/** Emit a word containing a constant int representing VALUE */
protected void emitConstantInt(int value, String comment) {
backend.emitWordLiteral(value, comment);
}
/** Emit a word containing a constant bool representing VALUE */
protected void emitConstantBool(boolean value, String comment) {
backend.emitWordLiteral(value ? 1 : 0, comment);
}
/**
* Emit a word containing a constant representing VALUE, assuming that it will be interpreted as
* a value of static type TYPE. VALUE may be null, indicating None. TYPE may be null, indicating
* object. COMMENT is an optional comment.
*/
protected void emitConstant(Literal value, ValueType type, String comment) {
if (type != null && type.equals(Type.INT_TYPE)) {
this.emitConstantInt(((IntegerLiteral) value).value, comment);
} else if (type != null && type.equals(Type.BOOL_TYPE)) {
this.emitConstantBool(((BooleanLiteral) value).value, comment);
} else {
backend.emitWordAddress(constants.fromLiteral(value), comment);
}
}
/** Emit code for all constants. */
protected void emitConstants() {
backend.emitGlobalLabel(constants.falseConstant);
backend.emitWordLiteral(boolClass.getTypeTag(), "Type tag for class: bool");
backend.emitWordLiteral(boolClass.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(boolClass.getDispatchTableLabel(), "Pointer to dispatch table");
backend.emitWordLiteral(0, "Constant value of attribute: __bool__");
alignObject();
backend.emitGlobalLabel(constants.trueConstant);
backend.emitWordLiteral(boolClass.getTypeTag(), "Type tag for class: bool");
backend.emitWordLiteral(boolClass.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(boolClass.getDispatchTableLabel(), "Pointer to dispatch table");
backend.emitWordLiteral(1, "Constant value of attribute: __bool__");
alignObject();
for (Map.Entry<String, Label> e : constants.strConstants.entrySet()) {
String value = e.getKey();
Label label = e.getValue();
int numWordsForCharacters = value.length() / wordSize + 1;
backend.emitGlobalLabel(label);
backend.emitWordLiteral(strClass.getTypeTag(), "Type tag for class: str");
backend.emitWordLiteral(3 + 1 + numWordsForCharacters, "Object size");
backend.emitWordAddress(strClass.getDispatchTableLabel(), "Pointer to dispatch table");
this.emitConstantInt(value.length(), "Constant value of attribute: __len__");
backend.emitString(value, "Constant value of attribute: __str__");
alignObject();
}
for (Map.Entry<Integer, Label> e : constants.intConstants.entrySet()) {
Integer value = e.getKey();
Label label = e.getValue();
backend.emitGlobalLabel(label);
backend.emitWordLiteral(intClass.getTypeTag(), "Type tag for class: int");
backend.emitWordLiteral(intClass.attributes.size() + HEADER_SIZE, "Object size");
backend.emitWordAddress(intClass.getDispatchTableLabel(), "Pointer to dispatch table");
backend.emitWordLiteral(value, "Constant value of attribute: __int__");
alignObject();
}
}
/** Emit the method dispatching table for CLASSINFO. */
protected void emitDispatchTable(ClassInfo classInfo) {
Label dispatchTableLabel = classInfo.getDispatchTableLabel();
if (dispatchTableLabel == null) {
return;
}
backend.emitGlobalLabel(dispatchTableLabel);
for (FuncInfo method : classInfo.methods) {
String cmnt =
String.format(
"Implementation for method: %s.%s",
classInfo.getClassName(), method.getBaseName());
backend.emitWordAddress(method.getCodeLabel(), cmnt);
}
}
/*------------------------------------------------------------*
* *
* UTILITY METHODS TO GET BYTE OFFSETS IN OBJECT LAYOUT *
* (Students will find these methods helpful to use in *
* their sub-class when generating code for expressions) *
* *
*------------------------------------------------------------*/
/** Return offset of the type-tag field in an object. */
protected int getTypeTagOffset() {
return 0 * wordSize;
}
/** Return offset of the size field in an object. */
protected int getObjectSizeOffset() {
return 1 * wordSize;
}
/** Return offset of the start of the pointer to the method-dispatching table in an object. */
protected int getDispatchTableOffset() {
return 2 * wordSize;
}
/** Return the offset of the ATTRNAME attribute of an object of type described by CLASSINFO. */
protected int getAttrOffset(ClassInfo classInfo, String attrName) {
int attrIndex = classInfo.getAttributeIndex(attrName);
assert attrIndex >= 0 : "Type checker ensures that attributes are valid";
return wordSize * (HEADER_SIZE + attrIndex);
}
/**
* Return the offset of the method named METHODNAME in the method-dispatching table for the
* class described by CLASSINFO.
*/
protected int getMethodOffset(ClassInfo classInfo, String methodName) {
int methodIndex = classInfo.getMethodIndex(methodName);
assert methodIndex >= 0 : "Type checker ensures that attributes are valid";
return wordSize * methodIndex;
}
/*------------------------------------------------------------*
* *
* UNIMPLEMENTED METHODS (should be extended) *
* *
*------------------------------------------------------------*/
/** Emits code for STATEMENTS, assumed to be at the top level. */
protected abstract void emitTopLevel(List<Stmt> statements);
/** Emits code for the body of user-defined function FUNCINFO. */
protected abstract void emitUserDefinedFunction(FuncInfo funcInfo);
/**
* Emits code outside the ChocoPy program.
*
* <p>Custom assembly routines (that may be jumpable from program statements) can be emitted
* here.
*/
protected abstract void emitCustomCode();
/*------------------------------------------------------------*
* *
* PREDEFINED FUNCTIONS AND ROUTINES *
* (Students may find a cursory read of these methods to *
* be useful to get an idea for how code can be emitted) *
* *
*------------------------------------------------------------*/
/**
* Return Risc V assembler code for function NAME from directory LIB, or null if it does not
* exist. LIB must end in '/'.
*/
protected String getStandardLibraryCode(String name, String lib) {
String simpleName = name.replace("$", "") + ".s";
return getResourceFileAsString(lib + simpleName);
}
/**
* Emit label and body for the function LABEL, taking the source from directory LIB (must end in
* '/').
*/
protected void emitStdFunc(Label label, String lib) {
emitStdFunc(label, label.toString(), lib);
}
/**
* Emit label and body for the function LABEL, taking the source from SOURCEFILE.s in directory
* LIB (must end in '/').
*/
protected void emitStdFunc(Label label, String sourceFile, String lib) {
String source = getStandardLibraryCode(sourceFile, lib);
if (source == null) {
throw fatal("Code for %s is missing.", sourceFile);
}
backend.emitGlobalLabel(label);
backend.emit(convertLiterals(source));
}
/**
* Emit label and body for the function LABEL, taking the source from from the default library
* directory.
*/
protected void emitStdFunc(Label label) {
emitStdFunc(label, LIBRARY_CODE_DIR);
}
/**
* Emit label and body for the function NAME, taking the source from from the default library
* directory.
*/
protected void emitStdFunc(String name) {
emitStdFunc(new Label(name));
}
/**
* Emit label and body for the function described by FUNCINFO, taking the source from from the
* default library directory.
*/
protected void emitStdFunc(FuncInfo funcInfo) {
emitStdFunc(funcInfo.getCodeLabel());
}
/** Pattern matching STRING["..."]. */
private static final Pattern STRING_LITERAL_PATN = Pattern.compile("STRING\\[\"(.*?)\"\\]");
/**
* Return result of converting STRING["..."] notations in SOURCE to labels of string constants,
* adding those constants to the pool.
*/
private String convertLiterals(String source) {
Matcher matcher = STRING_LITERAL_PATN.matcher(source);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String r = constants.getStrConstant(matcher.group(1)).toString();
matcher.appendReplacement(
result, pad(r, ' ', matcher.end(0) - matcher.start(0), false));
}
return matcher.appendTail(result).toString();
}
}