From 5e7311d8cc4ed2397ccb180a4a667bb624a66eff Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 23 Feb 2016 13:44:23 +0300 Subject: [PATCH] Start porting metaprogramming API implementation --- .idea/misc.xml | 1 + .../metaprogramming/impl/AliasFinder.java | 308 +++++ .../impl/CompositeMethodGenerator.java | 1121 +++++++++++++++++ .../metaprogramming/impl/LazyValueImpl.java | 46 + .../impl/MetaprogrammingImpl.java | 180 ++- metaprogramming-api/pom.xml | 21 + .../metaprogramming/Metaprogramming.java | 12 +- .../java/org/teavm/metaprogramming/Scope.java | 20 - .../test/MetaprogrammingTest.java | 40 + .../teavm-metaprogramming-api.iml | 15 + tests/pom.xml | 7 + tests/teavm-tests.iml | 1 + 12 files changed, 1737 insertions(+), 35 deletions(-) create mode 100644 core/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java create mode 100644 core/src/main/java/org/teavm/metaprogramming/impl/CompositeMethodGenerator.java create mode 100644 core/src/main/java/org/teavm/metaprogramming/impl/LazyValueImpl.java delete mode 100644 metaprogramming-api/src/main/java/org/teavm/metaprogramming/Scope.java create mode 100644 metaprogramming-api/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java diff --git a/.idea/misc.xml b/.idea/misc.xml index ec13cf2f5..6f86e5810 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -45,6 +45,7 @@ + diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java b/core/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java new file mode 100644 index 000000000..038e2161b --- /dev/null +++ b/core/src/main/java/org/teavm/metaprogramming/impl/AliasFinder.java @@ -0,0 +1,308 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.metaprogramming.impl; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.teavm.common.DisjointSet; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.FieldReference; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHandle; +import org.teavm.model.MethodReference; +import org.teavm.model.PhiReader; +import org.teavm.model.ProgramReader; +import org.teavm.model.RuntimeConstant; +import org.teavm.model.ValueType; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.ArrayElementType; +import org.teavm.model.instructions.BinaryBranchingCondition; +import org.teavm.model.instructions.BinaryOperation; +import org.teavm.model.instructions.BranchingCondition; +import org.teavm.model.instructions.CastIntegerDirection; +import org.teavm.model.instructions.InstructionReader; +import org.teavm.model.instructions.IntegerSubtype; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.NumericOperandType; +import org.teavm.model.instructions.SwitchTableEntryReader; + +/** + * + * @author Alexey Andreev + */ +public class AliasFinder { + int[] aliases; + Object[] constants; + ArrayElement[] arrayElements; + + public void findAliases(ProgramReader program) { + DisjointSet set = new DisjointSet(); + for (int i = 0; i < program.variableCount(); ++i) { + set.create(); + } + + AliasReader reader = new AliasReader(set, program.variableCount()); + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlockReader block = program.basicBlockAt(i); + block.readAllInstructions(reader); + for (PhiReader phi : block.readPhis()) { + Set inputs = phi.readIncomings().stream() + .map(incoming -> incoming.getValue().getIndex()) + .collect(Collectors.toSet()); + if (inputs.size() == 1) { + set.union(inputs.iterator().next(), phi.getReceiver().getIndex()); + } + } + } + + int[] map = new int[set.size()]; + Arrays.fill(map, -1); + int[] variables = new int[program.variableCount()]; + for (int i = 0; i < variables.length; ++i) { + int v = set.find(i); + int master = map[v]; + if (master == -1) { + master = i; + map[v] = master; + } + variables[i] = master; + } + + aliases = variables; + constants = reader.constants; + arrayElements = reader.arrayElements; + + for (int i = 0; i < arrayElements.length; ++i) { + ArrayElement elem = arrayElements[i]; + if (elem == null) { + continue; + } + elem.index = aliases[elem.index]; + if (constants[elem.index] instanceof Integer) { + elem.index = (Integer) constants[elem.index]; + } else { + arrayElements[i] = null; + } + } + } + + public static class ArrayElement { + public int array; + public int index; + } + + public int[] getAliases() { + return aliases.clone(); + } + + public Object[] getConstants() { + return constants.clone(); + } + + public ArrayElement[] getArrayElements() { + return arrayElements.clone(); + } + + static class AliasReader implements InstructionReader { + DisjointSet disjointSet; + Object[] constants; + ArrayElement[] arrayElements; + + AliasReader(DisjointSet disjointSet, int variableCount) { + this.disjointSet = disjointSet; + this.constants = new Object[variableCount]; + this.arrayElements = new ArrayElement[variableCount]; + } + + @Override + public void location(InstructionLocation location) { + } + + @Override + public void nop() { + } + + @Override + public void classConstant(VariableReader receiver, ValueType cst) { + constants[receiver.getIndex()] = cst; + } + + @Override + public void nullConstant(VariableReader receiver) { + } + + @Override + public void integerConstant(VariableReader receiver, int cst) { + constants[receiver.getIndex()] = cst; + } + + @Override + public void longConstant(VariableReader receiver, long cst) { + constants[receiver.getIndex()] = cst; + } + + @Override + public void floatConstant(VariableReader receiver, float cst) { + constants[receiver.getIndex()] = cst; + } + + @Override + public void doubleConstant(VariableReader receiver, double cst) { + constants[receiver.getIndex()] = cst; + } + + @Override + public void stringConstant(VariableReader receiver, String cst) { + constants[receiver.getIndex()] = cst; + } + + @Override + public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second, + NumericOperandType type) { + } + + @Override + public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) { + } + + @Override + public void assign(VariableReader receiver, VariableReader assignee) { + disjointSet.union(receiver.getIndex(), assignee.getIndex()); + } + + @Override + public void cast(VariableReader receiver, VariableReader value, ValueType targetType) { + } + + @Override + public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType, + NumericOperandType targetType) { + } + + @Override + public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type, + CastIntegerDirection targetType) { + } + + @Override + public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent, + BasicBlockReader alternative) { + } + + @Override + public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second, + BasicBlockReader consequent, BasicBlockReader alternative) { + } + + @Override + public void jump(BasicBlockReader target) { + } + + @Override + public void choose(VariableReader condition, List table, + BasicBlockReader defaultTarget) { + } + + @Override + public void exit(VariableReader valueToReturn) { + } + + @Override + public void raise(VariableReader exception) { + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, + List dimensions) { + } + + @Override + public void create(VariableReader receiver, String type) { + } + + @Override + public void getField(VariableReader receiver, VariableReader instance, FieldReference field, + ValueType fieldType) { + } + + @Override + public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) { + } + + @Override + public void arrayLength(VariableReader receiver, VariableReader array) { + } + + @Override + public void cloneArray(VariableReader receiver, VariableReader array) { + } + + @Override + public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { + disjointSet.union(receiver.getIndex(), array.getIndex()); + } + + @Override + public void getElement(VariableReader receiver, VariableReader array, VariableReader index) { + ArrayElement elem = new ArrayElement(); + elem.array = array.getIndex(); + elem.index = index.getIndex(); + arrayElements[receiver.getIndex()] = elem; + } + + @Override + public void putElement(VariableReader array, VariableReader index, VariableReader value) { + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + } + + @Override + public void invokeDynamic(VariableReader receiver, VariableReader instance, MethodDescriptor method, + List arguments, MethodHandle bootstrapMethod, + List bootstrapArguments) { + } + + @Override + public void isInstance(VariableReader receiver, VariableReader value, ValueType type) { + } + + @Override + public void initClass(String className) { + } + + @Override + public void nullCheck(VariableReader receiver, VariableReader value) { + } + + @Override + public void monitorEnter(VariableReader objectRef) { + } + + @Override + public void monitorExit(VariableReader objectRef) { + } + } +} diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/CompositeMethodGenerator.java b/core/src/main/java/org/teavm/metaprogramming/impl/CompositeMethodGenerator.java new file mode 100644 index 000000000..38c57da76 --- /dev/null +++ b/core/src/main/java/org/teavm/metaprogramming/impl/CompositeMethodGenerator.java @@ -0,0 +1,1121 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.metaprogramming.impl; + +import java.lang.reflect.Array; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.teavm.diagnostics.Diagnostics; +import org.teavm.metaprogramming.ReflectClass; +import org.teavm.metaprogramming.Value; +import org.teavm.metaprogramming.impl.AliasFinder.ArrayElement; +import org.teavm.metaprogramming.impl.reflect.ReflectClassImpl; +import org.teavm.metaprogramming.impl.reflect.ReflectFieldImpl; +import org.teavm.metaprogramming.impl.reflect.ReflectMethodImpl; +import org.teavm.metaprogramming.reflect.ReflectField; +import org.teavm.metaprogramming.reflect.ReflectMethod; +import org.teavm.model.BasicBlock; +import org.teavm.model.BasicBlockReader; +import org.teavm.model.CallLocation; +import org.teavm.model.ElementModifier; +import org.teavm.model.FieldReference; +import org.teavm.model.Incoming; +import org.teavm.model.IncomingReader; +import org.teavm.model.Instruction; +import org.teavm.model.InstructionLocation; +import org.teavm.model.InvokeDynamicInstruction; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodHandle; +import org.teavm.model.MethodReference; +import org.teavm.model.Phi; +import org.teavm.model.PhiReader; +import org.teavm.model.Program; +import org.teavm.model.ProgramReader; +import org.teavm.model.RuntimeConstant; +import org.teavm.model.TryCatchBlock; +import org.teavm.model.TryCatchBlockReader; +import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.VariableReader; +import org.teavm.model.instructions.ArrayElementType; +import org.teavm.model.instructions.ArrayLengthInstruction; +import org.teavm.model.instructions.AssignInstruction; +import org.teavm.model.instructions.BinaryBranchingCondition; +import org.teavm.model.instructions.BinaryBranchingInstruction; +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.CloneArrayInstruction; +import org.teavm.model.instructions.ConstructArrayInstruction; +import org.teavm.model.instructions.ConstructInstruction; +import org.teavm.model.instructions.ConstructMultiArrayInstruction; +import org.teavm.model.instructions.DoubleConstantInstruction; +import org.teavm.model.instructions.FloatConstantInstruction; +import org.teavm.model.instructions.GetElementInstruction; +import org.teavm.model.instructions.GetFieldInstruction; +import org.teavm.model.instructions.InitClassInstruction; +import org.teavm.model.instructions.InstructionReader; +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.IsInstanceInstruction; +import org.teavm.model.instructions.JumpInstruction; +import org.teavm.model.instructions.LongConstantInstruction; +import org.teavm.model.instructions.MonitorEnterInstruction; +import org.teavm.model.instructions.MonitorExitInstruction; +import org.teavm.model.instructions.NegateInstruction; +import org.teavm.model.instructions.NullCheckInstruction; +import org.teavm.model.instructions.NullConstantInstruction; +import org.teavm.model.instructions.NumericOperandType; +import org.teavm.model.instructions.PutElementInstruction; +import org.teavm.model.instructions.PutFieldInstruction; +import org.teavm.model.instructions.RaiseInstruction; +import org.teavm.model.instructions.StringConstantInstruction; +import org.teavm.model.instructions.SwitchInstruction; +import org.teavm.model.instructions.SwitchTableEntry; +import org.teavm.model.instructions.SwitchTableEntryReader; +import org.teavm.model.instructions.UnwrapArrayInstruction; + +public class CompositeMethodGenerator { + private Diagnostics diagnostics; + Program program; + InstructionLocation location; + InstructionLocation forcedLocation; + int blockIndex; + int returnBlockIndex; + private Variable resultVar; + private Phi resultPhi; + private Map phiBlockMap = new HashMap<>(); + VariableContext varContext; + + CompositeMethodGenerator(VariableContext varContext) { + this(varContext, new Program()); + program.createBasicBlock(); + } + + CompositeMethodGenerator(VariableContext varContext, Program program) { + this.diagnostics = MetaprogrammingImpl.agent.getDiagnostics(); + this.program = program; + this.varContext = varContext; + } + + public void addProgram(ProgramReader template, List capturedValues) { + location = null; + resultVar = null; + resultPhi = null; + AliasFinder aliasFinder = new AliasFinder(); + aliasFinder.findAliases(template); + + CapturedValue[] capturedValueArray = new CapturedValue[template.variableCount()]; + for (int i = 0; i < capturedValues.size(); ++i) { + capturedValueArray[i + 1] = capturedValues.get(i); + } + + TemplateSubstitutor substitutor = new TemplateSubstitutor(capturedValueArray, aliasFinder.getAliases(), + aliasFinder.getArrayElements(), program.basicBlockCount() - 1, + program.variableCount() - capturedValues.size()); + + for (int i = 0; i < template.basicBlockCount(); ++i) { + program.createBasicBlock(); + } + returnBlockIndex = program.basicBlockCount() - 1; + + for (int i = capturedValues.size(); i < template.variableCount(); ++i) { + program.createVariable(); + } + + int startBlock = blockIndex; + for (int i = 0; i < template.basicBlockCount(); ++i) { + BasicBlockReader templateBlock = template.basicBlockAt(i); + if (i > 0) { + blockIndex = substitutor.blockOffset + i; + } + BasicBlock targetBlock = program.basicBlockAt(blockIndex); + + for (PhiReader templatePhi : templateBlock.readPhis()) { + Phi phi = new Phi(); + for (IncomingReader templateIncoming : templatePhi.readIncomings()) { + Incoming incoming = new Incoming(); + incoming.setSource(substitutor.block(templateIncoming.getSource())); + incoming.setValue(substitutor.var(templateIncoming.getValue())); + phi.getIncomings().add(incoming); + } + phi.setReceiver(substitutor.var(templatePhi.getReceiver())); + targetBlock.getPhis().add(phi); + } + + for (TryCatchBlockReader templateTryCatch : templateBlock.readTryCatchBlocks()) { + TryCatchBlock tryCatch = new TryCatchBlock(); + tryCatch.setExceptionType(templateTryCatch.getExceptionType()); + tryCatch.setExceptionVariable(substitutor.var(templateTryCatch.getExceptionVariable())); + tryCatch.setHandler(substitutor.block(templateTryCatch.getHandler())); + targetBlock.getTryCatchBlocks().add(tryCatch); + } + + templateBlock.readAllInstructions(substitutor); + phiBlockMap.put(targetBlock, currentBlock()); + } + + for (int i = 0; i < template.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i == 0 ? startBlock : substitutor.blockOffset + i); + for (Phi phi : block.getPhis()) { + for (Incoming incoming : phi.getIncomings()) { + BasicBlock mappedBlock = phiBlockMap.get(incoming.getSource()); + if (mappedBlock != null) { + incoming.setSource(mappedBlock); + } + } + } + } + + phiBlockMap.clear(); + blockIndex = substitutor.blockOffset + template.basicBlockCount(); + } + + public Variable getResultVar() { + return resultVar; + } + + public BasicBlock currentBlock() { + return program.basicBlockAt(blockIndex); + } + + void add(Instruction insn) { + insn.setLocation(forcedLocation != null ? forcedLocation : location); + program.basicBlockAt(blockIndex).getInstructions().add(insn); + } + + Variable captureValue(CapturedValue captured) { + Object value = captured.obj; + if (value == null) { + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(program.createVariable()); + add(insn); + return insn.getReceiver(); + } else if (value instanceof Integer) { + IntegerConstantInstruction insn = new IntegerConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant((Integer) value); + add(insn); + Variable result = insn.getReceiver(); + if (!captured.primitive) { + result = box(result, ValueType.INTEGER); + } + return result; + } else if (value instanceof Long) { + LongConstantInstruction insn = new LongConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant((Long) value); + add(insn); + Variable result = insn.getReceiver(); + if (!captured.primitive) { + result = box(result, ValueType.LONG); + } + return result; + } else if (value instanceof Float) { + FloatConstantInstruction insn = new FloatConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant((Float) value); + add(insn); + Variable result = insn.getReceiver(); + if (!captured.primitive) { + result = box(result, ValueType.FLOAT); + } + return result; + } else if (value instanceof Double) { + DoubleConstantInstruction insn = new DoubleConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant((Double) value); + add(insn); + Variable result = insn.getReceiver(); + if (!captured.primitive) { + result = box(result, ValueType.DOUBLE); + } + return result; + } else if (value instanceof String) { + StringConstantInstruction insn = new StringConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant((String) value); + add(insn); + return insn.getReceiver(); + } else if (value instanceof ValueType) { + ClassConstantInstruction insn = new ClassConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant((ValueType) value); + add(insn); + return insn.getReceiver(); + } else if (value instanceof Class) { + ClassConstantInstruction insn = new ClassConstantInstruction(); + insn.setReceiver(program.createVariable()); + insn.setConstant(ValueType.parse((Class) value)); + add(insn); + return insn.getReceiver(); + } else if (value instanceof ValueImpl) { + return varContext.emitVariable((ValueImpl) value, new CallLocation(MetaprogrammingImpl.templateMethod, + location)); + } else if (value instanceof LazyValueImpl) { + return lazy((LazyValueImpl) value); + } else if (value instanceof ReflectFieldImpl) { + ReflectFieldImpl reflectField = (ReflectFieldImpl) value; + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can't reference this ReflectField {{f0}} directly except for calling special " + + "methods on it", reflectField.field.getReference()); + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(program.createVariable()); + add(insn); + return insn.getReceiver(); + } else if (value instanceof ReflectMethodImpl) { + ReflectMethodImpl reflectMethod = (ReflectMethodImpl) value; + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can't reference this ReflectMethod {{m0}} directly except for calling special methods on it", + reflectMethod.method.getReference()); + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(program.createVariable()); + add(insn); + return insn.getReceiver(); + } else if (value.getClass().getComponentType() != null) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can't reference this array directly except for fetching by constant index"); + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(program.createVariable()); + add(insn); + return insn.getReceiver(); + } else { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), "Wrong captured value"); + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(program.createVariable()); + add(insn); + return insn.getReceiver(); + } + } + + Variable lazy(LazyValueImpl lazyImpl) { + CompositeMethodGenerator nestedGenerator = new CompositeMethodGenerator(varContext, program); + nestedGenerator.blockIndex = blockIndex; + nestedGenerator.location = location; + nestedGenerator.forcedLocation = lazyImpl.forcedLocation; + MetaprogrammingImpl.generator = nestedGenerator; + Value result = lazyImpl.computation.compute(); + blockIndex = nestedGenerator.blockIndex; + MetaprogrammingImpl.generator = this; + + if (result instanceof ValueImpl) { + return ((ValueImpl) result).innerValue; + } else if (result instanceof LazyValueImpl) { + return lazy((LazyValueImpl) result); + } else if (result != null) { + throw new IllegalStateException("Unknown value type: " + result.getClass().getName()); + } else { + return null; + } + } + + Variable box(Variable var, ValueType type) { + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + return box(var, boolean.class, Boolean.class); + case BYTE: + return box(var, byte.class, Byte.class); + case SHORT: + return box(var, short.class, Short.class); + case CHARACTER: + return box(var, char.class, Character.class); + case INTEGER: + return box(var, int.class, Integer.class); + case LONG: + return box(var, long.class, Long.class); + case FLOAT: + return box(var, float.class, Float.class); + case DOUBLE: + return box(var, double.class, Double.class); + } + } + return var; + } + + private Variable box(Variable var, Class primitive, Class wrapper) { + InvokeInstruction insn = new InvokeInstruction(); + insn.setMethod(new MethodReference(wrapper, "valueOf", primitive, wrapper)); + insn.setType(InvocationType.SPECIAL); + insn.getArguments().add(var); + var = program.createVariable(); + insn.setReceiver(var); + add(insn); + return var; + } + + Variable unbox(Variable var, ValueType type) { + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + return unbox(var, boolean.class, Boolean.class); + case BYTE: + return unbox(var, byte.class, Byte.class); + case SHORT: + return unbox(var, short.class, Short.class); + case CHARACTER: + return unbox(var, char.class, Character.class); + case INTEGER: + return unbox(var, int.class, Integer.class); + case LONG: + return unbox(var, long.class, Long.class); + case FLOAT: + return unbox(var, float.class, Float.class); + case DOUBLE: + return unbox(var, double.class, Double.class); + } + } else if (!type.isObject(Object.class)) { + CastInstruction castInsn = new CastInstruction(); + castInsn.setValue(var); + castInsn.setReceiver(program.createVariable()); + castInsn.setTargetType(type); + var = castInsn.getReceiver(); + add(castInsn); + } + return var; + } + + private Variable unbox(Variable var, Class primitive, Class wrapper) { + CastInstruction castInsn = new CastInstruction(); + castInsn.setValue(var); + castInsn.setReceiver(program.createVariable()); + castInsn.setTargetType(ValueType.parse(wrapper)); + add(castInsn); + + InvokeInstruction insn = new InvokeInstruction(); + insn.setMethod(new MethodReference(wrapper, primitive.getName() + "Value", primitive)); + insn.setType(InvocationType.VIRTUAL); + insn.setInstance(castInsn.getReceiver()); + var = program.createVariable(); + insn.setReceiver(var); + add(insn); + return var; + } + + public Program getProgram() { + return program; + } + + private class TemplateSubstitutor implements InstructionReader { + private int blockOffset; + private int variableOffset; + int[] variableMapping; + CapturedValue[] capturedValues; + ArrayElement[] arrayElements; + + TemplateSubstitutor(CapturedValue[] capturedValues, int[] variableMapping, ArrayElement[] arrayElements, + int blockOffset, int variableOffset) { + this.capturedValues = capturedValues; + this.variableMapping = variableMapping; + this.arrayElements = arrayElements; + this.blockOffset = blockOffset; + this.variableOffset = variableOffset; + } + + @Override + public void location(InstructionLocation location) { + CompositeMethodGenerator.this.location = location; + } + + @Override + public void nop() { + } + + public Variable var(VariableReader variable) { + if (variable == null) { + return null; + } + int index = variableMapping[variable.getIndex()]; + if (capturedValues[index] != null) { + return captureValue(capturedValues[index]); + } + ArrayElement elem = arrayElements[index]; + if (elem != null) { + int arrayVar = variableMapping[elem.array]; + if (capturedValues[arrayVar] != null) { + Object capturedArray = capturedValues[arrayVar].obj; + boolean primitive = capturedArray.getClass().getComponentType().isPrimitive(); + return captureValue(new CapturedValue(Array.get(capturedArray, elem.index), primitive)); + } + } + return program.variableAt(variableOffset + variable.getIndex()); + } + + public BasicBlock block(BasicBlockReader block) { + if (block == null) { + return null; + } + return program.basicBlockAt(blockOffset + block.getIndex()); + } + + @Override + public void classConstant(VariableReader receiver, ValueType cst) { + ClassConstantInstruction insn = new ClassConstantInstruction(); + insn.setConstant(cst); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void nullConstant(VariableReader receiver) { + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void integerConstant(VariableReader receiver, int cst) { + IntegerConstantInstruction insn = new IntegerConstantInstruction(); + insn.setConstant(cst); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void longConstant(VariableReader receiver, long cst) { + LongConstantInstruction insn = new LongConstantInstruction(); + insn.setConstant(cst); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void floatConstant(VariableReader receiver, float cst) { + FloatConstantInstruction insn = new FloatConstantInstruction(); + insn.setConstant(cst); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void doubleConstant(VariableReader receiver, double cst) { + DoubleConstantInstruction insn = new DoubleConstantInstruction(); + insn.setConstant(cst); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void stringConstant(VariableReader receiver, String cst) { + StringConstantInstruction insn = new StringConstantInstruction(); + insn.setConstant(cst); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second, + NumericOperandType type) { + BinaryInstruction insn = new BinaryInstruction(op, type); + insn.setReceiver(var(receiver)); + insn.setFirstOperand(var(first)); + insn.setSecondOperand(var(second)); + add(insn); + } + + @Override + public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) { + NegateInstruction insn = new NegateInstruction(type); + insn.setReceiver(var(receiver)); + insn.setOperand(var(operand)); + add(insn); + } + + @Override + public void assign(VariableReader receiver, VariableReader assignee) { + int index = variableMapping[assignee.getIndex()]; + if (capturedValues[index] != null) { + return; + } + + AssignInstruction insn = new AssignInstruction(); + insn.setReceiver(var(receiver)); + insn.setAssignee(var(assignee)); + if (insn.getReceiver() != insn.getAssignee()) { + add(insn); + } + } + + @Override + public void cast(VariableReader receiver, VariableReader value, ValueType targetType) { + CastInstruction insn = new CastInstruction(); + insn.setTargetType(targetType); + insn.setValue(var(value)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType, + NumericOperandType targetType) { + CastNumberInstruction insn = new CastNumberInstruction(sourceType, targetType); + insn.setValue(var(value)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type, + CastIntegerDirection targetType) { + CastIntegerInstruction insn = new CastIntegerInstruction(type, targetType); + insn.setValue(var(value)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent, + BasicBlockReader alternative) { + BranchingInstruction insn = new BranchingInstruction(cond); + insn.setOperand(var(operand)); + insn.setConsequent(block(consequent)); + insn.setAlternative(block(alternative)); + add(insn); + } + + @Override + public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second, + BasicBlockReader consequent, BasicBlockReader alternative) { + BinaryBranchingInstruction insn = new BinaryBranchingInstruction(cond); + insn.setFirstOperand(var(first)); + insn.setSecondOperand(var(second)); + insn.setConsequent(block(consequent)); + insn.setAlternative(block(alternative)); + add(insn); + } + + @Override + public void jump(BasicBlockReader target) { + JumpInstruction insn = new JumpInstruction(); + insn.setTarget(block(target)); + add(insn); + } + + @Override + public void choose(VariableReader condition, List table, + BasicBlockReader defaultTarget) { + SwitchInstruction insn = new SwitchInstruction(); + insn.setCondition(var(condition)); + insn.setDefaultTarget(block(defaultTarget)); + for (SwitchTableEntryReader entry : table) { + SwitchTableEntry insnEntry = new SwitchTableEntry(); + insnEntry.setCondition(entry.getCondition()); + insnEntry.setTarget(block(entry.getTarget())); + insn.getEntries().add(insnEntry); + } + add(insn); + } + + @Override + public void exit(VariableReader valueToReturn) { + BasicBlock target = program.basicBlockAt(returnBlockIndex); + + if (valueToReturn != null) { + if (resultVar == null) { + resultVar = program.createVariable(); + resultPhi = new Phi(); + resultPhi.setReceiver(resultVar); + target.getPhis().add(resultPhi); + } + Incoming incoming = new Incoming(); + incoming.setSource(program.basicBlockAt(blockIndex)); + incoming.setValue(var(valueToReturn)); + resultPhi.getIncomings().add(incoming); + } + + JumpInstruction insn = new JumpInstruction(); + insn.setTarget(target); + add(insn); + } + + @Override + public void raise(VariableReader exception) { + RaiseInstruction insn = new RaiseInstruction(); + insn.setException(var(exception)); + add(insn); + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { + ConstructArrayInstruction insn = new ConstructArrayInstruction(); + insn.setReceiver(var(receiver)); + insn.setItemType(itemType); + insn.setSize(var(size)); + add(insn); + } + + @Override + public void createArray(VariableReader receiver, ValueType itemType, + List dimensions) { + ConstructMultiArrayInstruction insn = new ConstructMultiArrayInstruction(); + insn.setReceiver(var(receiver)); + insn.setItemType(itemType); + insn.getDimensions().addAll(dimensions.stream().map(this::var).collect(Collectors.toList())); + add(insn); + } + + @Override + public void create(VariableReader receiver, String type) { + ConstructInstruction insn = new ConstructInstruction(); + insn.setReceiver(var(receiver)); + insn.setType(type); + add(insn); + } + + @Override + public void getField(VariableReader receiver, VariableReader instance, FieldReference field, + ValueType fieldType) { + GetFieldInstruction insn = new GetFieldInstruction(); + insn.setField(field); + insn.setFieldType(fieldType); + insn.setInstance(var(instance)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void putField(VariableReader instance, FieldReference field, VariableReader value, + ValueType fieldType) { + PutFieldInstruction insn = new PutFieldInstruction(); + insn.setField(field); + insn.setFieldType(fieldType); + insn.setInstance(var(instance)); + insn.setValue(var(value)); + add(insn); + } + + @Override + public void arrayLength(VariableReader receiver, VariableReader array) { + ArrayLengthInstruction insn = new ArrayLengthInstruction(); + insn.setArray(var(array)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void cloneArray(VariableReader receiver, VariableReader array) { + CloneArrayInstruction insn = new CloneArrayInstruction(); + insn.setArray(var(array)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { + int arrayIndex = variableMapping[array.getIndex()]; + if (capturedValues[arrayIndex] != null) { + return; + } + + UnwrapArrayInstruction insn = new UnwrapArrayInstruction(elementType); + insn.setArray(var(array)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void getElement(VariableReader receiver, VariableReader array, VariableReader index) { + int arrayIndex = variableMapping[array.getIndex()]; + + ArrayElement elem = arrayElements[receiver.getIndex()]; + if (elem != null && capturedValues[arrayIndex] != null) { + AssignInstruction insn = new AssignInstruction(); + insn.setAssignee(var(receiver)); + insn.setReceiver(program.variableAt(variableOffset + receiver.getIndex())); + add(insn); + return; + } + + GetElementInstruction insn = new GetElementInstruction(); + insn.setArray(var(array)); + insn.setIndex(var(index)); + insn.setReceiver(var(receiver)); + add(insn); + } + + @Override + public void putElement(VariableReader array, VariableReader index, VariableReader value) { + PutElementInstruction insn = new PutElementInstruction(); + insn.setArray(var(array)); + insn.setIndex(var(index)); + insn.setValue(var(value)); + add(insn); + } + + @Override + public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { + if (type == InvocationType.VIRTUAL && instance != null) { + if (method.getClassName().equals(Value.class.getName())) { + if (method.getName().equals("get")) { + AssignInstruction insn = new AssignInstruction(); + insn.setReceiver(var(receiver)); + insn.setAssignee(var(instance)); + add(insn); + return; + } else { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can't call method {{m0}} in runtime domain", method); + } + } else if (method.getClassName().equals(ReflectField.class.getName())) { + if (replaceFieldGetSet(receiver, instance, method, arguments)) { + return; + } + } else if (method.getClassName().equals(ReflectMethod.class.getName())) { + if (replaceMethodInvocation(receiver, instance, method, arguments)) { + return; + } + } else if (method.getClassName().equals(ReflectClass.class.getName())) { + if (replaceClassInvocation(receiver, instance, method, arguments)) { + return; + } + } + } + InvokeInstruction insn = new InvokeInstruction(); + insn.setInstance(var(instance)); + insn.setReceiver(var(receiver)); + insn.setMethod(method); + insn.setType(type); + insn.getArguments().addAll(arguments.stream().map(this::var).collect(Collectors.toList())); + add(insn); + } + + private boolean replaceFieldGetSet(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments) { + int instanceIndex = variableMapping[instance.getIndex()]; + if (capturedValues[instanceIndex] == null) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), "Can call {{m0}}" + + " method only on a reflected field captured by lambda from outer context", method); + return false; + } + + Object value = capturedValues[instanceIndex].obj; + if (!(value instanceof ReflectFieldImpl)) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Wrong call to {{m0}} method ", method); + return false; + } + + ReflectFieldImpl field = (ReflectFieldImpl) value; + switch (method.getName()) { + case "get": { + Variable var = program.createVariable(); + GetFieldInstruction insn = new GetFieldInstruction(); + insn.setInstance(!field.field.hasModifier(ElementModifier.STATIC) ? var(arguments.get(0)) : null); + insn.setReceiver(var); + insn.setField(field.getBackingField().getReference()); + insn.setFieldType(field.getBackingField().getType()); + add(insn); + + var = box(var, field.getBackingField().getType()); + + AssignInstruction assign = new AssignInstruction(); + assign.setAssignee(var); + assign.setReceiver(var(receiver)); + add(assign); + + return true; + } + case "set": { + PutFieldInstruction insn = new PutFieldInstruction(); + insn.setInstance(!field.field.hasModifier(ElementModifier.STATIC) ? var(arguments.get(0)) : null); + insn.setValue(unbox(var(arguments.get(1)), field.getBackingField().getType())); + insn.setField(field.getBackingField().getReference()); + insn.setFieldType(field.getBackingField().getType()); + add(insn); + return true; + } + default: + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), "Can only " + + "call {{m0}} method from runtime domain", method); + return false; + } + } + + private boolean replaceMethodInvocation(VariableReader receiver, VariableReader instance, + MethodReference method, List arguments) { + int instanceIndex = variableMapping[instance.getIndex()]; + if (capturedValues[instanceIndex] == null) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can call {{m0}} method only on a reflected field captured by lambda from outer context", + method); + return false; + } + + Object value = capturedValues[instanceIndex].obj; + if (!(value instanceof ReflectMethodImpl)) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Wrong call to {{m0}} method ", method); + return false; + } + + ReflectMethodImpl reflectMethod = (ReflectMethodImpl) value; + switch (method.getName()) { + case "invoke": { + InvokeInstruction insn = new InvokeInstruction(); + insn.setInstance(!Modifier.isStatic(reflectMethod.getModifiers()) ? var(arguments.get(0)) : null); + insn.setType(Modifier.isStatic(reflectMethod.getModifiers()) ? InvocationType.SPECIAL + : InvocationType.VIRTUAL); + insn.setMethod(reflectMethod.method.getReference()); + emitArguments(var(arguments.get(1)), reflectMethod, insn.getArguments()); + add(insn); + + if (receiver != null) { + if (reflectMethod.method.getResultType() == ValueType.VOID) { + NullConstantInstruction nullInsn = new NullConstantInstruction(); + nullInsn.setReceiver(var(receiver)); + add(nullInsn); + } else { + Variable var = program.createVariable(); + insn.setReceiver(var); + var = box(var, reflectMethod.method.getResultType()); + + AssignInstruction assign = new AssignInstruction(); + assign.setAssignee(var); + assign.setReceiver(var(receiver)); + add(assign); + } + } + + return true; + } + case "construct": { + ConstructInstruction constructInsn = new ConstructInstruction(); + constructInsn.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + constructInsn.setType(reflectMethod.method.getOwnerName()); + add(constructInsn); + + InvokeInstruction insn = new InvokeInstruction(); + insn.setInstance(constructInsn.getReceiver()); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(reflectMethod.method.getReference()); + emitArguments(var(arguments.get(0)), reflectMethod, insn.getArguments()); + add(insn); + + return true; + } + default: + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can only call {{m0}} method from runtime domain", method); + return false; + } + } + + private boolean replaceClassInvocation(VariableReader receiver, VariableReader instance, + MethodReference method, List arguments) { + int instanceIndex = variableMapping[instance.getIndex()]; + if (capturedValues[instanceIndex] == null) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can call {{m0}} method only on a reflected class captured by lambda from outer context", + method); + return false; + } + + Object value = capturedValues[instanceIndex].obj; + if (!(value instanceof ReflectClassImpl)) { + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Wrong call to {{m0}} method ", method); + return false; + } + + ReflectClassImpl reflectClass = (ReflectClassImpl) value; + switch (method.getName()) { + case "isInstance": { + IsInstanceInstruction insn = new IsInstanceInstruction(); + insn.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + insn.setValue(var(arguments.get(0))); + insn.setType(reflectClass.type); + add(insn); + return true; + } + case "cast": { + CastInstruction insn = new CastInstruction(); + insn.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + insn.setValue(var(arguments.get(0))); + insn.setTargetType(reflectClass.type); + add(insn); + return true; + } + case "asJavaClass": { + ClassConstantInstruction insn = new ClassConstantInstruction(); + insn.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + insn.setConstant(reflectClass.type); + add(insn); + return true; + } + case "createArray": { + ConstructArrayInstruction insn = new ConstructArrayInstruction(); + insn.setItemType(reflectClass.type); + insn.setSize(var(arguments.get(0))); + insn.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + add(insn); + return true; + } + case "getArrayLength": { + ArrayLengthInstruction insn = new ArrayLengthInstruction(); + insn.setArray(unwrapArray(reflectClass.type, var(arguments.get(0)))); + insn.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + add(insn); + return true; + } + case "getArrayElement": { + GetElementInstruction insn = new GetElementInstruction(); + insn.setArray(unwrapArray(reflectClass.type, var(arguments.get(0)))); + insn.setIndex(var(arguments.get(1))); + insn.setReceiver(program.createVariable()); + add(insn); + + AssignInstruction assign = new AssignInstruction(); + assign.setAssignee(box(insn.getReceiver(), ((ValueType.Array) reflectClass.type).getItemType())); + assign.setReceiver(receiver != null ? var(receiver) : program.createVariable()); + add(assign); + + return true; + } + default: + diagnostics.error(new CallLocation(MetaprogrammingImpl.templateMethod, location), + "Can only call {{m0}} method from runtime domain", method); + return false; + } + } + + private void emitArguments(Variable argumentsVar, ReflectMethodImpl reflectMethod, + List arguments) { + UnwrapArrayInstruction unwrapInsn = new UnwrapArrayInstruction(ArrayElementType.OBJECT); + unwrapInsn.setArray(argumentsVar); + unwrapInsn.setReceiver(program.createVariable()); + add(unwrapInsn); + argumentsVar = unwrapInsn.getReceiver(); + + for (int i = 0; i < reflectMethod.getParameterCount(); ++i) { + IntegerConstantInstruction indexInsn = new IntegerConstantInstruction(); + indexInsn.setConstant(i); + indexInsn.setReceiver(program.createVariable()); + add(indexInsn); + + GetElementInstruction extractArgInsn = new GetElementInstruction(); + extractArgInsn.setArray(argumentsVar); + extractArgInsn.setIndex(indexInsn.getReceiver()); + extractArgInsn.setReceiver(program.createVariable()); + add(extractArgInsn); + + arguments.add(unbox(extractArgInsn.getReceiver(), + reflectMethod.method.parameterType(i))); + } + } + + private Variable unwrapArray(ValueType type, Variable array) { + CastInstruction cast = new CastInstruction(); + cast.setTargetType(ValueType.arrayOf(type)); + cast.setValue(array); + cast.setReceiver(program.createVariable()); + add(cast); + + UnwrapArrayInstruction unwrap = new UnwrapArrayInstruction(asArrayType(type)); + unwrap.setArray(cast.getReceiver()); + unwrap.setReceiver(program.createVariable()); + add(unwrap); + + return unwrap.getReceiver(); + } + + private ArrayElementType asArrayType(ValueType type) { + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) type).getKind()) { + case BOOLEAN: + case BYTE: + return ArrayElementType.BYTE; + case SHORT: + return ArrayElementType.SHORT; + case CHARACTER: + return ArrayElementType.CHAR; + case INTEGER: + return ArrayElementType.INT; + case LONG: + return ArrayElementType.LONG; + case FLOAT: + return ArrayElementType.FLOAT; + case DOUBLE: + return ArrayElementType.DOUBLE; + } + } + return ArrayElementType.OBJECT; + } + + @Override + public void invokeDynamic(VariableReader receiver, VariableReader instance, MethodDescriptor method, + List arguments, MethodHandle bootstrapMethod, + List bootstrapArguments) { + InvokeDynamicInstruction insn = new InvokeDynamicInstruction(); + insn.setBootstrapMethod(bootstrapMethod); + insn.setInstance(var(instance)); + insn.setReceiver(var(receiver)); + insn.setMethod(method); + insn.getArguments().addAll(arguments.stream().map(this::var).collect(Collectors.toList())); + insn.getBootstrapArguments().addAll(bootstrapArguments); + add(insn); + } + + @Override + public void isInstance(VariableReader receiver, VariableReader value, ValueType type) { + IsInstanceInstruction insn = new IsInstanceInstruction(); + insn.setReceiver(var(receiver)); + insn.setValue(var(value)); + insn.setType(type); + add(insn); + } + + @Override + public void initClass(String className) { + InitClassInstruction insn = new InitClassInstruction(); + insn.setClassName(className); + add(insn); + } + + @Override + public void nullCheck(VariableReader receiver, VariableReader value) { + NullCheckInstruction insn = new NullCheckInstruction(); + insn.setReceiver(var(receiver)); + insn.setValue(var(value)); + add(insn); + } + + @Override + public void monitorEnter(VariableReader objectRef) { + MonitorEnterInstruction insn = new MonitorEnterInstruction(); + insn.setObjectRef(var(objectRef)); + add(insn); + } + + @Override + public void monitorExit(VariableReader objectRef) { + MonitorExitInstruction insn = new MonitorExitInstruction(); + insn.setObjectRef(var(objectRef)); + add(insn); + } + } +} diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/LazyValueImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/LazyValueImpl.java new file mode 100644 index 000000000..35392fb24 --- /dev/null +++ b/core/src/main/java/org/teavm/metaprogramming/impl/LazyValueImpl.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.metaprogramming.impl; + +import org.teavm.metaprogramming.LazyComputation; +import org.teavm.metaprogramming.Value; +import org.teavm.model.InstructionLocation; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +public class LazyValueImpl implements Value { + boolean evaluated; + VariableContext context; + LazyComputation computation; + ValueType type; + InstructionLocation forcedLocation; + + public LazyValueImpl(VariableContext context, LazyComputation computation, ValueType type, + InstructionLocation forcedLocation) { + this.context = context; + this.computation = computation; + this.type = type; + this.forcedLocation = forcedLocation; + } + + @Override + public T get() { + throw new IllegalStateException("Can only read this value in emitter domain"); + } +} diff --git a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java index 9cca79a31..6f292733a 100644 --- a/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java +++ b/core/src/main/java/org/teavm/metaprogramming/impl/MetaprogrammingImpl.java @@ -15,46 +15,151 @@ */ package org.teavm.metaprogramming.impl; +import org.teavm.dependency.DependencyAgent; import org.teavm.metaprogramming.Action; import org.teavm.metaprogramming.Computation; +import org.teavm.metaprogramming.Diagnostics; import org.teavm.metaprogramming.InvocationHandler; import org.teavm.metaprogramming.LazyComputation; import org.teavm.metaprogramming.ReflectClass; -import org.teavm.metaprogramming.Scope; +import org.teavm.metaprogramming.SourceLocation; import org.teavm.metaprogramming.Value; import org.teavm.metaprogramming.impl.reflect.ReflectClassImpl; import org.teavm.metaprogramming.impl.reflect.ReflectContext; +import org.teavm.metaprogramming.impl.reflect.ReflectFieldImpl; +import org.teavm.metaprogramming.impl.reflect.ReflectMethodImpl; +import org.teavm.model.CallLocation; +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; import org.teavm.model.ValueType; +import org.teavm.model.Variable; +import org.teavm.model.instructions.ExitInstruction; +import org.teavm.model.instructions.InvocationType; +import org.teavm.model.instructions.InvokeInstruction; public final class MetaprogrammingImpl { static ClassLoader classLoader; + static ClassReaderSource classSource; static ReflectContext reflectContext; + static DependencyAgent agent; + static VariableContext varContext; + static MethodReference templateMethod; + static CompositeMethodGenerator generator; + static ValueType returnType; private MetaprogrammingImpl() { } public static Value emit(Computation computation) { - unsupported(); - return null; + if (computation instanceof ValueImpl) { + @SuppressWarnings("unchecked") + ValueImpl valueImpl = (ValueImpl) computation; + Variable var = varContext.emitVariable(valueImpl, new CallLocation(templateMethod, generator.location)); + return new ValueImpl<>(var, varContext, valueImpl.type); + } else if (computation instanceof LazyValueImpl) { + LazyValueImpl valueImpl = (LazyValueImpl) computation; + Variable var = generator.lazy(valueImpl); + return var != null ? new ValueImpl<>(var, varContext, valueImpl.type) : null; + } else { + Fragment fragment = (Fragment) computation; + MethodReader method = classSource.resolve(fragment.method); + generator.addProgram(method.getProgram(), fragment.capturedValues); + return new ValueImpl<>(generator.getResultVar(), varContext, fragment.method.getReturnType()); + } } public static void emit(Action action) { - unsupported(); + Fragment fragment = (Fragment) action; + MethodReader method = classSource.resolve(fragment.method); + generator.addProgram(method.getProgram(), fragment.capturedValues); } public static Value lazyFragment(LazyComputation computation) { - unsupported(); - return null; + return lazyFragment(ValueType.object("java.lang.Object"), computation); } public static Value lazy(Computation computation) { - unsupported(); - return null; + Fragment fragment = (Fragment) computation; + return lazyFragment(fragment.method.getReturnType(), () -> emit(computation)); } - public static Scope currentScope() { - unsupported(); - return null; + private static Value lazyFragment(ValueType type, LazyComputation computation) { + return new LazyValueImpl<>(varContext, computation, type, generator.forcedLocation); + } + + public static void exit(Value value) { + if (value == null) { + returnValue(null); + return; + } + + if (value instanceof Fragment) { + Fragment fragment = (Fragment) value; + MethodReader method = classSource.resolve(fragment.method); + generator.addProgram(method.getProgram(), fragment.capturedValues); + generator.blockIndex = generator.returnBlockIndex; + + returnValue(unbox(generator.getResultVar())); + } else if (value instanceof ValueImpl) { + ValueImpl valueImpl = (ValueImpl) value; + returnValue(unbox(varContext.emitVariable(valueImpl, new CallLocation(templateMethod, + generator.location)))); + } else if (value instanceof LazyValueImpl) { + Variable var = generator.lazy((LazyValueImpl) value); + returnValue(unbox(var)); + } else { + throw new IllegalStateException("Unexpected computation type: " + value.getClass().getName()); + } + } + + static Variable unbox(Variable var) { + if (returnType instanceof ValueType.Primitive) { + switch (((ValueType.Primitive) returnType).getKind()) { + case BOOLEAN: + var = unbox(var, Boolean.class, boolean.class); + break; + case BYTE: + var = unbox(var, Byte.class, byte.class); + break; + case SHORT: + var = unbox(var, Short.class, short.class); + break; + case CHARACTER: + var = unbox(var, Character.class, char.class); + break; + case INTEGER: + var = unbox(var, Integer.class, int.class); + break; + case LONG: + var = unbox(var, Long.class, long.class); + break; + case FLOAT: + var = unbox(var, Float.class, float.class); + break; + case DOUBLE: + var = unbox(var, Double.class, double.class); + break; + } + } + return var; + } + + static Variable unbox(Variable var, Class boxed, Class primitive) { + InvokeInstruction insn = new InvokeInstruction(); + insn.setInstance(var); + insn.setType(InvocationType.VIRTUAL); + insn.setMethod(new MethodReference(boxed, primitive.getName() + "Value", primitive)); + var = generator.program.createVariable(); + insn.setReceiver(var); + generator.add(insn); + return var; + } + + public static void exit() { + exit(null); } public static void location(String fileName, int lineNumber) { @@ -88,8 +193,7 @@ public final class MetaprogrammingImpl { } public static ReflectClass createClass(byte[] bytecode) { - unsupported(); - return null; + return findClass(agent.submitClassFile(bytecode).replace('/', '.')); } public static Value proxy(Class type, InvocationHandler handler) { @@ -101,8 +205,58 @@ public final class MetaprogrammingImpl { return null; } + private static void returnValue(Variable var) { + ExitInstruction insn = new ExitInstruction(); + insn.setValueToReturn(var); + generator.add(insn); + } + + public static Diagnostics getDiagnostics() { + return diagnostics; + } + + public void submitClass(ClassHolder cls) { + agent.submitClass(cls); + } + private static void unsupported() { throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time " + "environment"); } + + private static Diagnostics diagnostics = new Diagnostics() { + @Override + public void error(SourceLocation location, String error, Object... params) { + convertParams(params); + agent.getDiagnostics().error(convertLocation(location), error, params); + } + + @Override + public void warning(SourceLocation location, String error, Object... params) { + convertParams(params); + agent.getDiagnostics().warning(convertLocation(location), error, params); + } + + private void convertParams(Object[] params) { + for (int i = 0; i < params.length; ++i) { + if (params[i] instanceof ReflectMethodImpl) { + params[i] = ((ReflectMethodImpl) params[i]).method.getReference(); + } else if (params[i] instanceof ReflectClassImpl) { + params[i] = ((ReflectClassImpl) params[i]).type; + } else if (params[i] instanceof ReflectFieldImpl) { + params[i] = ((ReflectFieldImpl) params[i]).field.getReference(); + } else if (params[i] instanceof Class) { + params[i] = ValueType.parse((Class) params[i]); + } + } + } + + private CallLocation convertLocation(SourceLocation location) { + MethodReader method = ((ReflectMethodImpl) location.getMethod()).method; + return location.getFileName() != null + ? new CallLocation(method.getReference(), + new InstructionLocation(location.getFileName(), location.getLineNumber())) + : new CallLocation(method.getReference()); + } + }; } diff --git a/metaprogramming-api/pom.xml b/metaprogramming-api/pom.xml index 17925f49c..122af3be8 100644 --- a/metaprogramming-api/pom.xml +++ b/metaprogramming-api/pom.xml @@ -27,9 +27,19 @@ teavm-metaprogramming-api + bundle + TeaVM metaprogramming API Declaration of interfaces and annotations for TeaVM metaprogramming + + + junit + junit + test + + + @@ -47,6 +57,17 @@ org.apache.maven.plugins maven-javadoc-plugin + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + org.apache.felix maven-bundle-plugin diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java index 09bbd1dd1..87074320a 100644 --- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java +++ b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Metaprogramming.java @@ -39,9 +39,12 @@ public final class Metaprogramming { return null; } - public static Scope currentScope() { + public static void exit(Value returnValue) { + unsupported(); + } + + public static void exit() { unsupported(); - return null; } public static void location(String fileName, int lineNumber) { @@ -86,6 +89,11 @@ public final class Metaprogramming { return null; } + public static Diagnostics getDiagnostics() { + unsupported(); + return null; + } + private static void unsupported() { throw new UnsupportedOperationException("This operation is only supported from TeaVM compile-time " + "environment"); diff --git a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Scope.java b/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Scope.java deleted file mode 100644 index 192f4a96d..000000000 --- a/metaprogramming-api/src/main/java/org/teavm/metaprogramming/Scope.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2016 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.metaprogramming; - -public interface Scope { - void exit(Object returnValue); -} diff --git a/metaprogramming-api/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java b/metaprogramming-api/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java new file mode 100644 index 000000000..e769e90ea --- /dev/null +++ b/metaprogramming-api/src/test/java/org/teavm/metaprogramming/test/MetaprogrammingTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.metaprogramming.test; + +import static org.junit.Assert.assertEquals; +import static org.teavm.metaprogramming.Metaprogramming.exit; +import org.junit.Test; +import org.teavm.metaprogramming.CompileTime; +import org.teavm.metaprogramming.Meta; +import org.teavm.metaprogramming.ReflectClass; +import org.teavm.metaprogramming.Value; + +@CompileTime +public class MetaprogrammingTest { + @Test + public void works() { + assertEquals("java.lang.Object".length() + 2, classNameLength(Object.class, 2)); + assertEquals("java.lang.Integer".length() + 3, classNameLength(Integer.valueOf(5).getClass(), 3)); + } + + @Meta + static native int classNameLength(Class cls, int add); + static void classNameLength(ReflectClass cls, Value add) { + int length = cls.getName().length(); + exit(() -> length + add.get()); + } +} diff --git a/metaprogramming-api/teavm-metaprogramming-api.iml b/metaprogramming-api/teavm-metaprogramming-api.iml index dae5c19b8..48e1bc5e6 100644 --- a/metaprogramming-api/teavm-metaprogramming-api.iml +++ b/metaprogramming-api/teavm-metaprogramming-api.iml @@ -1,13 +1,28 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/pom.xml b/tests/pom.xml index 5ae754df0..911cd604c 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -55,6 +55,13 @@ tests compile + + org.teavm + teavm-metaprogramming-api + ${project.version} + tests + compile + junit junit diff --git a/tests/teavm-tests.iml b/tests/teavm-tests.iml index caeed3fc3..41b767a04 100644 --- a/tests/teavm-tests.iml +++ b/tests/teavm-tests.iml @@ -25,6 +25,7 @@ +