mirror of
https://gitlab.com/camelot/kickc.git
synced 2024-06-10 10:29:36 +00:00
441 lines
14 KiB
Java
441 lines
14 KiB
Java
package dk.camelot64.kickc.model.symbols;
|
|
|
|
import dk.camelot64.kickc.model.Comment;
|
|
import dk.camelot64.kickc.model.Bank;
|
|
import dk.camelot64.kickc.model.Program;
|
|
import dk.camelot64.kickc.model.statements.StatementSource;
|
|
import dk.camelot64.kickc.model.types.SymbolType;
|
|
import dk.camelot64.kickc.model.types.SymbolTypeProcedure;
|
|
import dk.camelot64.kickc.model.values.ProcedureRef;
|
|
import dk.camelot64.kickc.passes.Pass1ByteXIntrinsicRewrite;
|
|
import dk.camelot64.kickc.passes.Pass1PrintfIntrinsicRewrite;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.Objects;
|
|
|
|
/** Symbol describing a procedure/function */
|
|
public class Procedure extends Scope {
|
|
|
|
/** The return type. {@link SymbolType#VOID} if the procedure does not return a value. */
|
|
private final SymbolTypeProcedure procedureType;
|
|
/** The names of the parameters of the procedure. */
|
|
private List<String> parameterNames;
|
|
/** True if the parameter list ends with a variable length parameter list "..." */
|
|
private boolean variableLengthParameterList;
|
|
/** true if the procedure is declared inline. */
|
|
private boolean declaredInline;
|
|
/** True if the procedure is declared intrinsic. */
|
|
private boolean declaredIntrinsic;
|
|
/** The type of interrupt that the procedure serves. Null for all procedures not serving an interrupt. */
|
|
private String interruptType;
|
|
/** Comments preceding the procedure in the source code. */
|
|
private List<Comment> comments;
|
|
/** Reserved zeropage addresses. */
|
|
private List<Integer> reservedZps;
|
|
/** The data and code segment to put the procedure into. When null the procedure is not assigned to the code segment. */
|
|
private String segmentCode;
|
|
/** The list of constructor procedures for this procedure. The constructor procedures are called during program initialization. */
|
|
private final List<ProcedureRef> constructorRefs;
|
|
/** Is this procedure declared as a constructor procedure. */
|
|
private boolean isConstructor;
|
|
/** The source of the procedure definition. */
|
|
private StatementSource definitionSource;
|
|
/** The bank segment information. Collected during parsing. These are used to compare with the current currentBank to decide a near or a far call, and to keep inline calling routines.
|
|
* When this value is null, the procedure is not allocated to a bank.
|
|
*/
|
|
private Bank bankLocation;
|
|
|
|
|
|
/** The names of all legal intrinsic procedures. */
|
|
final public static List<String> INTRINSIC_PROCEDURES = Arrays.asList(
|
|
Pass1PrintfIntrinsicRewrite.INTRINSIC_PRINTF_NAME,
|
|
Pass1PrintfIntrinsicRewrite.INTRINSIC_SNPRINTF_NAME,
|
|
Pass1PrintfIntrinsicRewrite.INTRINSIC_SPRINTF_NAME,
|
|
Pass1ByteXIntrinsicRewrite.INTRINSIC_MAKELONG4
|
|
);
|
|
|
|
public Bank getBankLocation() {
|
|
return bankLocation;
|
|
}
|
|
|
|
public void setBankLocation(Bank bankLocation) {
|
|
this.bankLocation = bankLocation;
|
|
}
|
|
|
|
public enum CallingProximity {
|
|
NEAR("near"),
|
|
CLOSE("close"),
|
|
FAR("far");
|
|
|
|
public String getProximity() {
|
|
return proximity;
|
|
}
|
|
|
|
private final String proximity;
|
|
|
|
CallingProximity(String proximity) {
|
|
this.proximity = proximity;
|
|
}
|
|
}
|
|
|
|
/** The method for expressing the call distance to implement banking
|
|
*
|
|
* The following variations exist related to banked calls:
|
|
* - #1 - unbanked to unbanked and no banking areas
|
|
* - #2 - unbanked to banked to any bank area
|
|
* - #3 - banked to unbanked from any bank area
|
|
* - #4 - banked to same bank in same bank area
|
|
* - #5 - banked to different bank in same bank area
|
|
* - #6 - banked to any bank between different bank areas
|
|
*
|
|
* This brings us to the call types:
|
|
* - CallingDistance.NEAR - case #1, #3, #4
|
|
* - CallingDistance.CLOSE - case #2, #6
|
|
* - CallingDistance.FAR - case #5
|
|
*/
|
|
public static class CallingDistance {
|
|
|
|
private CallingProximity proximity;
|
|
private String bankArea;
|
|
private Long bank;
|
|
|
|
public CallingProximity getProximity() {
|
|
return proximity;
|
|
}
|
|
|
|
public String getBankArea() {
|
|
return bankArea;
|
|
}
|
|
|
|
public Long getBank() {
|
|
return bank;
|
|
}
|
|
|
|
|
|
public CallingDistance(Procedure from, Procedure to) {
|
|
if (((!from.isDeclaredBanked() && !to.isDeclaredBanked())) ||
|
|
((from.isDeclaredBanked() && !to.isDeclaredBanked())) ||
|
|
((from.isDeclaredBanked() && to.isDeclaredBanked()) &&
|
|
(from.getBank() == to.getBank()) &&
|
|
(from.getBankArea().contentEquals(to.getBankArea()))
|
|
)
|
|
) {
|
|
// near call - case #1, #3, #4
|
|
this.proximity = CallingProximity.NEAR;
|
|
this.bankArea = "";
|
|
this.bank = 0L;
|
|
} else {
|
|
if ((!from.isDeclaredBanked() && to.isDeclaredBanked()) ||
|
|
((from.isDeclaredBanked() && to.isDeclaredBanked()) && (!from.getBankArea().contentEquals(to.getBankArea())))
|
|
) {
|
|
// close call - case #2, #6
|
|
this.proximity = CallingProximity.CLOSE;
|
|
this.bankArea = to.getBankArea();
|
|
this.bank = to.getBank();
|
|
} else {
|
|
// far call - case #5
|
|
this.proximity = CallingProximity.FAR;
|
|
this.bankArea = to.getBankArea();
|
|
this.bank = to.getBank();
|
|
}
|
|
}
|
|
}
|
|
|
|
public String getFragmentName() {
|
|
return this.proximity.getProximity() + (this.bankArea.isEmpty() ? "" : "_" + this.bankArea);
|
|
}
|
|
}
|
|
|
|
/** The method for passing parameters and return value to the procedure. */
|
|
public enum CallingConvention {
|
|
/** Parameters and return value handled through PHI-transitions. */
|
|
PHI_CALL("__phicall"),
|
|
/** Parameters and return value over the stack. */
|
|
STACK_CALL("__stackcall"),
|
|
/** Parameters and return value handled through shared variables. */
|
|
VAR_CALL("__varcall"),
|
|
/** Intrinsic calling. Will be converted to intrinsic ASM late in the compile. */
|
|
INTRINSIC_CALL("__intrinsiccall");
|
|
|
|
private final String name;
|
|
|
|
CallingConvention(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
/** Get a calling convention by name. */
|
|
public static CallingConvention getCallingConvension(String name) {
|
|
for(CallingConvention value : CallingConvention.values()) {
|
|
if(value.getName().equalsIgnoreCase(name)) {
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** The calling convention used for this procedure. */
|
|
private CallingConvention callingConvention;
|
|
|
|
public Procedure(String name, SymbolTypeProcedure procedureType, Scope parentScope, String segmentCode, String segmentData, CallingConvention callingConvention, Bank bankLocation) {
|
|
super(name, parentScope, segmentData);
|
|
this.procedureType = procedureType;
|
|
this.declaredInline = false;
|
|
this.bankLocation = bankLocation;
|
|
this.interruptType = null;
|
|
this.comments = new ArrayList<>();
|
|
this.segmentCode = segmentCode;
|
|
this.callingConvention = callingConvention;
|
|
this.constructorRefs = new ArrayList<>();
|
|
this.isConstructor = false;
|
|
}
|
|
|
|
public StatementSource getDefinitionSource() {
|
|
return definitionSource;
|
|
}
|
|
|
|
public void setDefinitionSource(StatementSource definitionSource) {
|
|
this.definitionSource = definitionSource;
|
|
}
|
|
|
|
public CallingConvention getCallingConvention() {
|
|
return callingConvention;
|
|
}
|
|
|
|
public void setCallingConvention(CallingConvention callingConvention) {
|
|
this.callingConvention = callingConvention;
|
|
}
|
|
|
|
public String getSegmentCode() {
|
|
return segmentCode;
|
|
}
|
|
|
|
public void setSegmentCode(String segmentCode) {
|
|
this.segmentCode = segmentCode;
|
|
}
|
|
|
|
public List<String> getParameterNames() {
|
|
return parameterNames;
|
|
}
|
|
|
|
public void setVariableLengthParameterList(boolean variableLengthParameterList) {
|
|
this.variableLengthParameterList = variableLengthParameterList;
|
|
}
|
|
|
|
public boolean isVariableLengthParameterList() {
|
|
return variableLengthParameterList;
|
|
}
|
|
|
|
public boolean isDeclaredIntrinsic() {
|
|
return declaredIntrinsic;
|
|
}
|
|
|
|
public void setDeclaredIntrinsic(boolean declaredIntrinsic) {
|
|
this.declaredIntrinsic = declaredIntrinsic;
|
|
}
|
|
|
|
public Label getLabel() {
|
|
return new Label(getFullName(), getScope(), false);
|
|
}
|
|
|
|
public SymbolType getReturnType() {
|
|
return procedureType.getReturnType();
|
|
}
|
|
|
|
public List<Variable> getParameters() {
|
|
ArrayList<Variable> parameters = new ArrayList<>();
|
|
for(String name : parameterNames) {
|
|
parameters.add(this.getLocalVar(name));
|
|
}
|
|
return parameters;
|
|
}
|
|
|
|
public void setParameters(List<Variable> parameters) {
|
|
this.parameterNames = new ArrayList<>();
|
|
for(Variable parameter : parameters) {
|
|
parameterNames.add(parameter.getLocalName());
|
|
}
|
|
}
|
|
|
|
public void setParameterNames(List<String> parameterNames) {
|
|
this.parameterNames = parameterNames;
|
|
}
|
|
|
|
public List<Comment> getComments() {
|
|
return comments;
|
|
}
|
|
|
|
public void setComments(List<Comment> comments) {
|
|
this.comments = comments;
|
|
}
|
|
|
|
@Override
|
|
public String getFullName() {
|
|
return super.getFullName();
|
|
}
|
|
|
|
public String toStringVars(Program program, boolean onlyVars) {
|
|
StringBuilder res = new StringBuilder();
|
|
res.append(toString(program));
|
|
res.append("\n");
|
|
res.append(super.toStringVars(program, onlyVars));
|
|
return res.toString();
|
|
}
|
|
|
|
@Override
|
|
public SymbolTypeProcedure getType() {
|
|
return procedureType;
|
|
}
|
|
|
|
public boolean isDeclaredInline() {
|
|
return declaredInline;
|
|
}
|
|
|
|
public void setDeclaredInline(boolean declaredInline) {
|
|
this.declaredInline = declaredInline;
|
|
}
|
|
|
|
public boolean isDeclaredBanked() {
|
|
return bankLocation != null;
|
|
}
|
|
|
|
public Long getBank() {
|
|
if(bankLocation != null)
|
|
return bankLocation.getBank();
|
|
else
|
|
return 0L;
|
|
}
|
|
|
|
public String getBankArea() {
|
|
if(bankLocation != null)
|
|
return bankLocation.getBankArea();
|
|
else
|
|
return "";
|
|
}
|
|
|
|
public String getInterruptType() {
|
|
return interruptType;
|
|
}
|
|
|
|
public void setInterruptType(String interruptType) {
|
|
this.interruptType = interruptType;
|
|
}
|
|
|
|
/**
|
|
* Sets any zero-page addresses reserved by the procedure. The KickC-compiler is not allowed to use these addresses.
|
|
*
|
|
* @return reserved addresses
|
|
*/
|
|
public List<Integer> getReservedZps() {
|
|
return reservedZps;
|
|
}
|
|
|
|
/**
|
|
* Gets any reserved zero-page addresses that the compiler is not allowed to use.
|
|
*
|
|
* @param reservedZps reserved addresses
|
|
*/
|
|
public void setReservedZps(List<Integer> reservedZps) {
|
|
this.reservedZps = reservedZps;
|
|
}
|
|
|
|
/**
|
|
* Get references to all constructors needed for this procedure
|
|
*
|
|
* @return The references
|
|
*/
|
|
public List<ProcedureRef> getConstructorRefs() {
|
|
return constructorRefs;
|
|
}
|
|
|
|
public boolean isConstructor() {
|
|
return isConstructor;
|
|
}
|
|
|
|
public void setConstructor(boolean constructor) {
|
|
isConstructor = constructor;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return toString(null, false);
|
|
}
|
|
|
|
public String toString(Program program) {
|
|
return toString(program, false);
|
|
}
|
|
|
|
public String toString(Program program, boolean onlyTypes) {
|
|
StringBuilder res = new StringBuilder();
|
|
if(declaredInline) {
|
|
res.append("inline ");
|
|
}
|
|
if(declaredIntrinsic) {
|
|
res.append("__intrinsic ");
|
|
}
|
|
if(isDeclaredBanked()) {
|
|
res.append("__bank(").append(this.getBankArea()).append(", ").append(this.getBank()).append(") ");
|
|
}
|
|
if(!callingConvention.equals(CallingConvention.PHI_CALL)) {
|
|
res.append(getCallingConvention().getName()).append(" ");
|
|
}
|
|
if(interruptType != null) {
|
|
res.append("__interrupt(").append(interruptType).append(") ");
|
|
}
|
|
if(onlyTypes) {
|
|
res.append(getType().toCDecl());
|
|
} else {
|
|
res.append(getReturnType().toCDecl()).append(" ").append(getFullName()).append("(");
|
|
boolean first = true;
|
|
if(parameterNames != null) {
|
|
for(Variable parameter : getParameters()) {
|
|
if(!first) res.append(" , ");
|
|
first = false;
|
|
res.append(parameter.toCDecl());
|
|
}
|
|
}
|
|
if(isVariableLengthParameterList()) {
|
|
res.append(", ...");
|
|
}
|
|
res.append(")");
|
|
}
|
|
return res.toString();
|
|
}
|
|
|
|
public ProcedureRef getRef() {
|
|
return new ProcedureRef(this.getFullName());
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if(this == o) return true;
|
|
if(o == null || getClass() != o.getClass()) return false;
|
|
if(!super.equals(o)) return false;
|
|
Procedure procedure = (Procedure) o;
|
|
return variableLengthParameterList == procedure.variableLengthParameterList &&
|
|
declaredInline == procedure.declaredInline &&
|
|
Objects.equals(bankLocation, procedure.bankLocation) &&
|
|
declaredIntrinsic == procedure.declaredIntrinsic &&
|
|
isConstructor == procedure.isConstructor &&
|
|
Objects.equals(procedureType, procedure.procedureType) &&
|
|
Objects.equals(parameterNames, procedure.parameterNames) &&
|
|
Objects.equals(interruptType, procedure.interruptType) &&
|
|
Objects.equals(comments, procedure.comments) &&
|
|
Objects.equals(reservedZps, procedure.reservedZps) &&
|
|
Objects.equals(segmentCode, procedure.segmentCode) &&
|
|
Objects.equals(constructorRefs, procedure.constructorRefs) &&
|
|
callingConvention == procedure.callingConvention;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Objects.hash(super.hashCode(), procedureType, parameterNames, variableLengthParameterList, declaredInline, declaredIntrinsic, interruptType, comments, reservedZps, segmentCode, constructorRefs, isConstructor, callingConvention);
|
|
}
|
|
}
|