1
0
mirror of https://gitlab.com/camelot/kickc.git synced 2024-06-03 07:29:37 +00:00
kickc/src/main/java/dk/camelot64/kickc/fragment/AsmFragmentInstance.java
2023-04-23 10:09:42 +02:00

490 lines
20 KiB
Java

package dk.camelot64.kickc.fragment;
import dk.camelot64.cpufamily6502.CpuAddressingMode;
import dk.camelot64.cpufamily6502.CpuOpcode;
import dk.camelot64.kickc.NumberParser;
import dk.camelot64.kickc.asm.*;
import dk.camelot64.kickc.model.*;
import dk.camelot64.kickc.model.InternalError;
import dk.camelot64.kickc.model.symbols.Label;
import dk.camelot64.kickc.model.symbols.Variable;
import dk.camelot64.kickc.model.types.SymbolType;
import dk.camelot64.kickc.model.values.*;
import dk.camelot64.kickc.parser.KickCParser;
import dk.camelot64.kickc.parser.KickCParserBaseVisitor;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** ASM Code Fragment with register/variable bindings that can be used for generating ASM code for a specific statement . */
public class AsmFragmentInstance {
/** The symbol table. */
private final Program program;
/** The name of the fragment used in error messages. */
private final String name;
/** The fragment template for the ASM code. */
private final AsmFragmentTemplate fragmentTemplate;
/** Binding of named values in the fragment to values (constants, variables, ...) . */
private final Map<String, Value> bindings;
/** The scope containing the fragment. Used when referencing symbols defined in other scopes. */
private final ScopeRef codeScopeRef;
public AsmFragmentInstance(
Program program,
String name,
ScopeRef codeScopeRef,
AsmFragmentTemplate fragmentTemplate,
Map<String, Value> bindings) {
this.program = program;
this.name = name;
this.fragmentTemplate = fragmentTemplate;
this.bindings = bindings;
this.codeScopeRef = codeScopeRef;
}
public Value getBinding(String name) {
return bindings.get(name);
}
/**
* Get the value to replace a bound name with from the fragment signature
*
* @param name The name of the bound value in the fragment
* @return The bound value to use in the generated ASM code
*/
public AsmParameter getBoundValue(String name) {
Value boundValue = null;
if(name.length() == 2) {
// Short name!
for(String boundName : bindings.keySet()) {
if(boundName.substring(boundName.length() - 2).equals(name)) {
boundValue = getBinding(boundName);
break;
}
}
} else {
// Long name
boundValue = getBinding(name);
}
if(boundValue == null) {
throw new RuntimeException("Binding '" + name + "' not found in fragment " + this.name);
}
if(boundValue instanceof Variable && ((Variable) boundValue).isVariable()) {
Variable boundVar = (Variable) boundValue;
Registers.Register register = boundVar.getAllocation();
if(register instanceof Registers.RegisterZpMem) {
return new AsmParameter(AsmFormat.getAsmSymbolName(program, boundVar, codeScopeRef), true);
} else if(register instanceof Registers.RegisterMainMem) {
return new AsmParameter(AsmFormat.getAsmSymbolName(program, boundVar, codeScopeRef), false);
} else {
throw new RuntimeException("Register Type not implemented " + register);
}
} else if(boundValue instanceof Variable && ((Variable) boundValue).isKindConstant()) {
Variable constantVar = (Variable) boundValue;
String constantValueAsm = AsmFormat.getAsmConstant(program, constantVar.getConstantRef(), 99, codeScopeRef);
boolean constantValueZp = SymbolType.BYTE.equals(constantVar.getType());
if(!constantValueZp) {
constantValueZp = isConstantValueZp(constantVar.getInitValue());
}
return new AsmParameter(constantValueAsm, constantValueZp);
} else if(boundValue instanceof ConstantValue) {
ConstantValue boundConst = (ConstantValue) boundValue;
String constantValueAsm = AsmFormat.getAsmConstant(program, boundConst, 99, codeScopeRef);
boolean constantValueZp = isConstantValueZp(boundConst);
return new AsmParameter(constantValueAsm, constantValueZp);
} else if(boundValue instanceof Label) {
String param = AsmFormat.asmFix(((Label) boundValue).getLocalName());
return new AsmParameter(param, false);
} else if(boundValue instanceof LabelRef) {
String param = AsmFormat.asmFix(((LabelRef) boundValue).getLocalName());
return new AsmParameter(param, false);
} else if(boundValue instanceof ProcedureRef) {
String param = AsmFormat.asmFix(((ProcedureRef) boundValue).getFullName());
return new AsmParameter(param, false);
} else {
throw new InternalError("Bound Value Type not implemented " + boundValue);
}
}
/**
* Determine whether a constant value representing an address in memory is located on zeropage.
*
* @param boundConst The constant value
* @return true if the address represented by the constant is 0<=val<=255
*/
private boolean isConstantValueZp(ConstantValue boundConst) {
SymbolType boundConstType = boundConst.getType(program.getScope());
if(SymbolType.BYTE.equals(boundConstType))
return true;
try {
ConstantLiteral literal = boundConst.calculateLiteral(program.getScope());
if(literal instanceof ConstantInteger) {
Long integer = ((ConstantInteger) literal).getInteger();
return integer <= 255 && integer >= 0;
} else if (literal instanceof ConstantPointer) {
final Long location = ((ConstantPointer) literal).getLocation();
return location <= 255 && location >= 0;
}
} catch(ConstantNotLiteral e) {
// ignore
}
if(boundConst instanceof ConstantRef) {
Variable reffedConstant = program.getScope().getConstant((ConstantRef) boundConst);
return isConstantValueZp(reffedConstant.getInitValue());
}
if(boundConst instanceof ConstantCastValue) {
SymbolType toType = ((ConstantCastValue) boundConst).getToType();
if(SymbolType.BYTE.equals(toType) || SymbolType.SBYTE.equals(toType))
return true;
else
return isConstantValueZp(((ConstantCastValue) boundConst).getValue());
}
return false;
}
public String getFragmentName() {
return name;
}
/**
* Generate assembler code for the assembler fragment.
*
* @param asm The assembler sequence to generate into.
*/
public void generate(AsmProgram asm) {
AsmSequenceGenerator asmSequenceGenerator = new AsmSequenceGenerator(name, this, asm);
asmSequenceGenerator.generate(fragmentTemplate.getBodyAsm());
}
/**
* A parameter of an ASM instruction from a bound value.
*/
public static class AsmParameter {
private final String param;
private final boolean zp;
public AsmParameter(String param, boolean zp) {
this.param = param;
this.zp = zp;
}
public String getParam() {
return param;
}
public boolean isZp() {
return zp;
}
}
private static class AsmSequenceGenerator extends KickCParserBaseVisitor {
private final String name;
private final AsmProgram asmProgram;
private final AsmFragmentInstance fragmentInstance;
public AsmSequenceGenerator(String name, AsmFragmentInstance fragmentInstance, AsmProgram asmProgram) {
this.name = name;
this.fragmentInstance = fragmentInstance;
this.asmProgram = asmProgram;
}
public AsmProgram getAsmProgram() {
return asmProgram;
}
public void generate(KickCParser.AsmLinesContext context) {
this.visit(context);
}
public void handleTags(AsmLine asmLine, List<TerminalNode> tags) {
AsmLine line = addTags(asmLine, tags);
if(line.getTags().has("outside_flow")) {
// Outside the normal ASM flow - stash them in the program for later
asmProgram.stashLine(line);
} else {
asmProgram.addLine(line);
}
}
private static AsmLine addTags(AsmLine asmLine, List<TerminalNode> asmTags) {
if(asmTags != null)
for(TerminalNode asmTag : asmTags) {
final String tagName = asmTag.getText().substring(1);
asmLine.getTags().add(tagName);
}
return asmLine;
}
@Override
public Object visitAsmLabelName(KickCParser.AsmLabelNameContext ctx) {
AsmLabel label = new AsmLabel(ctx.ASM_NAME().getText());
handleTags(label, ctx.ASM_TAG());
return null;
}
@Override
public Object visitAsmLabelMulti(KickCParser.AsmLabelMultiContext ctx) {
AsmLabel label = new AsmLabel(ctx.ASM_MULTI_NAME().getText());
handleTags(label, ctx.ASM_TAG());
return null;
}
@Override
public Object visitAsmLabelReplace(KickCParser.AsmLabelReplaceContext ctx) {
String replaceName = ctx.ASM_NAME().getText();
AsmParameter boundValue = fragmentInstance.getBoundValue(replaceName);
handleTags(new AsmLabel(boundValue.getParam()), ctx.ASM_TAG());
return null;
}
@Override
public Object visitAsmBytes(KickCParser.AsmBytesContext ctx) {
List<KickCParser.AsmExprContext> asmExpr = ctx.asmExpr();
ArrayList<String> values = new ArrayList<>();
for(int i = 0; i < asmExpr.size(); i++) {
if(asmExpr.get(i) != null) {
AsmParameter par = (AsmParameter)this.visit(asmExpr.get(i));
values.add(par.getParam());
}
}
AsmDataNumeric data = new AsmDataNumeric(null, AsmDataNumeric.Type.BYTE, values);
handleTags(data, ctx.ASM_TAG());
return null;
}
@Override
public Object visitAsmInstruction(KickCParser.AsmInstructionContext ctx) {
KickCParser.AsmParamModeContext paramModeCtx = ctx.asmParamMode();
AsmInstruction instruction;
if(paramModeCtx == null) {
instruction = createAsmInstruction(ctx, null, null, null, CpuAddressingMode.NON);
} else {
instruction = (AsmInstruction) this.visit(paramModeCtx);
}
if(instruction != null) {
handleTags(instruction, ctx.ASM_TAG());
} else {
throw new RuntimeException("Error parsing ASM fragment line " + name + ".asm\n - Line: " + ctx.getText());
}
return null;
}
@Override
public Object visitAsmModeAbs(KickCParser.AsmModeAbsContext ctx) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.ABS);
}
@Override
public Object visitAsmModeImm(KickCParser.AsmModeImmContext ctx) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.IMM);
}
@Override
public Object visitAsmModeImmAndAbs(KickCParser.AsmModeImmAndAbsContext ctx) {
return createAsmInstruction(ctx, ctx.asmExpr(0), ctx.asmExpr(1), null, CpuAddressingMode.IAB);
}
@Override
public Object visitAsmModeImmAndAbsX(KickCParser.AsmModeImmAndAbsXContext ctx) {
return createAsmInstruction(ctx, ctx.asmExpr(0), ctx.asmExpr(1), null, CpuAddressingMode.IABX);
}
@Override
public Object visitAsmModeAbs2(KickCParser.AsmModeAbs2Context ctx) {
final KickCParser.AsmExprContext indexCtx = ctx.asmExpr(1);
if(indexCtx instanceof KickCParser.AsmExprLabelContext) {
final String xy = ((KickCParser.AsmExprLabelContext) indexCtx).ASM_NAME().getText();
if(xy.equals("x")) {
return createAsmInstruction(ctx, ctx.asmExpr(0), null, null, CpuAddressingMode.ABX);
} else if(xy.equals("y")) {
return createAsmInstruction(ctx, ctx.asmExpr(0), null, null, CpuAddressingMode.ABY);
}
}
// Test Relative Addressing Mode (2 parameters)
return createAsmInstruction(ctx, ctx.asmExpr(0), ctx.asmExpr(1), null, CpuAddressingMode.REZ);
}
@Override
public Object visitAsmModeAbs3(KickCParser.AsmModeAbs3Context ctx) {
// Abs*3 Addressing Mode (3 parameters)
return createAsmInstruction(ctx, ctx.asmExpr(0), ctx.asmExpr(1), ctx.asmExpr(2), CpuAddressingMode.ABS3);
}
@Override
public Object visitAsmModeIndIdxXY(KickCParser.AsmModeIndIdxXYContext ctx) {
String xy = ctx.ASM_NAME().getText();
if(xy.equals("y")) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.IZY);
} else if(xy.equals("z")) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.IZZ);
} else {
throw new RuntimeException("Unknown addressing mode " + ctx.getText());
}
}
@Override
public Object visitAsmModeIndLongIdxXY(KickCParser.AsmModeIndLongIdxXYContext ctx) {
String xy = ctx.ASM_NAME().getText();
if(xy.equals("z")) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.LIZ);
} else {
throw new RuntimeException("Unknown addressing mode " + ctx.getText());
}
}
@Override
public Object visitAsmModeIdxIndXY(KickCParser.AsmModeIdxIndXYContext ctx) {
String xy = ctx.ASM_NAME().getText();
if(xy.equals("x")) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.IAX);
} else {
throw new RuntimeException("Unknown addressing mode " + ctx.getText());
}
}
@Override
public Object visitAsmModeSPIndIdx(KickCParser.AsmModeSPIndIdxContext ctx) {
String sp = ctx.ASM_NAME(0).getText();
String y = ctx.ASM_NAME(1).getText();
if(sp.equals("sp") && y.equals("y")) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.ISY);
} else {
throw new RuntimeException("Unknown addressing mode " + ctx.getText());
}
}
@Override
public Object visitAsmModeInd(KickCParser.AsmModeIndContext ctx) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.IND);
}
@Override
public Object visitAsmModeIndLong(KickCParser.AsmModeIndLongContext ctx) {
return createAsmInstruction(ctx, ctx.asmExpr(), null, null, CpuAddressingMode.LIN);
}
private AsmInstruction createAsmInstruction(
KickCParser.AsmParamModeContext paramModeCtx,
KickCParser.AsmExprContext operand1Ctx,
KickCParser.AsmExprContext operand2Ctx,
KickCParser.AsmExprContext operand3Ctx,
CpuAddressingMode addressingMode) {
return createAsmInstruction((KickCParser.AsmInstructionContext) paramModeCtx.getParent(), operand1Ctx, operand2Ctx, operand3Ctx, addressingMode);
}
private AsmInstruction createAsmInstruction(
KickCParser.AsmInstructionContext instructionCtx,
KickCParser.AsmExprContext operand1Ctx,
KickCParser.AsmExprContext operand2Ctx,
KickCParser.AsmExprContext operand3Ctx,
CpuAddressingMode addressingMode) {
String mnemonic = instructionCtx.ASM_MNEMONIC().getSymbol().getText();
AsmParameter param1 = operand1Ctx == null ? null : (AsmParameter) this.visit(operand1Ctx);
AsmParameter param2 = operand2Ctx == null ? null : (AsmParameter) this.visit(operand2Ctx);
AsmParameter param3 = operand3Ctx == null ? null : (AsmParameter) this.visit(operand3Ctx);
// Convert to ZP-addressing mode if possible
boolean isZp = param1 != null && param1.isZp();
if(CpuAddressingMode.IAB.equals(addressingMode) || CpuAddressingMode.IABX.equals(addressingMode)) {
// For the HuC6280 CPU TST #imm,abs addressing mode it is param2 that can converted the instruction to ZP
isZp = param2 != null && param2.isZp();
}
CpuOpcode cpuOpcode = this.getAsmProgram().getTargetCpu().getCpu65xx().getOpcode(mnemonic, addressingMode, isZp);
if(!isZp && cpuOpcode==null) {
// Fallback to ZP-addressing
cpuOpcode = this.getAsmProgram().getTargetCpu().getCpu65xx().getOpcode(mnemonic, addressingMode, true);
}
String operand1 = param1 == null ? null : param1.getParam();
String operand2 = param2 == null ? null : param2.getParam();
String operand3 = param3 == null ? null : param3.getParam();
if(cpuOpcode == null) {
throw new CompileError("Error in " + name + ".asm line " + instructionCtx.getStart().getLine() + " - Instruction type not supported " + addressingMode.getAsm(mnemonic, operand1, operand2, operand3) + " by CPU " + this.fragmentInstance.fragmentTemplate.getTargetCpu().getName());
}
return new AsmInstruction(cpuOpcode, operand1, operand2, operand3);
}
@Override
public AsmParameter visitAsmExprBinary(KickCParser.AsmExprBinaryContext ctx) {
AsmParameter left = (AsmParameter) this.visit(ctx.asmExpr(0));
AsmParameter right = (AsmParameter) this.visit(ctx.asmExpr(1));
StringBuilder param = new StringBuilder();
param.append(left.getParam());
if(ctx.asmExpr(0) instanceof KickCParser.AsmExprLabelRelContext) {
// Add an extra space if we are doing a binary expression with a relative label as the left part
param.append(" ");
}
param.append(ctx.getChild(1).getText());
param.append(right.getParam());
boolean zp = left.isZp() && right.isZp();
return new AsmParameter(param.toString(), zp);
}
@Override
public AsmParameter visitAsmExprUnary(KickCParser.AsmExprUnaryContext ctx) {
AsmParameter sub = (AsmParameter) this.visit(ctx.asmExpr());
String operator = ctx.getChild(0).getText();
String param = operator + sub.getParam();
boolean isZp = sub.isZp();
if(operator.equals("<") || operator.equals(">")) {
isZp = true;
}
return new AsmParameter(param, isZp);
}
@Override
public Object visitAsmExprPar(KickCParser.AsmExprParContext ctx) {
AsmParameter sub = (AsmParameter) this.visit(ctx.asmExpr());
String param = "[" + sub.getParam() + "]";
return new AsmParameter(param, sub.isZp());
}
@Override
public AsmParameter visitAsmExprInt(KickCParser.AsmExprIntContext ctx) {
Number number = NumberParser.parseLiteral(ctx.ASM_NUMBER().getText());
boolean isZp = SymbolType.BYTE.contains(number.longValue()) || SymbolType.SBYTE.contains(number.longValue());
String param = AsmFormat.getAsmNumber(number);
return new AsmParameter(param, isZp);
}
@Override
public Object visitAsmExprChar(KickCParser.AsmExprCharContext ctx) {
return new AsmParameter(ctx.getText(), true);
}
@Override
public AsmParameter visitAsmExprLabel(KickCParser.AsmExprLabelContext ctx) {
String param = ctx.ASM_NAME().getSymbol().getText();
return new AsmParameter(param, false);
}
@Override
public Object visitAsmExprLabelRel(KickCParser.AsmExprLabelRelContext ctx) {
String param = ctx.ASM_MULTI_REL().getSymbol().getText();
return new AsmParameter(param, false);
}
@Override
public AsmParameter visitAsmExprReplace(KickCParser.AsmExprReplaceContext ctx) {
String replaceName = ctx.ASM_NAME().getSymbol().getText();
return fragmentInstance.getBoundValue(replaceName);
}
}
}