diff --git a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java index c23b9cadc..ee3f2e3c5 100644 --- a/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java +++ b/core/src/main/java/org/teavm/ast/decompilation/StatementGenerator.java @@ -232,7 +232,7 @@ class StatementGenerator implements InstructionVisitor { case SHORT: value = Expr.unary(UnaryOperation.INT_TO_SHORT, null, value); break; - case CHARACTER: + case CHAR: value = Expr.unary(UnaryOperation.INT_TO_CHAR, null, value); break; } diff --git a/core/src/main/java/org/teavm/model/Interpreter.java b/core/src/main/java/org/teavm/model/Interpreter.java index 4e24fb022..3a3a3652f 100644 --- a/core/src/main/java/org/teavm/model/Interpreter.java +++ b/core/src/main/java/org/teavm/model/Interpreter.java @@ -448,7 +448,7 @@ public class Interpreter { case SHORT: result = (short) a; break; - case CHARACTER: + case CHAR: result = (char) a; break; default: @@ -467,7 +467,7 @@ public class Interpreter { case SHORT: result = (Short) a; break; - case CHARACTER: + case CHAR: result = (Character) a; break; default: diff --git a/core/src/main/java/org/teavm/model/emit/ValueEmitter.java b/core/src/main/java/org/teavm/model/emit/ValueEmitter.java index a0925259a..e8e6cf5ed 100644 --- a/core/src/main/java/org/teavm/model/emit/ValueEmitter.java +++ b/core/src/main/java/org/teavm/model/emit/ValueEmitter.java @@ -231,7 +231,7 @@ public class ValueEmitter { case SHORT: return IntegerSubtype.SHORT; case CHARACTER: - return IntegerSubtype.CHARACTER; + return IntegerSubtype.CHAR; default: break; } @@ -776,7 +776,7 @@ public class ValueEmitter { return ValueType.BYTE; case SHORT: return ValueType.SHORT; - case CHARACTER: + case CHAR: return ValueType.CHARACTER; } throw new IllegalArgumentException("Unknown subtype: " + subtype); @@ -794,7 +794,7 @@ public class ValueEmitter { throw new EmitException("Can't cast non-short value: " + type); } break; - case CHARACTER: + case CHAR: if (type != ValueType.CHARACTER) { throw new EmitException("Can't cast non-char value: " + type); } diff --git a/core/src/main/java/org/teavm/model/instructions/IntegerSubtype.java b/core/src/main/java/org/teavm/model/instructions/IntegerSubtype.java index 6adf7cc9b..3000ee178 100644 --- a/core/src/main/java/org/teavm/model/instructions/IntegerSubtype.java +++ b/core/src/main/java/org/teavm/model/instructions/IntegerSubtype.java @@ -15,12 +15,8 @@ */ package org.teavm.model.instructions; -/** - * - * @author Alexey Andreev - */ public enum IntegerSubtype { BYTE, SHORT, - CHARACTER + CHAR } diff --git a/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java b/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java index 2a4da1ef8..aabd02af7 100644 --- a/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java +++ b/core/src/main/java/org/teavm/model/optimization/GlobalValueNumbering.java @@ -560,7 +560,7 @@ public class GlobalValueNumbering implements MethodOptimization { case SHORT: evaluatedConstant = value.intValue() << 16 >> 16; break; - case CHARACTER: + case CHAR: evaluatedConstant = value.intValue() & 0xFFFF; break; } diff --git a/core/src/main/java/org/teavm/model/text/ListingParser.java b/core/src/main/java/org/teavm/model/text/ListingParser.java index e1d5b9b31..4ba7bffbd 100644 --- a/core/src/main/java/org/teavm/model/text/ListingParser.java +++ b/core/src/main/java/org/teavm/model/text/ListingParser.java @@ -22,6 +22,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import org.teavm.model.BasicBlock; @@ -40,12 +41,17 @@ import org.teavm.model.instructions.BinaryInstruction; import org.teavm.model.instructions.BinaryOperation; import org.teavm.model.instructions.BranchingCondition; import org.teavm.model.instructions.BranchingInstruction; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.CastIntegerDirection; +import org.teavm.model.instructions.CastIntegerInstruction; +import org.teavm.model.instructions.CastNumberInstruction; import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.DoubleConstantInstruction; import org.teavm.model.instructions.EmptyInstruction; import org.teavm.model.instructions.ExitInstruction; import org.teavm.model.instructions.FloatConstantInstruction; import org.teavm.model.instructions.IntegerConstantInstruction; +import org.teavm.model.instructions.IntegerSubtype; import org.teavm.model.instructions.InvocationType; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.JumpInstruction; @@ -290,6 +296,9 @@ public class ListingParser { case "invokeStatic": parseInvoke(block, receiver); break; + case "cast": + parseCast(block, receiver); + break; default: unexpected(); break; @@ -549,6 +558,125 @@ public class ListingParser { insn.getArguments().addAll(arguments); } + insn.setLocation(currentLocation); + block.getInstructions().add(insn); + } + + private void parseCast(BasicBlock block, Variable receiver) throws IOException, ListingParseException { + lexer.nextToken(); + Variable value = expectVariable(); + expect(ListingToken.IDENTIFIER); + switch ((String) lexer.getTokenValue()) { + case "from": + parseNumericCast(block, receiver, value); + break; + case "to": + parseObjectCast(block, receiver, value); + break; + default: + unexpected(); + } + } + + private void parseNumericCast(BasicBlock block, Variable receiver, Variable value) + throws IOException, ListingParseException { + lexer.nextToken(); + + NumericTypeOrIntegerSubtype source = expectTypeOrIntegerSubtype(); + + if (source.subtype != null) { + CastIntegerInstruction insn = new CastIntegerInstruction(source.subtype, + CastIntegerDirection.TO_INTEGER); + expectKeyword("to"); + expectKeyword("int"); + insn.setReceiver(receiver); + insn.setValue(value); + insn.setLocation(currentLocation); + block.getInstructions().add(insn); + } else { + expectKeyword("to"); + expect(ListingToken.IDENTIFIER); + NumericTypeOrIntegerSubtype target = expectTypeOrIntegerSubtype(); + if (target.subtype != null) { + if (source.type != NumericOperandType.INT) { + throw new ListingParseException("Only int can be cast to " + + target.subtype.name().toLowerCase(Locale.ROOT), lexer.getIndex()); + } + CastIntegerInstruction insn = new CastIntegerInstruction(source.subtype, + CastIntegerDirection.FROM_INTEGER); + insn.setReceiver(receiver); + insn.setValue(value); + insn.setLocation(currentLocation); + block.getInstructions().add(insn); + } else { + CastNumberInstruction insn = new CastNumberInstruction(source.type, target.type); + insn.setReceiver(receiver); + insn.setValue(value); + insn.setLocation(currentLocation); + block.getInstructions().add(insn); + } + } + } + + private NumericTypeOrIntegerSubtype expectTypeOrIntegerSubtype() throws IOException, ListingParseException { + IntegerSubtype subtype = null; + NumericOperandType type = null; + expect(ListingToken.IDENTIFIER); + switch ((String) lexer.getTokenValue()) { + case "byte": + subtype = IntegerSubtype.BYTE; + break; + case "short": + subtype = IntegerSubtype.SHORT; + break; + case "char": + subtype = IntegerSubtype.CHAR; + break; + case "int": + type = NumericOperandType.INT; + break; + case "long": + type = NumericOperandType.LONG; + break; + case "float": + type = NumericOperandType.FLOAT; + break; + case "double": + type = NumericOperandType.DOUBLE; + break; + default: + unexpected(); + return null; + } + lexer.nextToken(); + return new NumericTypeOrIntegerSubtype(type, subtype); + } + + private static class NumericTypeOrIntegerSubtype { + NumericOperandType type; + IntegerSubtype subtype; + + public NumericTypeOrIntegerSubtype(NumericOperandType type, IntegerSubtype subtype) { + this.type = type; + this.subtype = subtype; + } + } + + private void parseObjectCast(BasicBlock block, Variable receiver, Variable value) + throws IOException, ListingParseException { + lexer.nextToken(); + expect(ListingToken.IDENTIFIER); + ValueType type = ValueType.parseIfPossible((String) lexer.getTokenValue()); + if (type == null) { + throw new ListingParseException("Unparseable type", lexer.getTokenStart()); + } + lexer.nextToken(); + + CastInstruction insn = new CastInstruction(); + insn.setReceiver(receiver); + insn.setValue(value); + insn.setTargetType(type); + insn.setLocation(currentLocation); block.getInstructions().add(insn); } diff --git a/core/src/main/java/org/teavm/parsing/ProgramParser.java b/core/src/main/java/org/teavm/parsing/ProgramParser.java index 34069b9b6..fa293e63c 100644 --- a/core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -185,12 +185,7 @@ public class ProgramParser { } for (LocalVariableNode localVar : method.localVariables) { int location = labelIndexes.get(localVar.start.getLabel()); - List vars = localVariableMap.get(location); - if (vars == null) { - vars = new ArrayList<>(); - localVariableMap.put(location, vars); - } - vars.add(localVar); + localVariableMap.computeIfAbsent(location, k -> new ArrayList<>()).add(localVar); } targetInstructions = new ArrayList<>(instructions.size()); targetInstructions.addAll(Collections.nCopies(instructions.size(), null)); @@ -1004,7 +999,7 @@ public class ProgramParser { } case Opcodes.CALOAD: { loadArrayElement(1, ArrayElementType.CHAR); - CastIntegerInstruction insn = new CastIntegerInstruction(IntegerSubtype.CHARACTER, + CastIntegerInstruction insn = new CastIntegerInstruction(IntegerSubtype.CHAR, CastIntegerDirection.TO_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); @@ -1492,7 +1487,7 @@ public class ProgramParser { break; } case Opcodes.I2C: { - CastIntegerInstruction insn = new CastIntegerInstruction(IntegerSubtype.CHARACTER, + CastIntegerInstruction insn = new CastIntegerInstruction(IntegerSubtype.CHAR, CastIntegerDirection.FROM_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); diff --git a/core/src/test/java/org/teavm/model/text/ParserTest.java b/core/src/test/java/org/teavm/model/text/ParserTest.java index 0be254cba..777858f6c 100644 --- a/core/src/test/java/org/teavm/model/text/ParserTest.java +++ b/core/src/test/java/org/teavm/model/text/ParserTest.java @@ -26,6 +26,9 @@ import org.junit.Assert; import org.junit.Test; import org.teavm.model.BasicBlock; import org.teavm.model.Program; +import org.teavm.model.instructions.CastInstruction; +import org.teavm.model.instructions.CastIntegerInstruction; +import org.teavm.model.instructions.CastNumberInstruction; import org.teavm.model.instructions.ClassConstantInstruction; import org.teavm.model.instructions.DoubleConstantInstruction; import org.teavm.model.instructions.FloatConstantInstruction; @@ -102,6 +105,18 @@ public class ParserTest { assertNotNull(invoke.getInstance()); } + @Test + public void casting() throws Exception { + Program program = runTest("casting"); + assertEquals(1, program.basicBlockCount()); + + BasicBlock block = program.basicBlockAt(0); + assertTrue(block.getInstructions().get(0) instanceof CastInstruction); + assertTrue(block.getInstructions().get(1) instanceof CastIntegerInstruction); + assertTrue(block.getInstructions().get(2) instanceof CastIntegerInstruction); + assertTrue(block.getInstructions().get(3) instanceof CastNumberInstruction); + } + private Program runTest(String name) throws IOException { ClassLoader classLoader = ParserTest.class.getClassLoader(); String path = "model/text/" + name + ".txt"; diff --git a/core/src/test/resources/model/text/casting.txt b/core/src/test/resources/model/text/casting.txt new file mode 100644 index 000000000..0fbdcc437 --- /dev/null +++ b/core/src/test/resources/model/text/casting.txt @@ -0,0 +1,6 @@ +$block + @r1 := cast @a to `Ljava/lang/String;` + @r2 := cast @b from int to char + @r3 := cast @c from byte to int + @r4 := cast @d from double to long + return @r1 \ No newline at end of file