package org.reasm.m68k.assembly.internal; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import org.reasm.*; import org.reasm.commons.source.LogicalLine; import org.reasm.commons.source.LogicalLineReader; import org.reasm.commons.source.SourceLocationUtils; import org.reasm.expressions.EvaluationContext; import org.reasm.expressions.SymbolLookup; import org.reasm.m68k.ConfigurationOptions; import org.reasm.m68k.M68KArchitecture; import org.reasm.m68k.expressions.internal.Tokenizer; import org.reasm.m68k.messages.InvalidSizeAttributeErrorMessage; import org.reasm.m68k.messages.SizeAttributeNotAllowedErrorMessage; import org.reasm.messages.WrongNumberOfOperandsErrorMessage; import org.reasm.source.SourceLocation; import org.reasm.source.SourceNode; import ca.fragag.Consumer; import com.google.common.collect.ImmutableList; /** * Stores all the contextual data for the M68000 family for a specific assembly. * * @author Francis Gagné */ final class M68KAssemblyContext extends M68KBasicAssemblyContext implements Consumer<AssemblyMessage>, CustomAssemblyData, SymbolResolutionFallback { /** * The key for {@link AssemblyBuilder#getCustomAssemblyData(Object)} and * {@link AssemblyBuilder#setCustomAssemblyData(Object, CustomAssemblyData)}. */ @Nonnull static final Object KEY = new Object(); /** The symbol context for register aliases. Register aliases can be used in place of a standard register name. */ @Nonnull static final SymbolContext<GeneralPurposeRegister> REGISTER_ALIAS = new SymbolContext<>(GeneralPurposeRegister.class); /** * The symbol context for register list aliases. Register list aliases can be used where a register list is expected (e.g. in * the <code>MOVEM</code> instruction). */ @Nonnull static final SymbolContext<RegisterList> REGISTER_LIST_ALIAS = new SymbolContext<>(RegisterList.class); /** The symbol context for mnemonics. */ @Nonnull static final SymbolContext<Mnemonic> MNEMONIC = new SymbolContext<>(Mnemonic.class); @Nonnull private static final ImmutableList<SymbolContext<?>> REGISTER_ALIAS_LOOKUP_CONTEXTS = ImmutableList.of(REGISTER_ALIAS, SymbolContext.VALUE); @Nonnull private static final ImmutableList<SymbolContext<?>> REGISTER_LIST_ALIAS_LOOKUP_CONTEXTS = ImmutableList.of( REGISTER_LIST_ALIAS, REGISTER_ALIAS, SymbolContext.VALUE); @Nonnull static M68KAssemblyContext getAssemblyContext(@Nonnull AssemblyBuilder builder) { M68KAssemblyContext context = (M68KAssemblyContext) builder.getCustomAssemblyData(KEY); if (context == null) { // If it doesn't exist yet, create it. context = new M68KAssemblyContext(builder); // Initialize it with the configuration options. final ConfigurationOptions configurationOptions = (ConfigurationOptions) builder.getAssembly().getConfiguration() .getCustomConfigurationOptions(ConfigurationOptions.KEY); if (configurationOptions != null) { context.automaticEven = configurationOptions.automaticEven(); context.optimizeCmpiToTst = configurationOptions.optimizeCmpiToTst(); context.optimizeMoveToMoveq = configurationOptions.optimizeMoveToMoveq(); context.optimizeToAddqSubq = configurationOptions.optimizeToAddqSubq(); context.optimizeUnsizedAbsoluteAddressingToPcRelative = configurationOptions .optimizeUnsizedAbsoluteAddressingToPcRelative(); context.optimizeUnsizedBranches = configurationOptions.optimizeUnsizedBranches(); context.optimizeZeroDisplacement = configurationOptions.optimizeZeroDisplacement(); } builder.setCustomAssemblyData(KEY, context); } // Initialize the context. context.initialize(builder.getStep()); context.encoding = builder.getAssembly().getCurrentEncoding(); return context; } @Nonnull final AssemblyBuilder builder; // Configuration options that can be changed during assembly boolean automaticEven; boolean optimizeCmpiToTst; boolean optimizeMoveToMoveq; boolean optimizeToAddqSubq; boolean optimizeUnsizedBranches; // Context of the current logical line being assembled // They are assigned in initialize(AssemblyStep) AssemblyStep step; SourceLocation sourceLocation; LogicalLine logicalLine; int numberOfLabels; int numberOfOperands; String mnemonic; String attribute; @CheckForNull private EvaluationContext evaluationContext; // Reusable objects @Nonnull final LogicalLineReader logicalLineReader = new LogicalLineReader(); @Nonnull final Tokenizer tokenizer = new Tokenizer(); @Nonnull final EffectiveAddress ea0 = new EffectiveAddress(); @Nonnull final EffectiveAddress ea1 = new EffectiveAddress(); @Nonnull final BranchLabelValueVisitor branchLabelValueVisitor = new BranchLabelValueVisitor(this); @Nonnull final CardinalValueVisitor cardinalValueVisitor = new CardinalValueVisitor(this); @Nonnull final DcFloatValueVisitor dcFloatValueVisitor = new DcFloatValueVisitor(this); @Nonnull final DcIntegerValueVisitor dcIntegerValueVisitor = new DcIntegerValueVisitor(this); @Nonnull final IntegerValueVisitor integerValueVisitor = new IntegerValueVisitor(this); @Nonnull final StringValueVisitor stringValueVisitor = new StringValueVisitor(this); // Persistent state @Nonnull final Map<AssemblyStepLocation, Object> blockStateMap = new HashMap<>(); @Nonnull final Map<AssemblyStepLocation, Macro> macrosByDefinitionLocation = new HashMap<>(); // - Special symbols @Nonnull final RsSymbol rs = new RsSymbol(); private M68KAssemblyContext(@Nonnull AssemblyBuilder builder) { this.builder = builder; } @Override public void accept(AssemblyMessage message) { this.builder.addTentativeMessage(message); } @Override public void completed() { } @Override public Symbol resolve(SymbolReference symbolReference) { // TODO: built-in symbols (functions, etc.) return null; } @Override public void startedNewPass() { this.rs.set(0, false); } void addInvalidSizeAttributeErrorMessage() { this.addMessage(new InvalidSizeAttributeErrorMessage(this.attribute)); } void addMessage(@Nonnull AssemblyMessage message) { this.builder.addMessage(message); } void addTentativeMessage(@Nonnull AssemblyMessage message) { this.builder.addTentativeMessage(message); } void addWrongNumberOfOperandsErrorMessage() { this.addMessage(new WrongNumberOfOperandsErrorMessage()); } void appendByte(byte by) throws IOException { this.builder.appendAssembledData(by); } void appendEffectiveAddress(@Nonnull EffectiveAddress ea) throws IOException { this.appendEffectiveAddress(ea, 0); } void appendEffectiveAddress(@Nonnull EffectiveAddress ea, int firstWord) throws IOException { for (int i = firstWord; i < ea.numberOfWords; i++) { this.appendWord(ea.getWord(i)); } } void appendLong(int longWord) throws IOException { this.builder.appendAssembledData((byte) (longWord >>> 24)); this.builder.appendAssembledData((byte) (longWord >>> 16)); this.builder.appendAssembledData((byte) (longWord >>> 8)); this.builder.appendAssembledData((byte) (longWord >>> 0)); } void appendQuad(long quadWord) throws IOException { this.builder.appendAssembledData((byte) (quadWord >>> 56)); this.builder.appendAssembledData((byte) (quadWord >>> 48)); this.builder.appendAssembledData((byte) (quadWord >>> 40)); this.builder.appendAssembledData((byte) (quadWord >>> 32)); this.builder.appendAssembledData((byte) (quadWord >>> 24)); this.builder.appendAssembledData((byte) (quadWord >>> 16)); this.builder.appendAssembledData((byte) (quadWord >>> 8)); this.builder.appendAssembledData((byte) (quadWord >>> 0)); } void appendWord(short word) throws IOException { this.builder.appendAssembledData((byte) (word >>> 8)); this.builder.appendAssembledData((byte) word); } void automaticEven() throws IOException { if (this.automaticEven && (this.programCounter & 1) != 0) { this.builder.appendAssembledData((byte) 0); this.programCounter++; } } @Nonnull SymbolLookup createSymbolLookup() { return new M68KSymbolLookup(this, this.builder.getAssembly().getCurrentSymbolLookupContext()); } /** * Defines all the labels on the logical line of the current assembly step except the last one with the current program counter * as their value. */ void defineExtraLabels() { final int numberOfLabels = this.numberOfLabels; for (int i = 0; i < numberOfLabels - 1; i++) { this.defineLabel(i); } } /** * Defines all the labels on the logical line of the current assembly step with the current program counter as their value. */ void defineLabels() { final int numberOfLabels = this.numberOfLabels; for (int i = 0; i < numberOfLabels; i++) { this.defineLabel(i); } } <TValue> void defineSymbol(@Nonnull SymbolContext<TValue> symbolContext, @Nonnull String symbolName, @Nonnull SymbolType symbolType, @CheckForNull TValue value) { final boolean isLocalName = M68KArchitecture.isLocalName(symbolName); this.builder.defineSymbol(symbolContext, symbolName, isLocalName, symbolType, value); } <TValue> void defineSymbols(@Nonnull SymbolContext<TValue> symbolContext, @Nonnull SymbolType symbolType, @CheckForNull TValue value) { for (int i = 0; i < this.numberOfLabels; i++) { this.defineSymbol(symbolContext, this.getLabelText(i), symbolType, value); } } @Nonnull DcValueVisitor getDcValueVisitor(@Nonnull InstructionSize size) { switch (size) { case DEFAULT: case BYTE: case WORD: case LONG: case QUAD: default: return this.dcIntegerValueVisitor; case SINGLE: case DOUBLE: case EXTENDED: case PACKED: return this.dcFloatValueVisitor; } } void getEffectiveAddress(@Nonnull String operand, @Nonnull Set<AddressingMode> validAddressingModes, @Nonnull InstructionSize size, @Nonnull EffectiveAddress ea) { this.getEffectiveAddress(operand, validAddressingModes, size, 2, ea); } void getEffectiveAddress(@Nonnull String operand, @Nonnull Set<AddressingMode> validAddressingModes, @Nonnull InstructionSize size, int offsetToExtensionWords, @Nonnull EffectiveAddress ea) { this.tokenizer.setCharSequence(operand); EffectiveAddress.getEffectiveAddress(this.tokenizer, this.createSymbolLookup(), validAddressingModes, false, size, offsetToExtensionWords, this.getEvaluationContext(), this, this, ea); } @Nonnull EvaluationContext getEvaluationContext() { if (this.evaluationContext == null) { this.evaluationContext = new EvaluationContext(this.builder.getAssembly(), this.programCounter, this); } return this.evaluationContext; } @Nonnull String getLabelText(int index) { this.logicalLineReader.setRange(this.sourceLocation, this.logicalLine.getLabelBounds(index)); return this.logicalLineReader.readToString(); } @CheckForNull Symbol getMnemonicSymbolByName(@Nonnull String name) { return this.getSymbolByContextAndName(MNEMONIC, name, Mnemonics.SYMBOL_RESOLUTION_FALLBACK); } @Nonnull String getMnemonicText() { final SubstringBounds mnemonicBounds = this.logicalLine.getMnemonicBounds(); assert mnemonicBounds != null; this.logicalLineReader.setRange(this.sourceLocation, mnemonicBounds); return this.logicalLineReader.readToString(); } @Nonnull String getOperandText(int index) { this.prepareOperandReader(index); return this.logicalLineReader.readToString(); } @CheckForNull Object getParentBlock() { return this.blockStateMap.get(this.step.getLocation().getParent()); } @CheckForNull SourceNode getParentNode() { final AssemblyStepLocation parent = this.step.getLocation().getParent(); assert parent != null; return parent.getSourceLocation().getSourceNode(); } @Override GeneralPurposeRegister getRegisterAliasByName(String name) { final Symbol symbol = this.builder.resolveSymbolReference(REGISTER_ALIAS_LOOKUP_CONTEXTS, name, M68KArchitecture.isLocalName(name), null, null).getSymbol(); if (symbol != null && symbol.getValue() instanceof GeneralPurposeRegister) { return (GeneralPurposeRegister) symbol.getValue(); } return null; } @CheckForNull Symbol getRegisterAliasOrRegisterListAliasSymbolByName(@Nonnull String name) { final Symbol symbol = this.builder.resolveSymbolReference(REGISTER_LIST_ALIAS_LOOKUP_CONTEXTS, name, M68KArchitecture.isLocalName(name), null, null).getSymbol(); if (symbol != null && ((UserSymbol) symbol).getContext() != SymbolContext.VALUE) { return symbol; } return null; } @Nonnull InstructionSize parseInstructionSize() { if (this.attribute == null) { return InstructionSize.DEFAULT; } if (this.attribute.length() != 1) { return InstructionSize.INVALID; } switch (this.attribute.charAt(0)) { case 'B': case 'b': return InstructionSize.BYTE; case 'W': case 'w': return InstructionSize.WORD; case 'L': case 'l': return InstructionSize.LONG; case 'Q': case 'q': return InstructionSize.QUAD; case 'S': case 's': return InstructionSize.SINGLE; case 'D': case 'd': return InstructionSize.DOUBLE; case 'X': case 'x': return InstructionSize.EXTENDED; case 'P': case 'p': return InstructionSize.PACKED; default: return InstructionSize.INVALID; } } @Nonnull InstructionSize parseIntegerInstructionSize() { if (this.attribute == null) { return InstructionSize.DEFAULT; } if (this.attribute.length() != 1) { return InstructionSize.INVALID; } switch (this.attribute.charAt(0)) { case 'B': case 'b': return InstructionSize.BYTE; case 'W': case 'w': return InstructionSize.WORD; case 'L': case 'l': return InstructionSize.LONG; default: return InstructionSize.INVALID; } } void prepareOperandReader(int index) { this.logicalLineReader.setRange(this.sourceLocation, this.logicalLine.getOperandBounds(index)); } boolean requireNumberOfOperands(int requiredNumberOfOperands) { if (this.numberOfOperands != requiredNumberOfOperands) { this.addWrongNumberOfOperandsErrorMessage(); } return this.numberOfOperands >= requiredNumberOfOperands; } void setMnemonic() { final String mnemonic = this.getMnemonicText(); final int indexOfPeriod = mnemonic.indexOf('.'); if (indexOfPeriod == -1) { this.mnemonic = mnemonic; this.attribute = null; } else { this.mnemonic = mnemonic.substring(0, indexOfPeriod); this.attribute = mnemonic.substring(indexOfPeriod + 1); } } void sizeNotAllowed() { if (this.attribute != null) { this.addMessage(new SizeAttributeNotAllowedErrorMessage()); } } void validateForByteAccess(@Nonnull EffectiveAddress ea) { if (ea.isAddressRegisterDirect()) { this.addInvalidSizeAttributeErrorMessage(); } } /** * Defines a label on the logical line of the current assembly step with the current program counter as its value. * * @param index * the index of the label to define */ private void defineLabel(int index) { final String label = this.getLabelText(index); this.defineSymbol(SymbolContext.VALUE, label, SymbolType.CONSTANT, new UnsignedIntValue(this.programCounter)); } @CheckForNull private <TValue> Symbol getSymbolByContextAndName(@Nonnull SymbolContext<TValue> context, @Nonnull String name, @Nonnull SymbolResolutionFallback symbolResolutionFallback) { return this.builder.resolveSymbolReference(context, name, M68KArchitecture.isLocalName(name), null, symbolResolutionFallback).getSymbol(); } private void initialize(@Nonnull AssemblyStep step) { this.step = step; this.programCounter = step.getProgramCounter(); this.sourceLocation = step.getLocation().getSourceLocation(); this.instructionSet = ((M68KArchitecture) this.sourceLocation.getArchitecture()).getInstructionSet(); final LogicalLine logicalLine = SourceLocationUtils.getLogicalLine(this.sourceLocation); if (logicalLine != null) { this.logicalLine = logicalLine; this.numberOfLabels = this.logicalLine.getNumberOfLabels(); this.numberOfOperands = this.logicalLine.getNumberOfOperands(); } else { this.logicalLine = null; this.numberOfLabels = 0; this.numberOfOperands = 0; } this.mnemonic = null; this.attribute = null; // Set the evaluation context to null. It will be created on demand in getEvaluationContext(). this.evaluationContext = null; } }