diff --git a/docs/user/en/src/source-files-format.rst b/docs/user/en/src/source-files-format.rst index cc1a2aafa..4074ecb6c 100644 --- a/docs/user/en/src/source-files-format.rst +++ b/docs/user/en/src/source-files-format.rst @@ -147,7 +147,8 @@ Instructions can take three types of parameters: * *Immediate values* an immediate value can be a number or a label; the number can be specified in base 10 or in base 16: base 10 numbers are simply inserted by writing the number, while base 16 number are inserted - by putting before the number the prefix "0x" + by putting before the number the prefix "0x". Immediate values can be preceded + by the # character. * *Address* an address is composed by an immediate value followed by a register name enclosed in brackets. The value of the register will be used as base, the value of the immediate will be the offset. @@ -155,6 +156,10 @@ Instructions can take three types of parameters: The size of immediate values is limited by the number of bits that are available in the bit encoding of the instruction. +When 16-bit immediates can be used, for example in ALU I-Type instructions, +it's also possible to use as an immediate value a memory label. The assembler +will put as immediate value the memory address the label points to. + You can use standard MIPS assembly aliases to address the first 32 registers, appending the alias to one of the standard register prefixes like "r", "\$" and "R". See the next table. diff --git a/docs/user/it/src/source-files-format.rst b/docs/user/it/src/source-files-format.rst index 8059be65e..9065ea188 100644 --- a/docs/user/it/src/source-files-format.rst +++ b/docs/user/it/src/source-files-format.rst @@ -156,7 +156,8 @@ Le istruzioni possono accettare tre tipi di parametri: un'etichetta; il numero può essere specificato in base 10 o in base 16. I numeri in base 10 sono inseriti semplicemente scrivendo il numero utilizzando l'usuale notazione decimale; i numeri in base 16 si inseriscono - aggiungendo all'inizio del numero il prefisso "0x"; + aggiungendo all'inizio del numero il prefisso "0x". I valori immediati + possono essere preceduti dal carattere #; * *Indirizzi* un indirizzo è composto da un valore immediato seguito dal nome di un registro tra parentesi. Il valore del registro sarà usato come base, quello dell'immediato come offset. @@ -164,6 +165,11 @@ Le istruzioni possono accettare tre tipi di parametri: La dimensione dei valori immediati è limitata al numero di bit disponibili nella codifica associata all'istruzione. +Nel caso di immediati a 16 bit, come ad esempio i valori immediati delle +istruzioni ALU I-Type, è possibile utilizzare come valore immediato un'etichetta +di memoria. L'assembler usera come valore immediato l'indirizzo della locazione +di memoria a cui l'etichetta punta. + è possibile utilizzare gli alias standard MIPS per i primi 32 registri, mettendo in coda ai prefissi standard per i registri ("r", "$", "R") uno degli alias indicati nella seguente tabella. diff --git a/src/main/java/org/edumips64/core/Converter.java b/src/main/java/org/edumips64/core/Converter.java index 6ece557d5..4900ea2dd 100644 --- a/src/main/java/org/edumips64/core/Converter.java +++ b/src/main/java/org/edumips64/core/Converter.java @@ -672,30 +672,37 @@ public static boolean isHexNumber(String num) { return true; } -/** Check if is a valid string for an immediate value (rough check on number of - * digits, the caller is responsible for checking the actual value). - * - * TODO: this is not very clean, this function should be removed and the caller - * should just convert to a number and check the value. - * @param imm the string to validate - * @return false if imm isn't a valid immediate, else true + /** + * Parses an immediate value without any overflow/underflow check. + * + * The immediate value may be preceded by the # character (which is ignored). + * The immediate value may be encoded in base 10 or in base 16. In the latter + * case, it must be preceded by the '0x' or '0X' prefix. + * + * If the # character is used in a base-16 immediate, it must precede the 0x prefix. + * + * @param immediate a string representing an immediate value. + * @throws NumberFormatException if the number is not well-formatted. + * @return the parsed integer value. */ - public static boolean isImmediate(String imm) { - if (imm.length() == 0) { - return false; + public static long parseImmediate(String immediate) { + if (immediate.length() == 0) { + throw new NumberFormatException("Invalid immediate: empty string."); } - - if (imm.charAt(0) == '#') { - imm = imm.substring(1); + + // Skip the initial #, if present. + if (immediate.charAt(0) == '#') { + immediate = immediate.substring(1); } - if (isInteger(imm)) { - return true; - } else if (isHexNumber(imm) && imm.length() <= 6) { - return true; + // Check if it's a hexadecimal. + int base = 10; + if (immediate.length() >= 3 && immediate.substring(0, 2).compareToIgnoreCase("0x") == 0) { + immediate = immediate.substring(2); + base = 16; } - return false; + return Long.parseLong(immediate, base); } } diff --git a/src/main/java/org/edumips64/core/is/ALU_IType.java b/src/main/java/org/edumips64/core/is/ALU_IType.java index 0cd169fce..0dfed2474 100644 --- a/src/main/java/org/edumips64/core/is/ALU_IType.java +++ b/src/main/java/org/edumips64/core/is/ALU_IType.java @@ -44,7 +44,7 @@ public abstract class ALU_IType extends ComputationalInstructions { protected final static int RT_FIELD_LENGTH = 5; protected final static int RS_FIELD_LENGTH = 5; protected final static int IMM_FIELD_LENGTH = 16; - protected final static int IMM_FIELD_MAX = (int) Math.pow(2, IMM_FIELD_LENGTH - 1) - 1; + protected final static int IMM_FIELD_MAX = 32767; // 2^15-1 protected String OPCODE_VALUE = ""; // Needs to be mutable because LUI's syntax is %R,%I, and IMM_FIELD will be 1 in that case. diff --git a/src/main/java/org/edumips64/core/is/Instruction.java b/src/main/java/org/edumips64/core/is/Instruction.java index c54022256..463778e2e 100644 --- a/src/main/java/org/edumips64/core/is/Instruction.java +++ b/src/main/java/org/edumips64/core/is/Instruction.java @@ -117,9 +117,9 @@ public BitSet32 getRepr() { * Valid type placeholders: * %R General Purpose Register * %F Floating Point Register - * %I Immediate value (6 bits?) - * %U Unsigned Immediate (5 bits?) - * %C Unsigned Immediate (3 bits) + * %I Signed Immediate Value (16 bits) [also allows a memory label as a value] + * %U Unsigned Immediate Value (5 bits) + * %C Unsigned Immediate Value (3 bits) * %L Memory Label * %E Program Label used for Jump Instructions * %B Program Label used for Branch Instructions diff --git a/src/main/java/org/edumips64/core/parser/Parser.java b/src/main/java/org/edumips64/core/parser/Parser.java index c7d11aa00..39887b1a4 100644 --- a/src/main/java/org/edumips64/core/parser/Parser.java +++ b/src/main/java/org/edumips64/core/parser/Parser.java @@ -610,304 +610,68 @@ public void doParsing(String code) throws ParserMultiException { continue; } - // %I: 16-bit immediate. + // %I: 16-bit signed immediate. + // Also allows a memory label as a parameter -- the address will be used as immediate value. } else if (type == 'I') { - int imm; + long immediateValue = 0; + String errorMessage = ""; - if (Converter.isImmediate(paramValue)) { - if (param.charAt(paramStart) == '#') { - paramStart++; - paramValue = paramValue.substring(1); - } - - if (Converter.isInteger(paramValue)) { - try { - imm = Integer.parseInt(paramValue); - - if (imm < -32768 || imm > 32767) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - } else if (Converter.isHexNumber(paramValue)) { - try { - try { - imm = (int) Long.parseLong(Converter.hexToShort(paramValue)); - logger.info("imm = " + imm); - - if (imm < -32768 || imm > 32767) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - } catch (IrregularStringOfHexException ex) { - //non ci dovrebbe mai arrivare - } - } - - } else { + try { + immediateValue = Converter.parseImmediate(paramValue); + } catch (NumberFormatException e) { + // Invalid number, try to parse it as a memory label. + MemoryElement tmpMem; try { - int cc; - MemoryElement tmpMem; - cc = param.indexOf("+", paramStart); - - if (cc != -1) { - tmpMem = symTab.getCell(param.substring(paramStart, cc).trim()); - - if (Converter.isInteger(param.substring(cc + 1, paramEnd))) { - try { - imm = Integer.parseInt(paramValue); - - if (imm < -32768 || imm > 32767) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(tmpMem.getAddress() + imm); - paramStart = paramEnd + 1; - } else if (Converter.isHexNumber(param.substring(cc + 1, paramEnd))) { - try { - try { - imm = (int) Long.parseLong(Converter.hexToLong(paramValue)); - - if (imm < -32768 || imm > 32767) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(tmpMem.getAddress() + imm); - paramStart = paramEnd + 1; - } catch (IrregularStringOfHexException ex) { - logger.severe("Irregular string of bits: " + ex.getMessage()); - } - } else { - MemoryElement tmpMem1 = symTab.getCell(param.substring(cc + 1, paramEnd).trim()); - tmpInst.getParams().add(tmpMem.getAddress() + tmpMem1.getAddress()); - } - - } else { - - cc = param.indexOf("-", paramStart); - - if (cc != -1) { - tmpMem = symTab.getCell(param.substring(paramStart, cc).trim()); - - if (Converter.isInteger(param.substring(cc + 1, paramEnd))) { - try { - imm = Integer.parseInt(paramValue); - - if (imm < -32768 || imm > 32767) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(tmpMem.getAddress() - imm); - paramStart = paramEnd + 1; - } else if (Converter.isHexNumber(param.substring(cc + 1, paramEnd))) { - try { - try { - imm = (int) Long.parseLong(Converter.hexToLong(paramValue)); - - if (imm < -32768 || imm > 32767) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(tmpMem.getAddress() - imm); - paramStart = paramEnd + 1; - } catch (IrregularStringOfHexException ex) { - //non ci dovrebbe mai arrivare - } - } else { - MemoryElement tmpMem1 = symTab.getCell(param.substring(cc + 1, paramEnd).trim()); - tmpInst.getParams().add(tmpMem.getAddress() - tmpMem1.getAddress()); - } - } else { - tmpMem = symTab.getCell(paramValue.trim()); - tmpInst.getParams().add(tmpMem.getAddress()); - } - } + tmpMem = symTab.getCell(paramValue.trim()); + immediateValue = tmpMem.getAddress(); } catch (MemoryElementNotFoundException ex) { - numError++; - error.add("INVALIDIMMEDIATE", row, line.indexOf(paramValue) + 1, line); - column = line.length(); - tmpInst.getParams().add(0); - continue; + errorMessage = "INVALIDIMMEDIATE"; } } - - // %U: Unsigned immediate. - } else if (type == 'U') { - int imm; - - if (Converter.isImmediate(paramValue)) { - if (param.charAt(paramStart) == '#') { - paramStart++; - paramValue = paramValue.substring(1); - } - - if (Converter.isInteger(paramValue)) { - try { - imm = Integer.parseInt(paramValue.trim()); - - if (imm < 0) { - numError++; - error.add("VALUEISNOTUNSIGNED", row, line.indexOf(paramValue) + 1, line); - column = line.length(); - tmpInst.getParams().add(0); - continue; - } - - if (imm < 0 || imm > 31) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("5BIT_IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - } else if (Converter.isHexNumber(paramValue.trim())) { - try { - imm = (int) Long.parseLong(Converter.hexToLong(paramValue)); - - if (imm < 0) { - numError++; - error.add("VALUEISNOTUNSIGNED", row, line.indexOf(paramValue) + 1, line); - column = line.length(); - tmpInst.getParams().add(0); - continue; - } - - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - - if (imm < 0 || imm > 31) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("5BIT_IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - - } catch (IrregularStringOfHexException ex) { - //non ci dovrebbe mai arrivare - } - } + if (errorMessage.isEmpty() && (immediateValue < -32768 || immediateValue > 32767)) { + errorMessage = "IMMEDIATE_TOO_LARGE"; + immediateValue = 0; + } - } else { + // Casting to int is safe because we know the value is between -32768 and 32767. + tmpInst.getParams().add((int)immediateValue); + if (!errorMessage.isEmpty()) { numError++; - error.add("INVALIDIMMEDIATE", row, line.indexOf(paramValue) + 1, line); + error.add(errorMessage, row, line.indexOf(paramValue) + 1, line); column = line.length(); - tmpInst.getParams().add(0); continue; } - - // %C: Unsigned Immediate (3 bit). - } else if (type == 'C') { - int imm; - - if (Converter.isImmediate(paramValue)) { - if (param.charAt(paramStart) == '#') { - paramStart++; - paramValue = paramValue.substring(1); - } - - if (Converter.isInteger(paramValue)) { - try { - imm = Integer.parseInt(paramValue.trim()); - - if (imm < 0) { - numError++; - error.add("VALUEISNOTUNSIGNED", row, line.indexOf(paramValue) + 1, line); - column = line.length(); - tmpInst.getParams().add(0); - continue; - } - - if (imm < 0 || imm > 7) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("3BIT_IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - } - - tmpInst.getParams().add(imm); + // %U: 5-bit unsigned immediate. + // %C: 3-bit unsigned immediate. + } else if (type == 'U' || type == 'C') { + // The code for %U and %C is the same, only the upper bound changes (and the overflow error message). + long maxValue = type == 'U' ? 31 : 7; + String overflowErrorMessage = type == 'U' ? "5BIT_IMMEDIATE_TOO_LARGE" : "3BIT_IMMEDIATE_TOO_LARGE"; + + long immediateValue = 0; + String errorMessage = ""; // Will be populated with the error message string if there's an error. + try { + immediateValue = Converter.parseImmediate(paramValue); + if (immediateValue < 0) { + errorMessage = "VALUEISNOTUNSIGNED"; + } else if (immediateValue > maxValue) { + errorMessage = overflowErrorMessage; + immediateValue = 0; + } else { paramStart = paramEnd + 1; - } else if (Converter.isHexNumber(paramValue.trim())) { - try { - imm = (int) Long.parseLong(Converter.hexToLong(paramValue)); - - if (imm < 0) { - numError++; - error.add("VALUEISNOTUNSIGNED", row, line.indexOf(paramValue) + 1, line); - column = line.length(); - tmpInst.getParams().add(0); - continue; - } - - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - - if (imm < 0 || imm > 31) { - throw new NumberFormatException(); - } - } catch (NumberFormatException ex) { - imm = 0; - numError++; - error.add("3BIT_IMMEDIATE_TOO_LARGE", row, line.indexOf(paramValue) + 1, line); - - tmpInst.getParams().add(imm); - paramStart = paramEnd + 1; - - } catch (IrregularStringOfHexException ex) { - //non ci dovrebbe mai arrivare - } } - - } else { + } catch (NumberFormatException e) { + errorMessage = "INVALIDIMMEDIATE"; + } + // Casting to int is safe because we know the value is between 0 and 31. + tmpInst.getParams().add((int)immediateValue); + if (!errorMessage.isEmpty()) { numError++; - error.add("INVALIDIMMEDIATE", row, line.indexOf(paramValue) + 1, line); + error.add(errorMessage, row, line.indexOf(paramValue) + 1, line); column = line.length(); - tmpInst.getParams().add(0); continue; } - // %L: Memory Label. } else if (type == 'L') { try { diff --git a/src/test/java/org/edumips64/EndToEndTests.java b/src/test/java/org/edumips64/EndToEndTests.java index ac5ed4fd6..af8ba17d1 100644 --- a/src/test/java/org/edumips64/EndToEndTests.java +++ b/src/test/java/org/edumips64/EndToEndTests.java @@ -659,7 +659,7 @@ public void testNegativeOffsets() throws Exception { /* Issue #255: Trying to store a large memory location in an immediate field causes EduMIPS64 to crash */ - @Test(expected = IntegerOverflowException.class, timeout=2000) + @Test(expected = ParserMultiException.class, timeout=2000) public void testImmediateOverflow() throws Exception { runMipsTest("immediate-overflow.s"); } diff --git a/src/test/java/org/edumips64/core/ConverterIsImmediateTest.java b/src/test/java/org/edumips64/core/ConverterIsImmediateTest.java deleted file mode 100644 index 06e59909a..000000000 --- a/src/test/java/org/edumips64/core/ConverterIsImmediateTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.edumips64.core; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -import java.util.Arrays; -import java.util.Collection; - -import static org.junit.Assert.assertEquals; - -@RunWith(Parameterized.class) -public class ConverterIsImmediateTest { - @Parameters(name = "{index}:isImmediate({0})={1}") - public static Collection data() { - return Arrays.asList(new Object[][]{ - {"", false}, {"+", false}, {"-", false}, {" ", false}, {"++", false}, - {"0", true}, {"+0", true}, {"-0", true}, {"00000000000", true}, - {"0xFF", true}, {"123", true}, {"foo", false}, - {"#", false}, {"#1", true}, {"#0xFFFF", true}, {"0x8000", true}, - {"#0xFFFFFFF", false}, {"0xFFFFFFF", false} - }); - - } - - @Parameter - public String num; - - @Parameter(1) - public boolean expected; - - @Test - public void test() { - assertEquals(expected, Converter.isImmediate(num)); - } -} \ No newline at end of file diff --git a/src/test/java/org/edumips64/core/ConverterTest.java b/src/test/java/org/edumips64/core/ConverterTest.java index dca712628..2af0acbc9 100644 --- a/src/test/java/org/edumips64/core/ConverterTest.java +++ b/src/test/java/org/edumips64/core/ConverterTest.java @@ -298,5 +298,46 @@ public void test64_BitLengthZeroReturnsZero() throws Exception{ assertEquals(0, actual); } + /** Tests for parseImmediate */ + @Test + public void ParseImmediateCorrectValues() throws Exception { + assertEquals(Converter.parseImmediate("1"), 1L); + assertEquals(Converter.parseImmediate("#1"), 1L); + assertEquals(Converter.parseImmediate("0x1"), 1L); + assertEquals(Converter.parseImmediate("0X1"), 1L); + assertEquals(Converter.parseImmediate("#0x1"), 1L); + + // Hexadecimal. + assertEquals(Converter.parseImmediate("0xA"), 10L); + assertEquals(Converter.parseImmediate("0xA"), 10L); + assertEquals(Converter.parseImmediate("0xDEADBEEF"), 3735928559L); + + // Negative numbers. + assertEquals(Converter.parseImmediate("-1"), -1L); + assertEquals(Converter.parseImmediate("#-1"), -1L); + assertEquals(Converter.parseImmediate("0x-1"), -1L); + + // Explicit plus sign. + assertEquals(Converter.parseImmediate("+1"), 1L); + assertEquals(Converter.parseImmediate("#+1"), 1L); + assertEquals(Converter.parseImmediate("0x+A"), 10L); + assertEquals(Converter.parseImmediate("#0x+A"), 10L); + assertEquals(Converter.parseImmediate("#0x+A1"), 161L); + } + + @Test(expected = NumberFormatException.class) + public void ParseImmediateEmptyString() throws Exception { + Converter.parseImmediate(""); + } + + @Test(expected = NumberFormatException.class) + public void ParseImmediateInvalidHex() throws Exception { + Converter.parseImmediate("xA"); + } + + @Test(expected = NumberFormatException.class) + public void ParseImmediateNonnumericString() throws Exception { + Converter.parseImmediate("foo"); + } } diff --git a/src/test/java/org/edumips64/core/ParserTest.java b/src/test/java/org/edumips64/core/ParserTest.java index 87d35c86f..0703dd630 100644 --- a/src/test/java/org/edumips64/core/ParserTest.java +++ b/src/test/java/org/edumips64/core/ParserTest.java @@ -155,6 +155,66 @@ public void ParserMetadataTest() throws Exception { assertEquals(5, thirdMeta.sourceLine); } + /** Tests for parsing immediate values. */ + @Test + public void ImmediateNotOutOfBoundsTest() throws Exception { + // Test that all the values just before overflow are parsed correctly. + + // 16-bit signed immediate. + ParseCode("daddi r1, r0, 32767"); + ParseCode("daddi r1, r0, -32768"); + + // 5-bit unsigned immediate + ParseCode("sll r1, r0, 31"); + ParseCode("sll r1, r0, 0"); + + // 3-bit unsigned immediate + ParseCode("movf.d f1, f2, 7"); + ParseCode("movf.d f3, f4, 0"); + } + + @Test + public void ImmediateCanStartWithHashTest() throws Exception { + // 16-bit signed immediate. + ParseCode("daddi r1, r0, #10"); + + // 5-bit unsigned immediate + ParseCode("sll r1, r0, #1"); + + // 3-bit unsigned immediate + ParseCode("movf.d f1, f2, #7"); + } + + @Test(expected = ParserMultiException.class) + public void Immediate16BitOverflow() throws Exception { + ParseCode("daddi r1, r0, 32768"); + } + + @Test(expected = ParserMultiException.class) + public void Immediate16BitUnderflow() throws Exception { + ParseCode("daddi r1, r0, -32769"); + } + + @Test(expected = ParserMultiException.class) + public void Immediate5BitOverflow() throws Exception { + ParseCode("sll r1, r0, 32"); + } + + @Test(expected = ParserMultiException.class) + public void Immediate5BitUnderflow() throws Exception { + ParseCode("sll r1, r0, -1"); + } + + @Test(expected = ParserMultiException.class) + public void Immediate3BitOverflow() throws Exception { + ParseCode("movf.d f1, f2, 8"); + } + + @Test(expected = ParserMultiException.class) + public void Immediate3BitUnderflow() throws Exception { + ParseCode("movf.d f3, f4, -1"); + } + /** Regression test for issue #95 */ @Test public void CRLFParsingTest() throws Exception { @@ -163,7 +223,7 @@ public void CRLFParsingTest() throws Exception { /** Regression tests for issue #1 */ @Test - public void NotOutOfBoundsTest() throws Exception { + public void MemoryNotOutOfBoundsTest() throws Exception { // Test that all the values just before overflow are parsed correctly. ParseData(".byte -128"); ParseData(".byte 127");