diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java index d6d99d2ef6..b03fa6a114 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitCompiler.java @@ -262,7 +262,8 @@ public class JitCompiler { oum.dumpResult(); } - JitCodeGenerator gen = new JitCodeGenerator(lookup, context, cfm, dfm, vsm, tm, am, oum); + JitCodeGenerator gen = + new JitCodeGenerator<>(lookup, context, cfm, dfm, vsm, tm, am, oum); return gen.load(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitJvmTypeUtils.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitJvmTypeUtils.java index 29e1a02825..4e7912a655 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitJvmTypeUtils.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitJvmTypeUtils.java @@ -116,4 +116,39 @@ public enum JitJvmTypeUtils { default -> throw new UnsupportedOperationException(); }; } + + /** + * Compute the erasure of a type variable with the given upper bounds + * + * @param bounds the upper bounds + * @return the erasure + */ + public static Class eraseBounds(Type[] bounds) { + if (bounds.length == 0) { + return Object.class; + } + return erase(bounds[0]); + } + + /** + * Compute the erasure of the given type + *

+ * For a class, this is just the same class. For an array, it is the array of the erasure of the + * element type. For a parameterized type, we take the erasure of the raw type, which should in + * turn be a class. For a wildcard, we take the erasure of its first upper bound. + * + * @param type the type + * @return the erasure + */ + public static Class erase(Type type) { + return switch (type) { + case Class cls -> cls; + case GenericArrayType arr -> Array.newInstance(erase(arr.getGenericComponentType()), 0) + .getClass(); + case ParameterizedType pt -> erase(pt.getRawType()); + case TypeVariable tv -> eraseBounds(tv.getBounds()); + case WildcardType wt -> eraseBounds(wt.getUpperBounds()); + default -> throw new UnsupportedOperationException(); + }; + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java index 59a1ab9757..41a66e4264 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/JitPcodeEmulator.java @@ -26,13 +26,12 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.objectweb.asm.MethodTooLargeException; import ghidra.pcode.emu.*; -import ghidra.pcode.emu.PcodeMachine.AccessKind; import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.analysis.JitDataFlowModel; import ghidra.pcode.emu.jit.analysis.JitDataFlowUseropLibrary; import ghidra.pcode.emu.jit.decode.JitPassageDecoder; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPointPrototype; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/AlignedMpIntHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/AlignedMpIntHandler.java new file mode 100644 index 0000000000..b043de6f0b --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/AlignedMpIntHandler.java @@ -0,0 +1,470 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.*; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler used for a varnode requiring allocation of multiple integers, where those integers + * correspond exactly to the variable's legs. + *

+ * In this case, we can usually give the operators direct access to the underlying mp-int operand. + * We do need to be careful that we don't unintentionally permit the operator to use the variable's + * storage for intermediate values. Thus, we have some provision for saying each leg is read-only, + * which will cause attempts to store into them to instead generate a writable temporary local. Such + * intermediate results will get written only by a call to + * {@link #genStoreFromOpnd(Emitter, JitCodeGenerator, Opnd, Ext, Scope)}. + * + * @param legs the list of legs in little-endian order + * @param type the type of the full multi-precision integer variable + * @param vn the complete varnode accessible to this handler + * @param opnd the (writable) list of local operands (in LE order) + * @param roOpnd the read-only version of {@code opnd}. + */ +public record AlignedMpIntHandler(List> legs, MpIntJitType type, + Varnode vn, MpIntLocalOpnd opnd, MpIntLocalOpnd roOpnd) implements VarHandler { + + /** + * Static utility for the {@link #AlignedMpIntHandler(List, MpIntJitType, Varnode)} constructor + * + * @param legs the list o legs in little-endian order + * @param type the type of the full mp-int variable + * @param vn the complete varnode + * @return the writable operand + */ + private static MpIntLocalOpnd createOpnd(List> legs, + MpIntJitType type, Varnode vn) { + List> opndLegs = new ArrayList<>(); + for (JvmLocal leg : legs) { + opndLegs.add(leg.opnd()); + } + return MpIntLocalOpnd.of(type, VarHandler.nameVn(vn), opndLegs); + } + + /** + * Static utility for the {@link #AlignedMpIntHandler(List, MpIntJitType, Varnode)} constructor + * + * @param legs the list o legs in little-endian order + * @param type the type of the full mp-int variable + * @param vn the complete varnode + * @return the read-only operand + */ + private static MpIntLocalOpnd createRoOpnd(List> legs, + MpIntJitType type, Varnode vn) { + List> opndLegs = new ArrayList<>(); + for (JvmLocal leg : legs) { + opndLegs.add(SimpleOpnd.ofIntReadOnly(leg.type(), leg.local())); + } + return MpIntLocalOpnd.of(type, VarHandler.nameVn(vn) + "_ro", opndLegs); + } + + /** + * Preferred constructor + * + * @param legs the list o legs in little-endian order + * @param type the type of the full muti-precision integer variable + * @param vn the complete varnode accessible to this handler + */ + public AlignedMpIntHandler(List> legs, MpIntJitType type, + Varnode vn) { + this(legs, type, vn, createOpnd(legs, type, vn), createRoOpnd(legs, type, vn)); + } + + @Override + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT to, Ext ext) { + return switch (to) { + case IntJitType t -> em + .emit(legs.get(0)::genLoadToStack, gen, to, ext); + case LongJitType t when legs.size() == 1 -> em + .emit(legs.get(0)::genLoadToStack, gen, to, ext); + case LongJitType t -> em + .emit(legs.get(0)::genLoadToStack, gen, LongJitType.I8, Ext.ZERO) + .emit(legs.get(1)::genLoadToStack, gen, LongJitType.I8, Ext.ZERO) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lshl) + .emit(Op::lor) + .emit(Opnd::convert, LongJitType.I8, to, ext); + default -> throw new AssertionError(); + }; + } + + @Override + public OpndEm genLoadToOpnd(Emitter em, + JitCodeGenerator gen, MpIntJitType to, Ext ext, Scope scope) { + return MpIntToMpInt.INSTANCE.convertOpndToOpnd(em, roOpnd, to, ext, scope); + } + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + IntJitType toType = type.legTypesLE().get(leg); + if (leg >= legs.size()) { + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> em + .emit(legs.getLast()::genLoadToStack, gen, toType, ext) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + }; + } + return em + .emit(legs.get(leg)::genLoadToStack, gen, toType, ext); + } + + @Override + public Emitter>> genLoadToArray(Emitter em, + JitCodeGenerator gen, MpIntJitType to, Ext ext, Scope scope, int slack) { + return MpIntToMpInt.INSTANCE.convertOpndToArray(em, opnd, to, ext, scope, slack); + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + var result = em + .emit(legs.get(0)::genLoadToStack, gen, IntJitType.I4, Ext.ZERO); + for (JvmLocal leg : legs) { + result = result + .emit(leg::genLoadToStack, gen, IntJitType.I4, Ext.ZERO) + .emit(Op::ior); + } + return result.emit(Opnd::intToBool); + } + + /** + * Emit bytecode to store a JVM int from the stack into the given local + * + * @param the tail of the incoming stack + * @param the incoming stack, having the int on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the int on the stack + * @param local the local to receive the value + * @param ext the kind of extension to apply + * @param scope a scope for generating local storage + * @return the emitter typed with the resulting stack, i.e., having popped the int + */ + protected > Emitter doGenStoreInt(Emitter em, + JitCodeGenerator gen, IntJitType type, JvmLocal local, Ext ext, + Scope scope) { + return em + .emit(local::genStoreFromStack, gen, type, ext, scope); + } + + /** + * Emit bytecode to compute the sign of the int on the stack, and store that int into a given + * local. + *

+ * The int is copied and stored into the given local. Then, the sign of the int is computed and + * remains on the stack. Signed extension is assumed. + * + * @param the tail of the incoming stack + * @param the incoming stack, having the int on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the int on the stack. Note that this type determines the + * position of the sign bit. + * @param local the local to receive the value + * @param scope a scope for generating local storage + * @return the emitter typed with the resulting stack, i.e., having popped the int and pushed + * the sign + */ + protected > Emitter> + doGenStoreIntAndSign(Emitter em, JitCodeGenerator gen, IntJitType type, + JvmLocal local, Scope scope) { + return em + .emit(Op::dup) + .emit(this::doGenStoreInt, gen, type, local, Ext.SIGN, scope) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + + /** + * Emit bytecode to store a JVM long from the stack into two given locals + * + * @param the tail of the incoming stack + * @param the incoming stack, having the long on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the int on the stack + * @param lower the local to receive the lower 32 bits of the value + * @param upper the local to receive the upper 32 bits of the value + * @param ext the kind of extension to apply + * @param scope a scope for generating local storage + * @return the emitter typed with the resulting stack, i.e., having popped the long + */ + protected > Emitter doGenStoreLong( + Emitter em, JitCodeGenerator gen, LongJitType type, + JvmLocal lower, JvmLocal upper, Ext ext, + Scope scope) { + return em + .emit(Op::dup2__2) + .emit(lower::genStoreFromStack, gen, type, ext, scope) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr) + .emit(upper::genStoreFromStack, gen, + LongJitType.forSize(type.size() - Integer.BYTES), ext, scope); + } + + /** + * Emit bytecode to compute the sign of the long on the stack, and store that long into two + * given locals. + *

+ * The long is copied and stored into the given local. Then, the sign of the long is computed + * and remains on the stack as an int. Signed extension is assumed. + * + * @param the tail of the incoming stack + * @param the incoming stack, having the long on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the long on the stack. Note that this type determines the + * position of the sign bit. + * @param lower the local to receive the lower 32 bits of the value + * @param upper the local to receive the upper 32 bits of the value + * @param scope a scope for generating local storage + * @return the emitter typed with the resulting stack, i.e., having popped the long and pushed + * the sign + */ + protected > Emitter> + doGenStoreLongAndSign(Emitter em, JitCodeGenerator gen, LongJitType type, + JvmLocal lower, JvmLocal upper, + Scope scope) { + return em + .emit(Op::dup2__2) + .emit(lower::genStoreFromStack, gen, type, Ext.SIGN, scope) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr) + .emit(Opnd::convert, type, IntJitType.I4, Ext.SIGN) + .emit(Op::dup) + .emit(upper::genStoreFromStack, gen, IntJitType.I4, Ext.SIGN, scope) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + + /** + * Emit bytecode to zero fill the given locals + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param locals the locals to zero fill + * @param scope a scope for generating local storage + * @return the emitter typed with the incoming stack + */ + protected Emitter doGenZeroFill(Emitter em, JitCodeGenerator gen, + List> locals, Scope scope) { + for (JvmLocal local : locals) { + em = em + .emit(Op::ldc__i, 0) + .emit(local::genStoreFromStack, gen, IntJitType.I4, Ext.ZERO, scope); + } + return em; + } + + /** + * Emit bytecode to sign fill the given locals + * + * @param the tail of the incoming stack + * @param the incoming stack having the sign int on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param locals the locals to sign fill + * @param scope a scope for generating local storage + * @return the emitter typed with the resulting stack, i.e., having popped the sign + */ + protected > Emitter doGenSignFill(Emitter em, + JitCodeGenerator gen, List> locals, Scope scope) { + for (JvmLocal local : locals.subList(0, locals.size() - 1)) { + em = em + .emit(Op::dup) + .emit(local::genStoreFromStack, gen, IntJitType.I4, Ext.SIGN, scope); + } + JvmLocal last = locals.getLast(); + return em + .emit(last::genStoreFromStack, gen, IntJitType.I4, Ext.SIGN, scope); + } + + @Override + public , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT from, Ext ext, Scope scope) { + return switch (from) { + case IntJitType t when legs.size() == 1 -> em + .emit(Opnd::castStack1, from, t) + .emit(this::doGenStoreInt, gen, t, legs.get(0), ext, scope); + case IntJitType t -> switch (ext) { + case ZERO -> em + .emit(Opnd::castStack1, from, t) + .emit(this::doGenStoreInt, gen, t, legs.get(0), ext, scope) + .emit(this::doGenZeroFill, gen, legs.subList(1, legs.size()), scope); + case SIGN -> em + .emit(Opnd::castStack1, from, t) + .emit(this::doGenStoreIntAndSign, gen, t, legs.get(0), scope) + .emit(this::doGenSignFill, gen, legs.subList(1, legs.size()), scope); + }; + case LongJitType t when legs.size() == 1 -> em + .emit(legs.get(0)::genStoreFromStack, gen, from, ext, scope); + case LongJitType t when legs.size() == 2 -> em + .emit(Opnd::castStack1, from, t) + .emit(this::doGenStoreLong, gen, t, legs.get(0), legs.get(1), ext, scope); + case LongJitType t -> switch (ext) { + case ZERO -> em + .emit(Opnd::castStack1, from, t) + .emit(this::doGenStoreLong, gen, t, legs.get(0), legs.get(1), ext, scope) + .emit(this::doGenZeroFill, gen, legs.subList(2, legs.size()), scope); + case SIGN -> em + .emit(Opnd::castStack1, from, t) + .emit(this::doGenStoreLongAndSign, gen, t, legs.get(0), legs.get(1), scope) + .emit(this::doGenSignFill, gen, legs.subList(2, legs.size()), scope); + }; + default -> throw new AssertionError(); + }; + } + + /** + * Emit bytecode to extend the value stored in our legs. + * + * @param the tail of the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param defLegs the number of legs having the input value + * @param legsOut the number of legs to receive the output value. If this is less than or equal + * to {@code defLegs}, there is no extension to apply, so no code is emitted. + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack + */ + protected Emitter genExt(Emitter em, JitCodeGenerator gen, + int defLegs, int legsOut, Ext ext, Scope scope) { + if (legsOut <= defLegs) { + return em; + } + return switch (ext) { + case ZERO -> doGenZeroFill(em, gen, legs.subList(defLegs, legsOut), scope); + case SIGN -> em + .emit(Op::iload, legs.get(defLegs - 1).local()) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) // Signed + .emit(this::doGenSignFill, gen, legs.subList(defLegs, legsOut), scope); + }; + } + + @Override + public Emitter genStoreFromOpnd(Emitter em, JitCodeGenerator gen, + Opnd from, Ext ext, Scope scope) { + List> fromLegs = from.type().castLegsLE(from); + List> toLegs = opnd.type().castLegsLE(opnd); + int legsIn = fromLegs.size(); + int legsOut = toLegs.size(); + int defLegs = Integer.min(legsIn, legsOut); + + for (int i = 0; i < defLegs; i++) { + SimpleOpnd fromLeg = fromLegs.get(i); + SimpleOpnd toLeg = toLegs.get(i); + em = em + .emit(fromLeg::read) + .emit(Opnd::convertIntToInt, fromLeg.type(), toLeg.type(), ext) + .emit(toLeg::writeDirect); + } + return genExt(em, gen, defLegs, legsOut, ext, scope); + } + + @Override + public >> Emitter genStoreFromArray( + Emitter em, JitCodeGenerator gen, MpIntJitType from, Ext ext, Scope scope) { + List fromLegTypes = from.legTypesLE(); + List> toLegs = opnd.type().castLegsLE(opnd); + int legsIn = fromLegTypes.size(); + int legsOut = toLegs.size(); + int defLegs = Integer.min(legsIn, legsOut); + + for (int i = 0; i < defLegs - 1; i++) { + IntJitType fromLegType = fromLegTypes.get(i); + SimpleOpnd toLeg = toLegs.get(i); + em = em + .emit(Op::dup) + .emit(Op::ldc__i, i) + .emit(Op::iaload) + .emit(Opnd::convertIntToInt, fromLegType, toLeg.type(), ext) + .emit(toLeg::writeDirect); + } + IntJitType fromLegType = fromLegTypes.get(defLegs - 1); + SimpleOpnd toLeg = toLegs.get(defLegs - 1); + return em + .emit(Op::ldc__i, defLegs - 1) + .emit(Op::iaload) + .emit(Opnd::convertIntToInt, fromLegType, toLeg.type(), ext) + .emit(toLeg::writeDirect) + .emit(this::genExt, gen, defLegs, legsOut, ext, scope); + } + + /** + * A utility for implementing {@link #subpiece(Endian, int, int)}, also used by + * {@link ShiftedMpIntHandler}. + * + * @param endian the endianness of the emulation target. Technically, this is only used in the + * naming of any temporary local variables. + * @param vn the varnode of the original handler + * @param parts the parts (perhaps aligned to the legs) of the original handler + * @param curShift if shifted, the number of bytes. If aligned, 0. + * @param addShift the offset (in bytes) of the subpiece, i.e., additional shift + * @param maxByteSize the size in bytes of the output operand, which indicate the maximum size + * of the resulting handler's varnode. + * @return the resulting handler + */ + static VarHandler subHandler(Endian endian, Varnode vn, List> parts, + int curShift, int addShift, int maxByteSize) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(endian, vn, addShift, maxByteSize); + int totalShift = curShift + addShift; + int firstPart = totalShift / Integer.BYTES; + int lastPartExcl = (totalShift + subVn.getSize() + Integer.BYTES - 1) / Integer.BYTES; + List> subParts = parts.subList(firstPart, lastPartExcl); + int subShift = totalShift % Integer.BYTES; + + if (subParts.size() == 1) { + IntJitType subType = IntJitType.forSize(subVn.getSize()); + if (subShift == 0) { + return new IntVarAlloc(subParts.getFirst(), subType); + } + return new IntInIntHandler(subParts.getFirst(), subType, subVn, subShift); + } + MpIntJitType subType = MpIntJitType.forSize(subVn.getSize()); + if (subShift == 0) { + return new AlignedMpIntHandler(subParts, subType, subVn); + } + return new ShiftedMpIntHandler(subParts, subType, subVn, subShift); + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + return subHandler(endian, vn, legs, 0, byteOffset, maxByteSize); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/DoubleVarAlloc.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/DoubleVarAlloc.java new file mode 100644 index 0000000000..b515de4c1d --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/DoubleVarAlloc.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.MpToStackConv; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.program.model.lang.Endian; + +/** + * The handler for a p-code variable allocated in one JVM {@code double}. + * + * @param local the JVM local + * @param type the p-code type + */ +public record DoubleVarAlloc(JvmLocal local, DoubleJitType type) + implements SimpleVarHandler { + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + return genLoadLegToStackC2(em, gen, type, leg, ext); + } + + @Override + public MpToStackConv getConvToStack() { + throw new AssertionError(); + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + return em + .emit(this::genLoadToStack, gen, type, Ext.ZERO) + .emit(Op::ldc__d, 0.0) + .emit(Op::dcmpl); // So long as lsb is set, it's true + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + throw new AssertionError("Who's subpiecing a double?"); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/FloatVarAlloc.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/FloatVarAlloc.java new file mode 100644 index 0000000000..837f1723e3 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/FloatVarAlloc.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.MpToStackConv; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.program.model.lang.Endian; + +/** + * The handler for a p-code variable allocated in one JVM {@code float}. + * + * @param local the JVM local + * @param type the p-code type + */ +public record FloatVarAlloc(JvmLocal local, FloatJitType type) + implements SimpleVarHandler { + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + return genLoadLegToStackC1(em, gen, type, leg, ext); + } + + @Override + public MpToStackConv getConvToStack() { + throw new AssertionError(); + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + return em + .emit(this::genLoadToStack, gen, type, Ext.ZERO) + .emit(Op::ldc__f, 0.0f) + .emit(Op::fcmpl); // So long as lsb is set, it's true + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + throw new AssertionError("Who's subpiecing a float?"); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntInIntHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntInIntHandler.java new file mode 100644 index 0000000000..c4d4370db4 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntInIntHandler.java @@ -0,0 +1,125 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler for an {@link IntJitType int} p-code variable stored in part of a JVM {@code int}. + * + * @param local see {@link #local()} + * @param type see {@link #type()} + * @param vn see {@link #vn()} (wrt. the sub variable) + * @param byteShift see {@link #byteShift()} + */ +public record IntInIntHandler(JvmLocal local, IntJitType type, Varnode vn, + int byteShift) implements SubVarHandler { + + @SuppressWarnings("javadoc") + public IntInIntHandler { + assertShiftFits(byteShift, type, local); + } + + @Override + public MpToStackConv getConvToSub() { + return MpIntToInt.INSTANCE; + } + + @Override + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + return em + .emit(Op::iload, local.local()) + .emit(Op::ldc__i, bitShift()) + .emit(Op::iushr) + .emit(Opnd::convert, this.type, type, ext); + } + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + if (leg == 0) { + return genLoadToStack(em, gen, type.legTypesLE().get(leg), ext); + } + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> { + int msb = (this.type.size() + byteShift) * Byte.SIZE; + if (msb == Integer.SIZE) { + yield em + .emit(Op::iload, local.local()) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + yield em + .emit(Op::iload, local.local()) + .emit(Op::ldc__i, Integer.SIZE - msb) + .emit(Op::ishl) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + }; + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + int mask = intMask(); + return em + .emit(Op::iload, local.local()) + .emit(Op::ldc__i, mask) + .emit(Op::iand) + .emit(Opnd::intToBool); + } + + @Override + public , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT type, Ext ext, Scope scope) { + int mask = intMask(); + return em + .emit(Opnd::convert, type, local.type(), ext) + .emit(Op::ldc__i, bitShift()) + .emit(Op::ishl) + .emit(Op::ldc__i, mask) + .emit(Op::iand) + .emit(Op::iload, local.local()) + .emit(Op::ldc__i, ~mask) + .emit(Op::iand) + .emit(Op::ior) + .emit(Op::istore, local.local()); + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(endian, vn, byteOffset, maxByteSize); + return new IntInIntHandler(local, IntJitType.forSize(subVn.getSize()), subVn, + byteShift + byteOffset); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntInLongHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntInLongHandler.java new file mode 100644 index 0000000000..00a24d13ec --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntInLongHandler.java @@ -0,0 +1,108 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler for an {@link IntJitType int} p-code variable stored in part of a JVM {@code long}. + * + * @param local see {@link #local()} + * @param type see {@link #type()} + * @param vn see {@link #vn()} (wrt. the sub variable) + * @param byteShift see {@link #byteShift()} + */ +public record IntInLongHandler(JvmLocal local, IntJitType type, Varnode vn, + int byteShift) implements SubInLongHandler { + + @SuppressWarnings("javadoc") + public IntInLongHandler { + assertShiftFits(byteShift, type, local); + } + + @Override + public MpToStackConv getConvToSub() { + return MpIntToInt.INSTANCE; + } + + @Override + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + return em + .emit(Op::lload, local.local()) + .emit(Op::ldc__i, bitShift()) + .emit(Op::lushr) + .emit(Op::l2i) + .emit(Opnd::convert, this.type, type, ext); + } + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + if (leg == 0) { + return genLoadToStack(em, gen, type.legTypesLE().get(leg), ext); + } + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> { + int msb = (this.type.size() + byteShift) * Byte.SIZE; + if (msb > Integer.SIZE) { + yield em + .emit(Op::lload, local.local()) + .emit(Op::ldc__i, msb - Integer.SIZE) + .emit(Op::lshr) + .emit(Op::l2i) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + if (msb == Integer.SIZE) { + yield em + .emit(Op::lload, local.local()) + .emit(Op::l2i) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + yield em + .emit(Op::lload, local.local()) + .emit(Op::l2i) + .emit(Op::ldc__i, Integer.SIZE - msb) + .emit(Op::ishl) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + }; + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(endian, vn, byteOffset, maxByteSize); + return new IntInLongHandler(local, IntJitType.forSize(subVn.getSize()), subVn, + byteShift + byteOffset); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntVarAlloc.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntVarAlloc.java new file mode 100644 index 0000000000..3b54fe069d --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/IntVarAlloc.java @@ -0,0 +1,75 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Scope; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler for a p-code variable allocated in one JVM {@code int}. + * + * @param local the JVM local + * @param type the p-code type + */ +public record IntVarAlloc(JvmLocal local, IntJitType type) + implements SimpleVarHandler { + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + return genLoadLegToStackC1(em, gen, type, leg, ext); + } + + @Override + public OpndEm genLoadToOpnd(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + return IntToMpInt.INSTANCE.doConvert(em, local.opnd(), local.name(), type, ext, scope); + } + + @Override + public MpToStackConv getConvToStack() { + return MpIntToInt.INSTANCE; + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + return em + .emit(this::genLoadToStack, gen, type, Ext.ZERO) + .emit(Opnd::intToBool); + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(endian, local.vn(), byteOffset, + Math.min(type.size(), maxByteSize)); + if (byteOffset == 0) { + return new IntVarAlloc(local, IntJitType.forSize(subVn.getSize())); + } + return new IntInIntHandler(local, IntJitType.forSize(subVn.getSize()), subVn, byteOffset); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/JvmLocal.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/JvmLocal.java new file mode 100644 index 0000000000..304e6e88c1 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/JvmLocal.java @@ -0,0 +1,197 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.pcode.emu.jit.gen.var.VarGen; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.program.model.address.Address; +import ghidra.program.model.pcode.Varnode; + +/** + * An allocated JVM local + * + * @param the JVM type of this local + * @param the p-code type of this local + * @param local the declared local this wraps + * @param type a type for this local + * @param vn the varnode whose value this local holds + * @param opnd this local as an operand + */ +public record JvmLocal, JT extends SimpleJitType>(Local local, + JT type, Varnode vn, SimpleOpnd opnd) { + + /** + * Create a {@link JvmLocal} with the given local, type, and varnode + * + * @param the JVM type of the local + * @param the p-code type of the local + * @param local the local + * @param type the p-code type of the local + * @param vn the varnode to assign to the local + * @return the new local (wrapper) + */ + public static , JT extends SimpleJitType> JvmLocal + of(Local local, JT type, Varnode vn) { + SimpleOpnd opnd = SimpleOpnd.of(type, local); + return new JvmLocal<>(local, type, vn, opnd); + } + + /** + * Get the name of the wrapped local + * + * @return the name + */ + public String name() { + return local.name(); + } + + /** + * Cast this local to satisfy checkers when a type variable is known to be of a given type + *

+ * This will verify at runtime that the types are in fact identical. + * + * @param the "to" JVM type + * @param the "to" p-code type + * @param type the "to" p-code type + * @return this local as the given type + */ + @SuppressWarnings("unchecked") + public , TJT extends SimpleJitType> JvmLocal + castOf(TJT type) { + if (this.type != type) { + throw new ClassCastException( + "JvmLocal is not of the given type: this is %s. Requested is %s." + .formatted(this.type, type)); + } + return (JvmLocal) this; + } + + /** + * Emit bytecode into the class constructor needed to access the varnode's actual value from the + * underlying {@link PcodeExecutorState}. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @return the emitter typed with the incoming stack + */ + public Emitter genInit(Emitter em, JitCodeGenerator gen) { + return VarGen.genVarnodeInit(em, gen, vn); + } + + /** + * Emit bytecode to load this local's value onto the JVM stack as the given type + * + * @param the desired JVM type + * @param the desired p-code type + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the desired p-code type + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having pushed the value + */ + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + return em + .emit(opnd::read) + .emit(Opnd::convert, this.type, type, ext); + } + + /** + * Emit bytecode to store the value on the JVM stack into the local + * + * @param the JVM type of the value on the stack + * @param the p-code type of the value on the stack + * @param the tail of the incoming stack + * @param the incoming stack with the value on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the value on the stack + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + public , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT type, Ext ext, Scope scope) { + return em + .emit(Opnd::convert, type, this.type, ext) + .emit(opnd::writeDirect); + } + + /** + * Emit bytecode to bring this varnode into scope. + * + *

+ * This will copy the value from the {@link JitBytesPcodeExecutorState state} into the local + * variable. + * + * @param the type of the compiled passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @return the emitter typed with the incoming stack + */ + public Emitter genBirthCode(Emitter em, + Local> localThis, JitCodeGenerator gen) { + return em + .emit(VarGen::genReadValDirectToStack, localThis, gen, type, vn) + .emit(opnd::writeDirect); + } + + /** + * Emit bytecode to take this varnode out of scope. + * + *

+ * This will copy the value from the local variable into the {@link JitBytesPcodeExecutorState + * state}. + * + * @param the type of the compiled passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @return the emitter typed with the incoming stack + */ + public Emitter genRetireCode(Emitter em, + Local> localThis, JitCodeGenerator gen) { + return em + .emit(opnd::read) + .emit(VarGen::genWriteValDirectFromStack, localThis, gen, type, vn); + } + + /** + * {@return the maximum address that would be occupied by the full primitive type} + */ + public Address maxPrimAddr() { + return vn.getAddress().add(type.ext().size() - 1); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/LongInLongHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/LongInLongHandler.java new file mode 100644 index 0000000000..c1398d7274 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/LongInLongHandler.java @@ -0,0 +1,88 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler for an {@link LongJitType long} p-code variable stored in part of a JVM {@code long}. + * + * @param local see {@link #local()} + * @param type see {@link #type()} + * @param vn see {@link #vn()} (wrt. the sub variable) + * @param byteShift see {@link #byteShift()} + */ +public record LongInLongHandler(JvmLocal local, LongJitType type, Varnode vn, + int byteShift) implements SubInLongHandler { + + @SuppressWarnings("javadoc") + public LongInLongHandler { + assertShiftFits(byteShift, type, local); + } + + @Override + public MpToStackConv getConvToSub() { + return MpIntToLong.INSTANCE; + } + + @Override + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + return em + .emit(Op::lload, local.local()) + .emit(Op::ldc__i, bitShift()) + .emit(Op::lushr) + .emit(Opnd::convert, this.type, type, ext); + } + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + if (leg == 0) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(gen.getAnalysisContext().getEndian(), + vn, 0, Integer.BYTES); + return new IntInLongHandler(local, IntJitType.I4, subVn, byteShift) + .genLoadLegToStack(em, gen, type, leg, ext); + } + Varnode subVn = JitDataFlowArithmetic.subPieceVn(gen.getAnalysisContext().getEndian(), + vn, Integer.BYTES, vn.getSize() - Integer.BYTES); + return new IntInLongHandler(local, IntJitType.forSize(subVn.getSize()), subVn, + byteShift + Integer.BYTES).genLoadLegToStack(em, gen, type, leg - 1, ext); + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(endian, vn, byteOffset, maxByteSize); + if (subVn.getSize() <= Integer.BYTES) { + return new IntInLongHandler(local, IntJitType.forSize(subVn.getSize()), subVn, + byteShift + byteOffset); + } + return new LongInLongHandler(local, LongJitType.forSize(subVn.getSize()), subVn, + byteShift + byteOffset); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/LongVarAlloc.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/LongVarAlloc.java new file mode 100644 index 0000000000..e5d1595e36 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/LongVarAlloc.java @@ -0,0 +1,74 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler for a p-code variable allocated in one JVM {@code long}. + * + * @param local the JVM local + * @param type the p-code type + */ +public record LongVarAlloc(JvmLocal local, LongJitType type) + implements SimpleVarHandler { + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + return genLoadLegToStackC2(em, gen, type, leg, ext); + } + + @Override + public MpToStackConv getConvToStack() { + return MpIntToLong.INSTANCE; + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + return em + .emit(this::genLoadToStack, gen, type, Ext.ZERO) + .emit(Op::ldc__l, 0) + .emit(Op::lcmp); // Outputs -1, 0, or 1. So long as lsb is set, it's true. + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + Varnode subVn = JitDataFlowArithmetic.subPieceVn(endian, local.vn(), byteOffset, + Math.min(type.size(), maxByteSize)); + if (byteOffset == 0) { + return new LongVarAlloc(local, LongJitType.forSize(subVn.getSize())); + } + if (subVn.getSize() <= Integer.BYTES) { + return new IntInLongHandler(local, IntJitType.forSize(subVn.getSize()), subVn, + byteOffset); + } + return new LongInLongHandler(local, LongJitType.forSize(subVn.getSize()), subVn, + byteOffset); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/NoHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/NoHandler.java new file mode 100644 index 0000000000..6ac13301a1 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/NoHandler.java @@ -0,0 +1,106 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Scope; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * A dummy handler for values/variables that are not allocated in JVM locals + *

+ * Every operation on this handler will throw an exception at code generation time. + */ +public enum NoHandler implements VarHandler { + /** Singleton */ + INSTANCE; + + @Override + public Varnode vn() { + return null; + } + + @Override + public JitType type() { + return null; + } + + @Override + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + throw new AssertionError(); + } + + @Override + public OpndEm genLoadToOpnd(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + throw new AssertionError(); + } + + @Override + public Emitter>> genLoadToArray(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope, int slack) { + throw new AssertionError(); + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + throw new AssertionError(); + } + + @Override + public , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT type, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + public Emitter genStoreFromOpnd(Emitter em, JitCodeGenerator gen, + Opnd opnd, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + public >> Emitter genStoreFromArray( + Emitter em, JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + return this; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/ShiftedMpIntHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/ShiftedMpIntHandler.java new file mode 100644 index 0000000000..2f020e2d51 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/ShiftedMpIntHandler.java @@ -0,0 +1,607 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.*; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd.SimpleOpndEm; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * The handler used for a varnode requiring allocation of multiple integers, where those integers + * do not align to the variable's legs. + *

+ * The below diagram is an example shifted allocation, whose {@code byteShift} value is 3, and whose + * varnode size is 11 (admittedly pathological, but made to illustrate a complicated example). + * + *

+ * +--*--*--*--+--*--*--*--+--*--*--*--+--*--*--*--+
+ * | parts[3]  | parts[2]  | parts[1]  | parts[0]  |
+ * +-----------+-----------+-----------+-----------+
+ *       +--*--*--+--*--*--*--+--*--*--*--+
+ *       |  leg2  |   leg1    |   leg0    |
+ *       +--------+-----------+-----------+
+ * 
+ *

+ * In the unaligned case, all loads and stores require copying the shifted value into a series of + * temporary locals, representing the legs of the value. Because these are already temporary, the + * operator may freely use the legs as temporary storage. + * + * @param parts the list of locals spanned by the variable, in little-endian order. + * @param type the type of the multi-precision integer variable (only considering the varnode, not + * the whole comprised of the spanned parts). In the diagram, this would be + * {@link MpIntJitType}{@code (size=11)}. + * @param vn the complete varnode accessible to this handler. NOTE: The handler must take care not + * to modify or permit access to portions of the parts at either end not actually part of + * its varnode. In the example, the lower 24 bits of {@code parts[0]} and the upper 16 + * bits of {@code parts[3]} cannot be accessed. Should a caller to + * {@link #genLoadToOpnd(Emitter, JitCodeGenerator, MpIntJitType, Ext, Scope)} specify a + * type larger than 11 bytes, only the 11-byte value is loaded, then extended to the + * requested size. We do not load the more sigificant portion of {@code parts[3]}. + * @param byteShift the number of least-significant bytes of the handler's least-significant part + * that are excluded from the variable's least-significant leg. I.e., the number + * of bytes to shift right when loading the value. In the example, this is 3. + */ +public record ShiftedMpIntHandler(List> parts, MpIntJitType type, + Varnode vn, int byteShift) implements VarHandler { + + @SuppressWarnings("javadoc") + public ShiftedMpIntHandler { + assert byteShift > 0 && byteShift < 4; + assert parts.size() > 1; + } + + private int bitShift() { + return byteShift * Byte.SIZE; + } + + /** + * Emit bytecode to load the right portion of a given leg + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param i the index of the part, 0 being the least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having the portion pushed onto it + * positioned in an int + */ + private Emitter> doGenLoadIntRight(Emitter em, + JitCodeGenerator gen, int i, Ext ext) { + return em + .emit(parts.get(i)::genLoadToStack, gen, IntJitType.I4, ext) + .emit(Op::ldc__i, bitShift()) + .emit(Op::iushr); + } + + /** + * Emit bytecode to load and {@code or} in the left portion of a given leg + * + * @param the tail of the incoming stack + * @param the incoming stack having the right portion of the same leg already on it + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param i the index of the part, 0 being the least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the incoming stack, though the top value has been modified. + */ + private > Emitter> + doGenLoadOrIntLeft(Emitter em, JitCodeGenerator gen, int i, Ext ext) { + if (i >= parts.size()) { + // TODO: This may require attention to ext + return Misc.cast1(em); + } + return em + .emit(parts.get(i)::genLoadToStack, gen, IntJitType.I4, ext) + .emit(Op::ldc__i, Integer.SIZE - bitShift()) + .emit(Op::ishl) + .emit(Op::ior); + } + + /** + * Emit bytecode to load the right portion of a long + *

+ * This operates as if the long is positioned at the same byte offset as the least-sigificant + * leg. Adapting the example from the class documentation: + * + *

+	 * +--*--*--*--+--*--*--*--+--*--*--*--+--*--*--*--+
+	 * | parts[3]  | parts[2]  | parts[1]  | parts[0]  |
+	 * +-----------+-----------+-----------+-----------+
+	 *       +--*--*--+--*--*--*--+--*--*--*--+
+	 *       |  leg2  |   leg1    |   leg0    |
+	 *       +--------+-----------+-----------+
+	 *                +-----------------------+
+	 *                |        as long        |
+	 *                +-----------------------+
+	 * 
+ *

+ * Thus, to load the full long, we need to retrieve and shift into place the values from + * {@code parts[0]}, {@code [1]}, and {@code [2]}. This method loads the right-most portion. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param i the index of the part, 0 being the least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having the portion pushed onto it + * positioned in a long + */ + private Emitter> doGenLoadLongRight(Emitter em, + JitCodeGenerator gen, int i, Ext ext) { + return em + .emit(parts.get(i)::genLoadToStack, gen, LongJitType.I8, ext) + .emit(Op::ldc__i, bitShift()) + .emit(Op::lushr); + } + + /** + * Emit bytecode to load and {@code or} in the middle portion of a long + * + * @see #doGenLoadLongRight(Emitter, JitCodeGenerator, int, Ext) + * @param the tail of the incoming stack + * @param the incoming stack having the right portion of the long already on it + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param i the index of the part, 0 being the least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the incoming stack, though the top value has been modified. + */ + private > Emitter> + doGenLoadOrLongMiddle(Emitter em, JitCodeGenerator gen, int i, Ext ext) { + return em + .emit(parts.get(i)::genLoadToStack, gen, LongJitType.I8, ext) + .emit(Op::ldc__i, Integer.SIZE - bitShift()) + .emit(Op::lshl) + .emit(Op::lor); + } + + /** + * Emit bytecode to load and {@code or} in the left portion of a long + * + * @see #doGenLoadLongRight(Emitter, JitCodeGenerator, int, Ext) + * @param the tail of the incoming stack + * @param the incoming stack having the right and middle portions of the long already + * {@code or}ed together on it + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param i the index of the part, 0 being least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the incoming stack, though the top value has been modified. + */ + private > Emitter> + doGenLoadOrLongLeft(Emitter em, JitCodeGenerator gen, int i, Ext ext) { + return em + .emit(parts.get(i)::genLoadToStack, gen, LongJitType.I8, ext) + .emit(Op::ldc__i, Integer.SIZE * 2 - bitShift()) + .emit(Op::lshl) + .emit(Op::lor); + } + + @Override + public , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + return switch (type) { + case IntJitType t when t.size() + byteShift <= Integer.BYTES -> em + // We know at most 4 bytes (1 part) are involved + .emit(this::doGenLoadIntRight, gen, 0, ext) + .emit(Opnd::convert, IntJitType.forSize(Integer.BYTES - byteShift), type, ext); + case IntJitType t -> em + .emit(this::doGenLoadIntRight, gen, 0, ext) + .emit(this::doGenLoadOrIntLeft, gen, 1, ext) + .emit(Opnd::convert, IntJitType.I4, type, ext); + case LongJitType t when t.size() + byteShift <= Long.BYTES -> em + // We know at most 8 bytes (2 parts) are involved + .emit(this::doGenLoadLongRight, gen, 0, ext) + .emit(this::doGenLoadOrLongMiddle, gen, 1, ext) + .emit(Opnd::convert, LongJitType.forSize(Long.BYTES - byteShift), type, ext); + case LongJitType t -> em + .emit(this::doGenLoadLongRight, gen, 0, ext) + .emit(this::doGenLoadOrLongMiddle, gen, 1, ext) + .emit(this::doGenLoadOrLongLeft, gen, 2, ext) + .emit(Opnd::convert, LongJitType.I8, type, ext); + default -> throw new AssertionError(); + }; + } + + @Override + public OpndEm genLoadToOpnd(Emitter em, + JitCodeGenerator gen, MpIntJitType to, Ext ext, Scope scope) { + /** + * NOTE: Even though we can access more significant parts, we should not incorporate + * anything beyond what is allowed by the type (which corresponds to the varnode size). + */ + List fromLegTypes = type.legTypesLE(); + List toLegTypes = to.legTypesLE(); + List> toLegs = new ArrayList<>(); + int legsOut = toLegTypes.size(); + int legsIn = fromLegTypes.size(); + int defLegs = Integer.min(legsIn, legsOut); + + for (int i = 0; i < defLegs; i++) { + IntJitType fromLegType = fromLegTypes.get(i); + IntJitType toLegType = toLegTypes.get(i); + var result = em + .emit(this::doGenLoadIntRight, gen, i, ext) + .emit(this::doGenLoadOrIntLeft, gen, i + 1, ext) + // This chained convert should blot out anything outside the varnode + .emit(Opnd::convertIntToInt, IntJitType.I4, fromLegType, ext) + .emit(Opnd::convertIntToInt, fromLegType, toLegType, ext) + .emit(Opnd::createInt, toLegType, "%s_leg%d".formatted(name(), i), scope); + em = result.em(); + toLegs.add(result.opnd()); + } + if (legsOut > defLegs) { + var sign = switch (ext) { + case ZERO -> new SimpleOpndEm<>(IntConstOpnd.ZERO_I4, em); + case SIGN -> em + .emit(toLegs.getLast()::read) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) // Signed + .emit(Opnd::createIntReadOnly, IntJitType.I4, + "%s_sign".formatted(name()), scope); + }; + em = sign.em(); + for (int i = defLegs; i < legsOut; i++) { + toLegs.add(sign.opnd()); + } + } + return new OpndEm<>(MpIntLocalOpnd.of(to, name(), toLegs), em); + } + + @Override + public Emitter> genLoadLegToStack(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + List fromLegTypes = type.legTypesLE(); + int fromLegCount = fromLegTypes.size(); + if (leg >= fromLegCount) { + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> em + .emit(this::doGenLoadIntRight, gen, fromLegCount - 1, ext) // Remove ??? + .emit(this::doGenLoadOrIntLeft, gen, fromLegCount, ext) + .emit(Opnd::convertIntToInt, IntJitType.I4, fromLegTypes.getLast(), ext) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + }; + } + IntJitType fromLegType = fromLegTypes.get(leg); + IntJitType toLegType = type.legTypesLE().get(leg); + return em + .emit(this::doGenLoadIntRight, gen, leg, ext) + .emit(this::doGenLoadOrIntLeft, gen, leg + 1, ext) + .emit(Opnd::convertIntToInt, IntJitType.I4, fromLegType, ext) + .emit(Opnd::convertIntToInt, fromLegType, toLegType, ext); + } + + @Override + public Emitter>> genLoadToArray(Emitter em, + JitCodeGenerator gen, MpIntJitType to, Ext ext, Scope scope, int slack) { + List fromLegTypes = type.legTypesLE(); + List toLegTypes = to.legTypesLE(); + int legsOut = toLegTypes.size(); + int legsIn = fromLegTypes.size(); + int defLegs = Integer.min(legsIn, legsOut); + + Local> arr = scope.decl(Types.T_INT_ARR, name()); + em = em + .emit(Op::ldc__i, legsOut + slack) + .emit(Op::newarray, Types.T_INT) + .emit(Op::astore, arr); + + for (int i = 0; i < defLegs; i++) { + IntJitType fromLegType = fromLegTypes.get(i); + IntJitType toLegType = toLegTypes.get(i); + em = em + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(this::doGenLoadIntRight, gen, i, ext) + .emit(this::doGenLoadOrIntLeft, gen, i + 1, ext) + .emit(Opnd::convertIntToInt, IntJitType.I4, fromLegType, ext) + .emit(Opnd::convertIntToInt, fromLegType, toLegType, ext) + .emit(Op::iastore); + } + return em + .emit(MpIntToMpInt::doGenArrExt, arr, legsOut, defLegs, ext, scope) + .emit(Op::aload, arr); + } + + @Override + public Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + int maskRight = -1 << bitShift(); + var result = em + .emit(Op::iload, parts.get(0).local()) + .emit(Op::ldc__i, maskRight) + .emit(Op::iand); + for (int i = 0; i < parts.size(); i++) { + int bytesLeft = type.size() - byteShift - i * Integer.SIZE; + var twoInts = result + .emit(Op::iload, parts.get(i).local()); + if (bytesLeft < Integer.SIZE) { + int maskLeft = -1 >>> (Integer.SIZE - bytesLeft); + twoInts = twoInts + .emit(Op::ldc__i, maskLeft) + .emit(Op::iand); + } + result = twoInts + .emit(Op::ior); + } + return result.emit(Opnd::intToBool); + } + + /** + * Emit bytecode to store from the stack into a given part + *

+ * This will combined the existing value and the positioned value using a mask of the accessible + * portion of the given part. This code will compute that mask and emit the bytecode to apply + * it. + * + * @param the tail of the incoming stack + * @param the incoming stack having the value on top, positioned in an int + * @param em the emitter typed with the incoming stack + * @param i the index of the part, 0 being the least significant + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + private > Emitter doGenStoreInt(Emitter em, + int i) { + int bitShift = byteShift * Byte.SIZE; + int mask = -1; + if (i == 0) { + mask &= -1 << bitShift; + } + // bytes we'd exceed to the left + int bytesToRight = Integer.BYTES * (i + 1) - type.size() - byteShift; + if (bytesToRight > 0) { + mask &= -1 >>> (bytesToRight * Byte.SIZE); + } + JvmLocal part = parts.get(i); + assert mask != 0; + return mask == -1 + ? em + .emit(Op::istore, part.local()) + : em + .emit(Op::ldc__i, mask) + .emit(Op::iand) + .emit(Op::iload, part.local()) + .emit(Op::ldc__i, ~mask) + .emit(Op::iand) + .emit(Op::ior) + .emit(Op::istore, part.local()); + } + + @Override + public , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT from, Ext ext, Scope scope) { + int bitShift = byteShift * Byte.SIZE; + return switch (from) { + case IntJitType t -> { + var emConvPos = em + .emit(Opnd::convert, from, LongJitType.I8, ext) + .emit(Op::ldc__i, bitShift) + .emit(Op::lshl); + for (int i = 0; i < parts.size() - 1; i++) { + emConvPos = emConvPos + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(this::doGenStoreInt, i) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Opnd::lextshr, ext); + } + yield emConvPos + .emit(Op::l2i) + .emit(this::doGenStoreInt, parts.size() - 1); + } + case LongJitType t -> { + var emConvPos = em + .emit(Opnd::convert, from, LongJitType.I8, ext) + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(Op::ldc__i, bitShift) + .emit(Op::ishl) + .emit(this::doGenStoreInt, 0) + .emit(Op::ldc__i, Integer.SIZE - bitShift) + .emit(Opnd::lextshr, ext); + for (int i = 1; i < parts.size() - 1; i++) { + emConvPos = emConvPos + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(this::doGenStoreInt, i) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Opnd::lextshr, ext); + } + yield emConvPos + .emit(Op::l2i) + .emit(this::doGenStoreInt, parts.size() - 1); + } + default -> throw new AssertionError(); + }; + } + + /** + * Emit bytecode to load a leg from the source operand and position it within a long on the + * stack. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param leg the leg to load + * @param bitShift the number of bits to shift left + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having pushed the long + */ + private Emitter> positionOpndLeg(Emitter em, + SimpleOpnd leg, int bitShift, Ext ext) { + return em + .emit(leg::read) + .emit(Opnd::convert, leg.type(), LongJitType.I8, ext) + .emit(Op::ldc__i, bitShift) + .emit(Op::lshl); + } + + /** + * Emit bytecode to store a leg into its two overlapped parts, then load the next leg from the + * source positioning it and the remainder into the long on the top of the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param fromLegs the list of source legs in little-endian order + * @param i the index of the part to store into, 0 being the least significant + * @param bitShift the number of bits to shift the next leg left + * @param ext the kind of extension to apply + * @return the emitter typed with the incoming stack, though the top value has been modified + */ + private > Emitter> + storePartAndPositionNextOpndLeg(Emitter em, + List> fromLegs, int i, int bitShift, Ext ext) { + var emStored = em + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(this::doGenStoreInt, i); + return i + 1 < fromLegs.size() + ? emStored + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr) + .emit(this::positionOpndLeg, fromLegs.get(i + 1), bitShift, ext) + .emit(Op::lor) + : emStored + .emit(Op::ldc__i, Integer.SIZE) + .emit(Opnd::lextshr, ext); + } + + /** + * {@inheritDoc} + *

+ * The general strategy is to load the source operand one leg at a time. In order to put each + * leg with the remaining portion of the previous leg in position, we use a long on the stack as + * a temporary. This eases "gluing" the legs together and then writing the shifted portion into + * each part. + */ + @Override + public Emitter genStoreFromOpnd(Emitter em, JitCodeGenerator gen, + Opnd opnd, Ext ext, Scope scope) { + List> fromLegs = opnd.type().castLegsLE(opnd); + int bitShift = byteShift * Byte.SIZE; + var emConvPos = em.emit(this::positionOpndLeg, fromLegs.get(0), bitShift, ext); + for (int i = 0; i < parts.size() - 1; i++) { + emConvPos = + emConvPos.emit(this::storePartAndPositionNextOpndLeg, fromLegs, i, bitShift, ext); + } + return emConvPos + .emit(Op::l2i) + .emit(this::doGenStoreInt, parts.size() - 1); + } + + /** + * The analog to {@link #positionOpndLeg(Emitter, SimpleOpnd, int, Ext)}, but for a source value + * in an array. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param arr a handle to the local holding the source array reference + * @param legType the type of the leg being loaded + * @param bitShift the number of bits to shift left + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having pushed the long + */ + private Emitter> positionArrLeg(Emitter em, + Local> arr, IntJitType legType, int bitShift, Ext ext) { + return em + .emit(Op::aload, arr) + .emit(Op::ldc__i, 0) + .emit(Op::iaload) + .emit(Opnd::convert, legType, LongJitType.I8, ext) + .emit(Op::ldc__i, bitShift) + .emit(Op::lshl); + } + + /** + * The analog to {@link #storePartAndPositionNextOpndLeg(Emitter, List, int, int, Ext)}, but for + * a source value in an array. + * + * @param the tail of the incoming stack + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param arr a handle to the local holding the source array reference + * @param fromLegTypes the list of leg types in the source value, in little-endian order + * @param i the index of the part to store into, 0 being the least significant + * @param bitShift the number of bits to shift the next leg left + * @param ext the kind of extension to apply + * @return the emitter typed with the incoming stack, though the top value has been modified + */ + private > Emitter> + storePartAndPositionNextArrLeg(Emitter em, Local> arr, + List fromLegTypes, int i, int bitShift, Ext ext) { + var emStored = em + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(this::doGenStoreInt, i); + return i + 1 < fromLegTypes.size() + ? emStored + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr) + .emit(this::positionArrLeg, arr, fromLegTypes.get(i + 1), bitShift, ext) + .emit(Op::lor) + : emStored + .emit(Op::ldc__i, Integer.SIZE) + .emit(Opnd::lextshr, ext); + } + + /** + * {@inheritDoc} + *

+ * The strategy here is the same as for + * {@link #genStoreFromOpnd(Emitter, JitCodeGenerator, Opnd, Ext, Scope)} + */ + @Override + public >> Emitter genStoreFromArray( + Emitter em, JitCodeGenerator gen, MpIntJitType from, Ext ext, Scope scope) { + try (SubScope ss = scope.sub()) { + Local> arr = ss.decl(Types.T_INT_ARR, "temp_arr"); + List fromLegTypes = from.legTypesLE(); + int bitShift = byteShift + Byte.SIZE; + var emConvPos = em + .emit(Op::astore, arr) + .emit(this::positionArrLeg, arr, fromLegTypes.get(0), bitShift, ext); + for (int i = 0; i < parts.size() - 1; i++) { + emConvPos = emConvPos.emit(this::storePartAndPositionNextArrLeg, arr, fromLegTypes, + i, bitShift, ext); + } + return emConvPos + .emit(Op::l2i) + .emit(this::doGenStoreInt, parts.size() - 1); + } + } + + @Override + public VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize) { + return AlignedMpIntHandler.subHandler(endian, vn, parts, byteShift, byteOffset, + maxByteSize); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SimpleVarHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SimpleVarHandler.java new file mode 100644 index 0000000000..89fe8923f2 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SimpleVarHandler.java @@ -0,0 +1,192 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.pcode.Varnode; + +/** + * A handler for p-code variables composed of a single JVM local variable. + * + * @param the JVM type of the variable + * @param the p-code type of the variable + */ +public interface SimpleVarHandler, JT extends SimpleJitType> + extends VarHandler { + /** + * Get the local variable into which this p-code variable is allocated + * + * @return the local + */ + JvmLocal local(); + + @Override + default Varnode vn() { + return local().vn(); + } + + @Override + JT type(); + + @Override + default , TJT extends SimpleJitType, N extends Next> + Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext) { + return local().genLoadToStack(em, gen, type, ext); + } + + @Override + default OpndEm genLoadToOpnd(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + return em + .emit(this::genLoadToStack, gen, type(), ext) + .emit(Opnd::convertToOpnd, type(), local().name(), type, ext, scope); + } + + /** + * This provides the implementation of + * {@link #genLoadLegToStack(Emitter, JitCodeGenerator, MpIntJitType, int, Ext)} for category-1 + * primitives, i.e., {@code int} and {@code float}. + *

+ * Only leg 0 is meaningful for a category-1 primitive. Any other leg is just the extension of + * the one leg. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the complete multi-precision value + * @param leg the index of the leg to load, 0 being least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having the int leg pushed onto it + */ + default Emitter> genLoadLegToStackC1(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + if (leg == 0) { + return em + .emit(this::genLoadToStack, gen, type.legTypesLE().get(leg), ext); + } + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> { + IntJitType intType = IntJitType.forSize(type().size()); + yield em + .emit(this::genLoadToStack, gen, intType, ext) + .emit(Op::ldc__i, Integer.SIZE - intType.size() * Byte.SIZE) + .emit(Op::ishl) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + }; + } + + /** + * This provides the implementation of + * {@link #genLoadLegToStack(Emitter, JitCodeGenerator, MpIntJitType, int, Ext)} for category-2 + * primitives, i.e., {@code long} and {@code double}. + *

+ * Only legs 0 and 1 are meaningful for a category-2 primitive. Any other leg is just the + * extension of the upper leg. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the complete multi-precision value + * @param leg the index of the leg to load, 0 being least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having the int leg pushed onto it + */ + default Emitter> genLoadLegToStackC2(Emitter em, + JitCodeGenerator gen, MpIntJitType type, int leg, Ext ext) { + if (leg == 0) { + return em + .emit(this::genLoadToStack, gen, type.legTypesLE().get(leg), ext); + } + if (leg == 1) { + LongJitType longType = LongJitType.forSize(type().size()); + return em + .emit(this::genLoadToStack, gen, longType, ext) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lshr) + .emit(Op::l2i) + .emit(Opnd::convertIntToInt, IntJitType.forSize(type().size() - Integer.BYTES), + type.legTypesLE().get(leg), ext); + } + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> { + LongJitType longType = LongJitType.forSize(type().size()); + yield em + .emit(this::genLoadToStack, gen, longType, ext) + .emit(Op::ldc__i, longType.size() * Byte.SIZE - Integer.SIZE) + .emit(Op::lshr) + .emit(Op::l2i) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); // FIXME: Is size conversion required here? + } + }; + } + + @Override + default Emitter>> genLoadToArray(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope, int slack) { + return em + .emit(this::genLoadToStack, gen, type(), ext) + .emit(Opnd::convertToArray, type(), local().name(), type, ext, scope, slack); + } + + @Override + default , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT type, Ext ext, Scope scope) { + return local().genStoreFromStack(em, gen, type, ext, scope); + } + + /** + * Get the converter of multi-precision integers to the stack type of this handler's local. + *

+ * Note that the converter need only extract the least 1 or 2 legs of the source multi-precision + * int, depending on the category of the destination's type. The converter knows how to handle + * both the operand (series of locals) and array forms. + * + * @return the converter + */ + MpToStackConv getConvToStack(); + + @Override + default Emitter genStoreFromOpnd(Emitter em, JitCodeGenerator gen, + Opnd opnd, Ext ext, Scope scope) { + return em + .emit(getConvToStack()::convertOpndToStack, opnd, this.type(), ext) + .emit(this::genStoreFromStack, gen, this.type(), ext, scope); + } + + @Override + default >> Emitter genStoreFromArray( + Emitter em, JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + return em + .emit(getConvToStack()::convertArrayToStack, type, this.type(), ext) + .emit(this::genStoreFromStack, gen, this.type(), ext, scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SubInLongHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SubInLongHandler.java new file mode 100644 index 0000000000..740af7fa21 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SubInLongHandler.java @@ -0,0 +1,66 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * A handler for a p-code variable stored in part of a JVM {@code long}. + * + * @param the JVM type of the sub variable + * @param the p-code type of the sub variable + */ +public interface SubInLongHandler, SJT extends SimpleJitType> + extends SubVarHandler { + + @Override + default Emitter> genLoadToBool(Emitter em, + JitCodeGenerator gen) { + long mask = longMask(); + return em + .emit(Op::lload, local().local()) + .emit(Op::ldc__l, mask) + .emit(Op::land) + .emit(Op::ldc__l, 0) + .emit(Op::lcmp); + } + + @Override + default , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT type, Ext ext, Scope scope) { + long mask = longMask(); + return em + .emit(Opnd::convert, type, LongJitType.I8, ext) + .emit(Op::ldc__i, bitShift()) + .emit(Op::lshl) + .emit(Op::ldc__l, mask) + .emit(Op::land) + .emit(Op::lload, local().local()) + .emit(Op::ldc__l, ~mask) + .emit(Op::land) + .emit(Op::lor) + .emit(Op::lstore, local().local()); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SubVarHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SubVarHandler.java new file mode 100644 index 0000000000..4243d84172 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/SubVarHandler.java @@ -0,0 +1,139 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Scope; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * A handler to p-code variables stored in just a portion of a single JVM local variable. + * + * @param the JVM type of the sub variable + * @param the p-code type of the sub variable + * @param the JVM type of the containing variable + * @param the p-code type of the containing variable + */ +public interface SubVarHandler, SJT extends SimpleJitType, + WT extends BPrim, WJT extends SimpleJitType> extends VarHandler { + + /** + * Verify that the sub variable as shifted actually fits in the containing variable + * + * @param byteShift the number of unused bytes in the container variable to the right of the sub + * variable + * @param type the type of the sub variable + * @param local the containing local variable + */ + default void assertShiftFits(int byteShift, SJT type, JvmLocal local) { + assert byteShift >= 0 && byteShift + type.size() <= local.type().size(); + } + + /** + * {@return the number of unused bytes in the container variable to the right of the sub + * variable} + */ + int byteShift(); + + /** + * {@return the number of bits in the sub variable} + */ + default int bitSize() { + return type().size() * Byte.SIZE; + } + + /** + * {@return the number of unused bits in the container variable to the right of the sub + * variable} + */ + default int bitShift() { + return byteShift() * Byte.SIZE; + } + + /** + * {@return the mask indicating which parts of the {@code int} containing variable are within + * the sub variable} + */ + default int intMask() { + return (-1 >>> (Integer.SIZE - bitSize())) << bitShift(); + } + + /** + * {@return the mask indicating which parts of the {@code long} containing variable are within + * the sub variable} + */ + default long longMask() { + return (-1L >>> (Long.SIZE - bitSize())) << bitShift(); + } + + /** + * {@return The containing local variable} + */ + JvmLocal local(); + + @Override + SJT type(); + + /** + * Get the converter of multi-precision integers to the type of the sub variable. + *

+ * The converter need not worry about positioning or masking wrt. the sub variable. It should + * extract from the multi-precision integer the minimum number of legs needed to fill the sub + * variable, i.e., it need only consider the sub variable's size. This handler will then mask + * and position it within the containing variable for storage. + * + * @return the converter + */ + MpToStackConv getConvToSub(); + + @Override + default OpndEm genLoadToOpnd(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + return em + .emit(this::genLoadToStack, gen, this.type(), ext) + .emit(Opnd::convertToOpnd, this.type(), name(), type, ext, scope); + } + + @Override + default Emitter>> genLoadToArray(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope, int slack) { + return em + .emit(this::genLoadToStack, gen, this.type(), ext) + .emit(Opnd::convertToArray, this.type(), name(), type, ext, scope, slack); + } + + @Override + default Emitter genStoreFromOpnd(Emitter em, JitCodeGenerator gen, + Opnd opnd, Ext ext, Scope scope) { + return em + .emit(getConvToSub()::convertOpndToStack, opnd, this.type(), ext) + .emit(this::genStoreFromStack, gen, this.type(), ext, scope); + } + + @Override + default >> Emitter genStoreFromArray( + Emitter em, JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope) { + return em + .emit(getConvToSub()::convertArrayToStack, type, this.type(), ext) + .emit(this::genStoreFromStack, gen, this.type(), ext, scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/VarHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/VarHandler.java new file mode 100644 index 0000000000..aa6628b47a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/alloc/VarHandler.java @@ -0,0 +1,226 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.alloc; + +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.op.SubPieceOpGen; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Scope; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * A handler that knows how to load and store variables' values from local storage. + *

+ * Some variables are hosted in a single JVM local of compatible type. Others, notably + * multi-precision integers, are allocated among two or more JVM local integers. Each such integer + * is called a "leg" of the multi-precision integer. Other literature may call these "digits" (whose + * value is in [0, 0xffffffff]). Depending on the operator implementation, value may need to be + * loaded with alternative types or in different forms. e.g., any float operator will need to + * convert its inputs into the appropriate float type, even if the operands were allocated as an int + * type. Similarly, some operators are implement their multi-precision computations by invoking + * static methods whose parameters are {@code int[]}, and so they will load and store the array + * forms instead of accessing the legs' locals. This interface provides generators for the various + * circumstances. Each subclass provides the implementations for various allocations. + */ +public interface VarHandler { + + /** + * Generate a name for the variable representing the given varnode + *

+ * These are for debugging purposes. When dumping generating bytecode, the declared local + * variables and their scopes are often also dumped. This provides a human with the local + * variable index for various varnodes. + * + * @param vn the varnode + * @return the name + */ + static String nameVn(Varnode vn) { + return "var_%s_%x_%d".formatted( + vn.getAddress().getAddressSpace().getName(), + vn.getOffset(), + vn.getSize()); + } + + /** + * Get the complete varnode accessible to this handler + * + * @return the varnode + */ + Varnode vn(); + + /** + * Get the name for this handler's local variable, named after the varnode is represents. + * + * @return the name of the local variable + */ + default String name() { + return nameVn(vn()); + } + + /** + * Get the p-code type of the local variable this handler uses. + * + * @return the type + */ + JitType type(); + + /** + * Emit bytecode to load the varnode's value onto the JVM stack. + * + * @param the JVM type of the value to load onto the stack + * @param the p-code type of the value to load onto the stack + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the value expected on the JVM stack by the proceeding bytecode + * @param ext the kind of extension to apply when adjusting from JVM size to varnode size + * @return the emitter typed with the resulting stack + */ + , TJT extends SimpleJitType, N extends Next> Emitter> + genLoadToStack(Emitter em, JitCodeGenerator gen, TJT type, Ext ext); + + /** + * Emit bytecode to load the varnode's value into several locals. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the value expected on the JVM stack by the proceeding bytecode + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the operand containing the locals, and the emitter typed with the incoming stack + */ + OpndEm genLoadToOpnd(Emitter em, JitCodeGenerator gen, + MpIntJitType type, Ext ext, Scope scope); + + /** + * Emit bytecode to load one leg of a multi-precision value from the varnode onto the JVM stack. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the complete multi-precision value + * @param leg the index of the leg to load, 0 being least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having the int leg pushed onto it + */ + Emitter> genLoadLegToStack(Emitter em, JitCodeGenerator gen, + MpIntJitType type, int leg, Ext ext); + + /** + * Emit bytecode to load the varnode's value into an integer array in little-endian order, + * pushing its ref onto the JVM stack. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the complete multi-precision value + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @param slack the number of additional, more significant, elements to allocate in the array + * @return the emitter typed with the resulting stack, i.e., having the ref pushed onto it + */ + Emitter>> genLoadToArray(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope, int slack); + + /** + * Emit bytecode to load the varnode's value, interpreted as a boolean, as an integer onto the + * JVM stack. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @return the emitter typed with the resulting stack, i.e., having the int boolean pushed onto + * it + */ + Emitter> genLoadToBool(Emitter em, JitCodeGenerator gen); + + /** + * Emit bytecode to store a value into a variable from the JVM stack. + * + * @param the JVM type of the value on the stack + * @param the p-code type of the value on the stack + * @param the tail of the incoming stack + * @param the incoming stack having the value to store on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the value on the stack + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + , FJT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genStoreFromStack(Emitter em, + JitCodeGenerator gen, FJT type, Ext ext, Scope scope); + + /** + * Emit bytecode to store a varnode's value from several locals. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param opnd the operand whose locals contain the value to be stored + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack + */ + Emitter genStoreFromOpnd(Emitter em, JitCodeGenerator gen, + Opnd opnd, Ext ext, Scope scope); + + /** + * Emit bytecode to store a varnode's value from an array of integer legs, in little endian + * order + * + * @param the tail of the incoming stack + * @param the incoming stack having the array ref on top + * @param em the emitter typed with the incoming stack + * @param gen the code generator + * @param type the p-code type of the value on the stack + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., having popped the array + */ + >> Emitter genStoreFromArray(Emitter em, + JitCodeGenerator gen, MpIntJitType type, Ext ext, Scope scope); + + /** + * Create a handler for a {@link PcodeOp#SUBPIECE} of a value. + *

+ * To implement {@link SubPieceOpGen subpiece}, we could load the entire varnode and then + * extract the designated portion. Or, we could load only the designated portion, averting any + * code and execution cost of loading the un-designated portions. We accomplish this by + * re-writing the subpiece op and a load of the sub-varnode. + * + * @param endian the endianness of the emulation target + * @param byteOffset the number of least-significant bytes to remove + * @param maxByteSize the maximum size of the resulting variable. In general, a subpiece should + * never exceed the size of the parent varnode, but if it does, this will truncate + * that excess. + * @return the resulting subpiece handler + */ + VarHandler subpiece(Endian endian, int byteOffset, int maxByteSize); +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java index 5f3ab37a8f..73bf855af1 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitAllocationModel.java @@ -16,25 +16,21 @@ package ghidra.pcode.emu.jit.analysis; import static ghidra.pcode.emu.jit.analysis.JitVarScopeModel.maxAddr; -import static ghidra.pcode.emu.jit.analysis.JitVarScopeModel.overlapsLeft; -import static org.objectweb.asm.Opcodes.*; import java.math.BigInteger; import java.util.*; -import java.util.Map.Entry; -import org.objectweb.asm.*; +import org.objectweb.asm.Opcodes; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitCompiler; +import ghidra.pcode.emu.jit.alloc.*; import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.GenConsts; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; -import ghidra.pcode.emu.jit.gen.var.VarGen; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Scope; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; import ghidra.pcode.emu.jit.var.*; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; @@ -175,486 +171,14 @@ import ghidra.program.model.pcode.Varnode; */ public class JitAllocationModel { - /** - * An allocated JVM local - * - * @param index the index reserved for this local - * @param name the human-readable name for this local - * @param type a type for this local - * @param vn the varnode whose value this local holds - */ - public record JvmLocal(int index, String name, SimpleJitType type, Varnode vn) { - - /** - * Emit bytecode into the class constructor. - * - * @param gen the code generator - * @param iv the visitor for the class constructor - */ - public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { - VarGen.generateValInitCode(gen, vn); - } - - /** - * Emit bytecode at the top of the {@link JitCompiledPassage#run(int) run} method. - * - *

- * This will declare all of the allocated locals for the entirety of the method. - * - * @param gen the code generator - * @param start a label at the top of the method - * @param end a label at the end of the method - * @param rv the visitor for the run method - */ - public void generateDeclCode(JitCodeGenerator gen, Label start, Label end, - MethodVisitor rv) { - rv.visitLocalVariable(name, Type.getDescriptor(type.javaType()), null, start, end, - index); - } - - /** - * Emit bytecode to load the varnode's value onto the JVM stack. - * - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - public void generateLoadCode(MethodVisitor rv) { - rv.visitVarInsn(type.opcodeLoad(), index); - } - - /** - * Emit bytecode to store the value on the JVM stack into the varnode. - * - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - public void generateStoreCode(MethodVisitor rv) { - rv.visitVarInsn(type.opcodeStore(), index); - } - - /** - * Emit bytecode to bring this varnode into scope. - * - *

- * This will copy the value from the {@link JitBytesPcodeExecutorState state} into the local - * variable. - * - * @param gen the code generator - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - public void generateBirthCode(JitCodeGenerator gen, MethodVisitor rv) { - VarGen.generateValReadCodeDirect(gen, type, vn, rv); - generateStoreCode(rv); - } - - /** - * Emit bytecode to take this varnode out of scope. - * - *

- * This will copy the value from the local variable into the - * {@link JitBytesPcodeExecutorState state}. - * - * @param gen the code generator - * @param rv the visitor for the {@link JitCompiledPassage#run(int)} method - */ - public void generateRetireCode(JitCodeGenerator gen, MethodVisitor rv) { - generateLoadCode(rv); - VarGen.generateValWriteCodeDirect(gen, type, vn, rv); - } - - /** - * {@return the maximum address that would be occupied by the full primitive type} - */ - public Address maxPrimAddr() { - return vn.getAddress().add(type.ext().size() - 1); - } - } - - /** - * A handler that knows how to load and store variable values onto and from the JVM stack. - */ - public interface VarHandler { - /** - * Get the p-code type of the variable this handler handles. - * - * @return the type - */ - JitType type(); - - /** - * Emit bytecode into the class constructor. - * - * @param gen the code generator - * @param iv the visitor for the class constructor - */ - void generateInitCode(JitCodeGenerator gen, MethodVisitor iv); - - /** - * If needed, emit bytecode at the top of the {@link JitCompiledPassage#run(int) run} - * method. - * - * @param gen the code generator - * @param start a label at the top of the method - * @param end a label at the end of the method - * @param rv the visitor for the run method - */ - void generateDeclCode(JitCodeGenerator gen, Label start, Label end, MethodVisitor rv); - - /** - * Emit bytecode to load the varnode's value onto the JVM stack. - * - * @param gen the code generator - * @param type the p-code type of the value expected on the JVM stack by the proceeding - * bytecode - * @param ext the kind of extension to apply when adjusting from JVM size to varnode size - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, MethodVisitor rv); - - /** - * Emit bytecode to load the varnode's value onto the JVM stack. - * - * @param gen the code generator - * @param type the p-code type of the value produced on the JVM stack by the preceding - * bytecode - * @param ext the kind of extension to apply when adjusting from varnode size to JVM size - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, MethodVisitor rv); - } - - /** - * A handler for p-code variables composed of a single JVM local variable. - */ - public interface OneLocalVarHandler extends VarHandler { - /** - * Get the local variable into which this p-code variable is allocated - * - * @return the local - */ - JvmLocal local(); - - @Override - default void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { - // Generator inits decls directly - } - - @Override - default void generateDeclCode(JitCodeGenerator gen, Label start, Label end, - MethodVisitor rv) { - // Generator calls decls directly - } - - @Override - default void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - local().generateLoadCode(rv); - TypeConversions.generate(gen, this.type(), type, ext, rv); - } - - @Override - default void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type(), ext, rv); - local().generateStoreCode(rv); - } - } - - /** - * The handler for a p-code variable allocated in one JVM {@code int}. - * - * @param local the JVM local - * @param type the p-code type - */ - public record IntVarAlloc(JvmLocal local, IntJitType type) implements OneLocalVarHandler {} - - /** - * The handler for a p-code variable allocated in one JVM {@code long}. - * - * @param local the JVM local - * @param type the p-code type - */ - public record LongVarAlloc(JvmLocal local, LongJitType type) implements OneLocalVarHandler {} - - /** - * The handler for a p-code variable allocated in one JVM {@code float}. - * - * @param local the JVM local - * @param type the p-code type - */ - public record FloatVarAlloc(JvmLocal local, FloatJitType type) implements OneLocalVarHandler {} - - /** - * The handler for a p-code variable allocated in one JVM {@code double}. - * - * @param local the JVM local - * @param type the p-code type - */ - public record DoubleVarAlloc(JvmLocal local, DoubleJitType type) - implements OneLocalVarHandler {} - - /** - * A portion of a multi-local variable handler. - * - *

- * This portion is allocated in a JVM local. When loading with a positive shift, the value is - * shifted to the right to place it into position. - * - * @param local the local variable allocated to this part - * @param shift the number of bytes and direction to shift (+ is right) - */ - public record MultiLocalSub(JvmLocal local, int shift) { - private JitType chooseLargerType(JitType t1, JitType t2) { - return t1.size() > t2.size() ? t1 : t2; - } - - /** - * Emit bytecode to load the value from this local and position it in a value on the JVM - * stack. - * - *

- * If multiple parts are to be combined, the caller should emit a bitwise or after all loads - * but the first. - * - * @param gen the code generator - * @param type the p-code type of the value expected on the stack by the proceeding - * bytecode, which may be to load additional parts - * @param ext the kind of extension to apply when adjusting from JVM size to varnode size - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - * - * @implNote We must keep temporary values in a variable of the larger of the local's or the - * expected type, otherwise bits may get dropped while positioning the value. - */ - public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - local.generateLoadCode(rv); - JitType tempType = chooseLargerType(local.type, type); - TypeConversions.generate(gen, local.type, tempType, ext, rv); - if (shift > 0) { - switch (tempType) { - case IntJitType t -> { - rv.visitLdcInsn(shift * Byte.SIZE); - rv.visitInsn(IUSHR); - } - case LongJitType t -> { - rv.visitLdcInsn(shift * Byte.SIZE); - rv.visitInsn(LUSHR); - } - default -> throw new AssertionError(); - } - } - else if (shift < 0) { - switch (tempType) { - case IntJitType t -> { - rv.visitLdcInsn(-shift * Byte.SIZE); - rv.visitInsn(ISHL); - } - case LongJitType t -> { - rv.visitLdcInsn(-shift * Byte.SIZE); - rv.visitInsn(LSHL); - } - default -> throw new AssertionError(); - } - } - TypeConversions.generate(gen, tempType, type, ext, rv); - } - - /** - * Emit bytecode to extract this part from the value on the JVM stack and store it in the - * local variable. - * - *

- * If multiple parts are to be stored, the caller should emit a {@link Opcodes#DUP dup} or - * {@link Opcodes#DUP2 dup2} before all stores but the last. - * - * @param gen the code generator - * @param type the p-code type of the value expected on the stack by the proceeding - * bytecode, which may be to load additional parts - * @param ext the kind of extension to apply when adjusting from varnode size to JVM size - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - * - * @implNote We must keep temporary values in a variable of the larger of the local's or the - * expected type, otherwise bits may get dropped while positioning the value. - */ - public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - JitType tempType = chooseLargerType(local.type, type); - TypeConversions.generate(gen, type, tempType, ext, rv); - switch (tempType) { - case IntJitType t -> { - if (shift > 0) { - rv.visitLdcInsn(shift * Byte.SIZE); - rv.visitInsn(ISHL); - } - else if (shift < 0) { - rv.visitLdcInsn(-shift * Byte.SIZE); - rv.visitInsn(IUSHR); - } - } - case LongJitType t -> { - if (shift > 0) { - rv.visitLdcInsn(shift * Byte.SIZE); - rv.visitInsn(LSHL); - } - else if (shift < 0) { - rv.visitLdcInsn(-shift * Byte.SIZE); - rv.visitInsn(LUSHR); - } - } - default -> throw new AssertionError("tempType = " + tempType); - } - TypeConversions.generate(gen, tempType, local.type, ext, rv); - switch (local.type) { - case IntJitType t -> { - int mask = -1 >>> (Integer.SIZE - Byte.SIZE * type.size()); - if (shift > 0) { - mask <<= shift * Byte.SIZE; - } - else { - mask >>>= -shift * Byte.SIZE; - } - rv.visitLdcInsn(mask); - rv.visitInsn(IAND); - local.generateLoadCode(rv); - rv.visitLdcInsn(~mask); - rv.visitInsn(IAND); - rv.visitInsn(IOR); - local.generateStoreCode(rv); - } - case LongJitType t -> { - long mask = -1L >>> (Long.SIZE - Byte.SIZE * type.size()); - if (shift > 0) { - mask <<= shift * Byte.SIZE; - } - else { - mask >>>= -shift * Byte.SIZE; - } - rv.visitLdcInsn(mask); - rv.visitInsn(LAND); - local.generateLoadCode(rv); - rv.visitLdcInsn(~mask); - rv.visitInsn(LAND); - rv.visitInsn(LOR); - local.generateStoreCode(rv); - } - default -> throw new AssertionError(); - } - } - } - - public record MultiLocalPart(List subs, SimpleJitType type) { - public void generateLoadCode(JitCodeGenerator gen, Ext ext, MethodVisitor rv) { - subs.get(0).generateLoadCode(gen, this.type, ext, rv); - for (MultiLocalSub sub : subs.subList(1, subs.size())) { - sub.generateLoadCode(gen, this.type, ext, rv); - switch (this.type) { - case IntJitType t -> rv.visitInsn(IOR); - case LongJitType t -> rv.visitInsn(LOR); - default -> throw new AssertionError("this.type = " + this.type); - } - } - TypeConversions.generate(gen, this.type, type, ext, rv); - } - - public void generateStoreCode(JitCodeGenerator gen, Ext ext, MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type, ext, rv); - for (MultiLocalSub sub : subs.subList(1, subs.size()).reversed()) { - switch (this.type) { - case IntJitType t -> rv.visitInsn(DUP); - case LongJitType t -> rv.visitInsn(DUP2); - default -> throw new AssertionError("this.type = " + this.type); - } - sub.generateStoreCode(gen, this.type, ext, rv); - } - subs.get(0).generateStoreCode(gen, this.type, ext, rv); - } - } - - /** - * The handler for a variable allocated in a composition of locals - * - *

- * This can also handle a varnode that is a subpiece of a local variable allocated for a larger - * varnode. For example, this may handle {@code EAX}, when we have allocated a {@code long} to - * hold all of {@code RAX}. - * - * @param parts the parts describing how the locals are composed - * @param type the p-code type of the (whole) variable - */ - public record MultiLocalVarHandler(List parts, JitType type) - implements VarHandler { - - @Override - public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { - // Generator calls local inits directly - } - - @Override - public void generateDeclCode(JitCodeGenerator gen, Label start, Label end, - MethodVisitor rv) { - // Generator calls local decls directly - } - - @Override - public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - for (MultiLocalPart part : parts) { - part.generateLoadCode(gen, ext, rv); - // TODO: Optimize case where last sub of cur is first sub of next - } - TypeConversions.generate(gen, this.type, type, ext, rv); - } - - @Override - public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type, ext, rv); - for (MultiLocalPart part : parts.reversed()) { - part.generateStoreCode(gen, ext, rv); - // TODO: Optimize case where last sub of cur is first sub of next - } - } - } - - /** - * A dummy handler for values/variables that are not allocated in JVM locals - */ - public enum NoHandler implements VarHandler { - /** Singleton */ - INSTANCE; - - @Override - public JitType type() { - return null; - } - - @Override - public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { - } - - @Override - public void generateDeclCode(JitCodeGenerator gen, Label start, Label end, - MethodVisitor rv) { - } - - @Override - public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - throw new AssertionError(); - } - - @Override - public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, - MethodVisitor rv) { - throw new AssertionError(); - } - } - /** * The descriptor of a p-code variable * *

* This is just a logical grouping of a varnode and its assigned p-code type. */ - private record VarDesc(int spaceId, long offset, int size, JitType type, Language language) { + private record VarDesc(int spaceId, long offset, int size, JitType type, + Language language) { /** * Create a descriptor from the given varnode and type * @@ -693,267 +217,6 @@ public class JitAllocationModel { } } - /** - * A local that is always allocated in its respective method - */ - public interface FixedLocal { - /** - * The JVM index of the local - * - * @return the index - */ - int index(); - - /** - * The name of the local - * - * @return the name - */ - String varName(); - - /** - * A JVM type descriptor for the local - * - * @param nameThis the name of this class, in case it's the this pointer. - * @return the type descriptor - */ - String typeDesc(String nameThis); - - /** - * The JVM opcode used to load the variable - * - * @return the load opcode - */ - int opcodeLoad(); - - /** - * The JVM opcode used to store the variable - * - * @return the store opcode - */ - int opcodeStore(); - - /** - * Generate the declaration of this variable. - * - *

- * This is not required, but is nice to have when debugging generated code. - * - * @param mv the method visitor - * @param nameThis the name of the class defining the containing method - * @param startLocals the start label which should be placed at the top of the method - * @param endLocals the end label which should be placed at the bottom of the method - */ - default void generateDeclCode(MethodVisitor mv, String nameThis, Label startLocals, - Label endLocals) { - mv.visitLocalVariable(varName(), typeDesc(nameThis), null, startLocals, endLocals, - index()); - } - - /** - * Generate the initialization of this variable. - * - * @param mv the method visitor - * @param nameThis the name of the class defining the containing method - */ - default void generateInitCode(MethodVisitor mv, String nameThis) { - } - - /** - * Generate a load of this variable onto the JVM stack. - * - * @param mv the method visitor - */ - default void generateLoadCode(MethodVisitor mv) { - mv.visitVarInsn(opcodeLoad(), index()); - } - - /** - * Generate a store to this variable from the JVM stack. - * - * @param mv the method visitor - */ - default void generateStoreCode(MethodVisitor mv) { - mv.visitVarInsn(opcodeStore(), index()); - } - } - - /** - * Locals that exist in every compiled passage's constructor. - */ - public enum InitFixedLocal implements FixedLocal { - /** - * Because we're compiling a non-static method, the JVM reserves index 0 for {@code this}. - */ - THIS("this", ALOAD, ASTORE) { - @Override - public String typeDesc(String nameThis) { - return "L" + nameThis + ";"; - } - }, - /** - * The parameter {@code thread} is reserved by the JVM into index 1. - */ - THREAD("thread", ALOAD, ASTORE) { - @Override - public String typeDesc(String nameThis) { - return GenConsts.TDESC_JIT_PCODE_THREAD; - } - }; - - private final String varName; - private final int opcodeLoad; - private final int opcodeStore; - - private InitFixedLocal(String varName, int opcodeLoad, int opcodeStore) { - this.varName = varName; - this.opcodeLoad = opcodeLoad; - this.opcodeStore = opcodeStore; - } - - @Override - public int index() { - return ordinal(); - } - - @Override - public String varName() { - return varName; - } - - @Override - public int opcodeLoad() { - return opcodeLoad; - } - - @Override - public int opcodeStore() { - return opcodeStore; - } - } - - /** - * Locals that exist in every compiled passage's {@link JitCompiledPassage#run(int) run} method. - */ - public enum RunFixedLocal implements FixedLocal { - /** - * Because we're compiling a non-static method, the JVM reserves index 0 for {@code this}. - */ - THIS("this", ALOAD, ASTORE) { - @Override - public String typeDesc(String nameThis) { - return "L" + nameThis + ";"; - } - }, - /** - * The parameter {@code blockId} is reserved by the JVM into index 1. - */ - BLOCK_ID("blockId", ILOAD, ISTORE) { - @Override - public String typeDesc(String nameThis) { - return Type.getDescriptor(int.class); - } - }, - /** - * We declare a local variable to indicate that a context-modifying userop has been invoked. - */ - CTXMOD("ctxmod", ILOAD, ISTORE) { - @Override - public String typeDesc(String nameThis) { - return Type.getDescriptor(boolean.class); - } - - @Override - public void generateInitCode(MethodVisitor mv, String nameThis) { - mv.visitLdcInsn(0); - mv.visitVarInsn(ISTORE, index()); - } - }; - - private final String varName; - private final int opcodeLoad; - private final int opcodeStore; - - private RunFixedLocal(String varName, int opcodeLoad, int opcodeStore) { - this.varName = varName; - this.opcodeLoad = opcodeLoad; - this.opcodeStore = opcodeStore; - } - - /** - * All of the runtime locals - */ - public static final List ALL = List.of(values()); - - @Override - public int index() { - return ordinal(); - } - - @Override - public String varName() { - return varName; - } - - @Override - public int opcodeLoad() { - return opcodeLoad; - } - - @Override - public int opcodeStore() { - return opcodeStore; - } - } - - public class JvmTempAlloc implements AutoCloseable { - final MethodVisitor mv; - final String prefix; - final Class primitiveType; - final int startIndex; - final int count; - final int step; - final Label start; - final Label end; - - JvmTempAlloc(MethodVisitor mv, String prefix, Class primitiveType, int count, - int startIndex, int step, Label start, Label end) { - this.mv = mv; - this.prefix = prefix; - this.primitiveType = primitiveType; - this.count = count; - this.startIndex = startIndex; - this.step = step; - this.start = start; - this.end = end; - } - - public int idx(int i) { - if (i >= count) { - throw new IndexOutOfBoundsException(i); - } - return startIndex + i * step; - } - - public void visitLocals() { - mv.visitLabel(end); - for (int i = 0; i < count; i++) { - String name = count == 1 ? prefix : (prefix + i); - mv.visitLocalVariable(name, Type.getDescriptor(primitiveType), null, start, end, - startIndex + step * i); - } - } - - public int getCount() { - return count; - } - - @Override - public void close() { - releaseTemp(this); - } - } - private final JitDataFlowModel dfm; private final JitVarScopeModel vsm; private final JitTypeModel tm; @@ -961,11 +224,9 @@ public class JitAllocationModel { private final SleighLanguage language; private final Endian endian; - private int nextLocal = RunFixedLocal.ALL.size(); private final Map handlers = new HashMap<>(); private final Map handlersPerVarnode = new HashMap<>(); - private final NavigableMap locals = new TreeMap<>(); - private final Deque tempAllocs = new LinkedList<>(); + private final NavigableMap> locals = new TreeMap<>(); /** * Construct the allocation model. @@ -984,7 +245,7 @@ public class JitAllocationModel { this.endian = context.getEndian(); this.language = context.getLanguage(); - allocate(); + analyze(); } /** @@ -995,82 +256,10 @@ public class JitAllocationModel { * @param desc the variable's descriptor * @return the allocated JVM local */ - private JvmLocal genFreeLocal(String name, SimpleJitType type, VarDesc desc) { - int i = nextLocal; - if (type.javaType() == long.class || type.javaType() == double.class) { - nextLocal += 2; - } - else { - nextLocal += 1; - } - return new JvmLocal(i, name, type, desc.toVarnode()); - } - - /** - * Temporarily allocate the next {@code count} indices of local variables - * - *

- * These indices are reserved only within the scope of the {@code try-with-resources} block - * creating the allocation. If the {@code primitiveType} is a {@code long} or {@code double}, - * then the number of actual indices allocated is multiplied by 2, such that the total number of - * variables is given by {@code count}. - *

- * This should be used by operator code generators after all the local variables, - * including those used to bypass {@link JitBytesPcodeExecutorState state}, have been allocated, - * or else this may generate colliding indices. These variables ought to be released before the - * next operator's code generator is invoked. - *

- * NOTE: This will automatically invoke - * {@link MethodVisitor#visitLocalVariable(String, String, String, Label, Label, int)} and place - * the appropriate labels for you. - * - * @param mv the method visitor - * @param prefix the name of the local variable, or its prefix if count > 1 - * @param primitiveType the type of each variable. NOTE: If heterogeneous allocations are - * needed, invoke this method more than once in the {@code try-with-resources} - * assignment. - * @param count the number of variables to allocate - * @return the handle to the allocation. - */ - public JvmTempAlloc allocateTemp(MethodVisitor mv, String prefix, Class primitiveType, - int count) { - if (count == 0) { - return null; - } - int startIndex = nextLocal; - int step = primitiveType == long.class || primitiveType == double.class ? 2 : 1; - int countIndices = count * step; - nextLocal += countIndices; - - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - JvmTempAlloc temp = - new JvmTempAlloc(mv, prefix, primitiveType, count, startIndex, step, start, end); - tempAllocs.push(temp); - return temp; - } - - /** - * Temporarily allocate the next {@code count} indices of local {@code int} variables - * - * @param mv the method visitor - * @param prefix the name of the local variable, or its prefix if count > 1 - * @param count the number of variables to allocate - * @return the handle to the allocation. - * @see #allocateTemp(MethodVisitor, String, Class, int) - */ - public JvmTempAlloc allocateTemp(MethodVisitor mv, String prefix, int count) { - return allocateTemp(mv, prefix, int.class, count); - } - - private void releaseTemp(JvmTempAlloc alloc) { - JvmTempAlloc popped = tempAllocs.pop(); - if (popped != alloc) { - throw new AssertionError("Temp allocations must obey stack semantics"); - } - alloc.visitLocals(); - nextLocal = alloc.startIndex; + private , JT extends SimpleJitType> JvmLocal declareLocal( + Scope scope, JT type, String name, VarDesc desc) { + Local local = scope.decl(type.bType(), name); + return JvmLocal.of(local, type, desc.toVarnode()); } /** @@ -1081,17 +270,17 @@ public class JitAllocationModel { * @param desc the (whole) variable's descriptor * @return the allocated JVM locals from most to least significant */ - private List genFreeLocals(String name, List types, - VarDesc desc) { - JvmLocal[] result = new JvmLocal[types.size()]; - Iterable it = language.isBigEndian() ? types : types.reversed(); + private , JT extends SimpleJitType> List> + declareLocals(Scope scope, List types, String name, VarDesc desc) { + @SuppressWarnings("unchecked") + JvmLocal[] result = new JvmLocal[types.size()]; + // assert types.stream().mapToInt(t -> t.size()).sum() == desc.size; long offset = desc.offset; - int i = 0; - for (SimpleJitType t : it) { + for (int i = 0; i < types.size(); i++) { + JT t = types.get(i); VarDesc d = new VarDesc(desc.spaceId, offset, t.size(), t, language); - result[i] = genFreeLocal(name + "_" + i, t, d); + result[i] = declareLocal(scope, t, name + "_" + i, d); offset += t.size(); - i++; } return List.of(result); } @@ -1149,16 +338,32 @@ public class JitAllocationModel { * @param local the local * @return the handler */ - private OneLocalVarHandler createOneLocalHandler(JvmLocal local) { - return switch (local.type) { - case IntJitType t -> new IntVarAlloc(local, t); - case LongJitType t -> new LongVarAlloc(local, t); - case FloatJitType t -> new FloatVarAlloc(local, t); - case DoubleJitType t -> new DoubleVarAlloc(local, t); + @SuppressWarnings("unchecked") + private , JT extends SimpleJitType> SimpleVarHandler + createSimpleHandler(JvmLocal local) { + return (SimpleVarHandler) switch (local.type()) { + case IntJitType t -> new IntVarAlloc(local.castOf(t), t); + case LongJitType t -> new LongVarAlloc(local.castOf(t), t); + case FloatJitType t -> new FloatVarAlloc(local.castOf(t), t); + case DoubleJitType t -> new DoubleVarAlloc(local.castOf(t), t); default -> throw new AssertionError(); }; } + private int computeByteShift(Varnode part, Varnode first, Varnode last) { + Varnode coalesced = vsm.getCoalesced(part); + if (coalesced.equals(part)) { + /** + * We could shift, but there's no point since there's no interplay with other varnodes. + */ + return 0; + } + return (int) switch (endian) { + case BIG -> maxAddr(last).subtract(maxAddr(part)); + case LITTLE -> part.getAddress().subtract(first.getAddress()); + }; + } + /** * Create a handler for a multi-part or subpiece varnode * @@ -1168,54 +373,64 @@ public class JitAllocationModel { */ private VarHandler createComplicatedHandler(Varnode vn) { JitType type = JitTypeBehavior.INTEGER.type(vn.getSize()); - NavigableMap legs = - new TreeMap<>(Comparator.comparing(Varnode::getAddress)); - switch (endian) { - case BIG -> { - Address address = vn.getAddress(); - for (SimpleJitType legType : type.legTypes()) { - Varnode legVn = new Varnode(address, legType.size()); - legs.put(legVn, new MultiLocalPart(new ArrayList<>(), legType)); - address = address.add(legType.size()); - } - } - case LITTLE -> { - Address address = maxAddr(vn); - for (SimpleJitType legType : type.legTypes()) { - address = address.subtract(legType.size() - 1); - Varnode legVn = new Varnode(address, legType.size()); - legs.put(legVn, new MultiLocalPart(new ArrayList<>(), legType)); - address = address.subtractWrap(1); - } + + Map.Entry> firstEntry = locals.floorEntry(vn.getAddress()); + assert JitVarScopeModel.overlapsLeft(firstEntry.getValue().vn(), vn); + + if (type instanceof SimpleJitType st) { + JvmLocal local = firstEntry.getValue(); + if (local.vn().contains(maxAddr(vn))) { + int byteShift = computeByteShift(vn, local.vn(), local.vn()); + return switch (st) { + case IntJitType t -> switch (local.type()) { + case IntJitType ct -> new IntInIntHandler(local.castOf(ct), t, vn, + byteShift); + case LongJitType ct -> new IntInLongHandler(local.castOf(ct), t, vn, + byteShift); + default -> throw new AssertionError(); + }; + case LongJitType t -> switch (local.type()) { + case LongJitType ct -> new LongInLongHandler(local.castOf(ct), t, vn, + byteShift); + default -> throw new AssertionError(); + }; + default -> throw new AssertionError(); + }; } } - Entry firstEntry = locals.floorEntry(vn.getAddress()); - assert overlapsLeft(firstEntry.getValue().vn, vn); + /** + * NOTE: Type is not necessarily an MpIntJitType, but we are going to use an Aligned or + * Shifted MpIntHandler. They know how to load/store primitive types to/from the stack, too. + * We do need to select the equivalently-sized mp-int type, though, which is why we can't + * always assert mp-ints have more than 2 ints (exceed a long). They should have more than + * 1, though. + */ + MpIntJitType mpType = MpIntJitType.forSize(type.size()); + assert mpType.legsAlloc() > 1; + + List> parts = new ArrayList<>(); Address min = firstEntry.getKey(); - NavigableMap sub = locals.subMap(min, true, maxAddr(vn), true); - for (JvmLocal local : sub.values()) { - Varnode startVn = legs.floorKey(local.vn); - if (startVn == null || !startVn.intersects(local.vn)) { - startVn = local.vn; - } - for (Entry ent : legs.tailMap(startVn).entrySet()) { - Varnode legVn = ent.getKey(); - if (!legVn.intersects(local.vn)) { - break; - } - int offset = (int) switch (endian) { - case BIG -> maxAddr(local.vn).subtract(maxAddr(legVn)); - case LITTLE -> legVn.getAddress().subtract(local.vn.getAddress()); - }; - ent.getValue().subs.add(new MultiLocalSub(local, offset)); - } + NavigableMap> sub = locals.subMap(min, true, maxAddr(vn), true); + for (JvmLocal local : sub.values()) { + assert local.type() instanceof IntJitType; + @SuppressWarnings("unchecked") + var localInt = (JvmLocal) local; + parts.add(localInt); } - List parts = List.copyOf(legs.values()); - return new MultiLocalVarHandler(switch (endian) { - case BIG -> parts; - case LITTLE -> parts.reversed(); - }, type); + int byteShift = computeByteShift(vn, parts.getFirst().vn(), parts.getLast().vn()); + /** + * All of the mp-int stuff assumes the lower-indexed legs are less significant, i.e., + * they're given in little-endian order. We populated parts in order of address/offset. If + * the machine is little-endian, then they are already in the correct order. If the machine + * is big-endian, then we need to reverse them. (This seems opposite the usual intuition.) + */ + if (endian == Endian.BIG) { + Collections.reverse(parts); + } + return byteShift == 0 + ? new AlignedMpIntHandler(parts, mpType, vn) + : new ShiftedMpIntHandler(parts, mpType, vn, byteShift); } /** @@ -1226,9 +441,9 @@ public class JitAllocationModel { */ private VarHandler getOrCreateHandlerForVarnodeVar(JitVarnodeVar vv) { return handlersPerVarnode.computeIfAbsent(vv.varnode(), vn -> { - JvmLocal oneLocal = locals.get(vn.getAddress()); - if (oneLocal != null && oneLocal.vn.equals(vn)) { - return createOneLocalHandler(oneLocal); + JvmLocal oneLocal = locals.get(vn.getAddress()); + if (oneLocal != null && oneLocal.vn().equals(vn)) { + return createSimpleHandler(oneLocal); } return createComplicatedHandler(vn); }); @@ -1257,15 +472,13 @@ public class JitAllocationModel { throw new AssertionError(); } - /** - * Perform the actual allocations - */ - private void allocate() { + private void analyze() { for (JitVal v : dfm.allValues()) { if (v instanceof JitVarnodeVar vv && !(v instanceof JitMemoryVar)) { Varnode vn = vv.varnode(); Varnode coalesced = vsm.getCoalesced(vn); - TypeContest tc = typeContests.computeIfAbsent(coalesced, __ -> new TypeContest()); + TypeContest tc = + typeContests.computeIfAbsent(coalesced, __ -> new TypeContest()); if (vn.equals(coalesced)) { tc.vote(tm.typeOf(v)); } @@ -1274,19 +487,30 @@ public class JitAllocationModel { } } } + } + /** + * Perform the actual allocations + * + * @param scope the (probably root) scope for declaring the locals + */ + public void allocate(Scope scope) { for (Map.Entry entry : typeContests.entrySet() .stream() .sorted(Comparator.comparing(e -> e.getKey().getAddress())) .toList()) { - VarDesc desc = VarDesc.fromVarnode(entry.getKey(), entry.getValue().winner(), language); + VarDesc desc = + VarDesc.fromVarnode(entry.getKey(), entry.getValue().winner(), language); switch (desc.type()) { - case SimpleJitType t -> { - locals.put(entry.getKey().getAddress(), genFreeLocal(desc.name(), t, desc)); + case @SuppressWarnings("rawtypes") SimpleJitType t -> { + @SuppressWarnings("unchecked") + JvmLocal local = declareLocal(scope, t, desc.name(), desc); + locals.put(entry.getKey().getAddress(), local); } case MpIntJitType t -> { - for (JvmLocal leg : genFreeLocals(desc.name(), t.legTypes(), desc)) { - locals.put(leg.vn.getAddress(), leg); + for (JvmLocal leg : declareLocals(scope, t.legTypesBE(), desc.name(), + desc)) { + locals.put(leg.vn().getAddress(), leg); } } default -> throw new AssertionError(); @@ -1294,6 +518,13 @@ public class JitAllocationModel { } for (JitVal v : dfm.allValuesSorted()) { + /** + * NOTE: We cannot cull outputs of synthetic ops here. Their outputs can (and usually + * are) consumed by real ops, and the values are assigned handlers by ref ID. Thus, the + * consuming op will need a handler for the synthetic op's output. We could consider + * keying the handlers some by an alternative (e.g., varnode when available), but that's + * for later exploration. + */ handlers.put(v, createHandler(v)); } } @@ -1313,20 +544,19 @@ public class JitAllocationModel { * * @return the locals */ - public Collection allLocals() { + public Collection> allLocals() { return locals.values(); } /** * Get all of the locals allocated for the given varnode * - * * @implNote This is used by the code generator to birth and retire the local variables, given * that scope is analyzed in terms of varnodes. * @param vn the varnode * @return the locals */ - public Collection localsForVn(Varnode vn) { + public Collection> localsForVn(Varnode vn) { Address min = vn.getAddress(); Address floor = locals.floorKey(min); if (floor != null) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java index ef7f79d971..dc3c705a5e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowArithmetic.java @@ -79,6 +79,13 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { return endian; } + /** + * Remove the given number of bytes from the higher-offset end of the varnode + * + * @param vn the varnode + * @param amt the number of bytes to remove + * @return the resulting varnode + */ public Varnode truncVnFromRight(Varnode vn, int amt) { return new Varnode(vn.getAddress(), vn.getSize() - amt); } @@ -102,6 +109,13 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { return subpiece(outVn, endian.isBigEndian() ? amt : 0, in1); } + /** + * Remove the given number of bytes from the lower-offset end of the varnode + * + * @param vn the varnode + * @param amt the number of bytes to remove + * @return the resulting varnode + */ public Varnode truncVnFromLeft(Varnode vn, int amt) { return new Varnode(vn.getAddress().add(amt), vn.getSize() - amt); } @@ -228,11 +242,25 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { return dfm.notifyOp(new JitSynthSubPieceOp(out, offset, v)).out(); } - private Varnode subPieceVn(int size, int offset, Varnode whole) { - if (endian.isBigEndian()) { - return new Varnode(whole.getAddress().add(whole.getSize() - offset - size), size); + /** + * Compute the varnode representing a {@link JitSubPieceOp subpiece} of the given varnode + * + * @param endian the endianness of the emulation target + * @param whole the whole varnode + * @param offset the number of least-significant bytes to remove + * @param size the size of the subpiece (maximum, since truncation may occur) + * @return the resulting subpiece. + */ + public static Varnode subPieceVn(Endian endian, Varnode whole, int offset, int size) { + int minSize = Math.min(whole.getSize() - offset, size); + if (minSize < 1) { + throw new AssertionError("subpiece would have non-positive size"); } - return new Varnode(whole.getAddress().add(offset), size); + int addrOffset = switch (endian) { + case BIG -> whole.getSize() - offset - minSize; + case LITTLE -> offset; + }; + return new Varnode(whole.getAddress().add(addrOffset), minSize); } /** @@ -246,7 +274,7 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @return the output */ public JitVal shaveFromRight(int amt, JitVal in1) { - return subpiece(in1.size() - amt, amt, in1); + return subpiece(in1, amt, in1.size() - amt); } /** @@ -260,7 +288,7 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @return the output */ public JitVal shaveFromLeft(int amt, JitVal in1) { - return subpiece(in1.size() - amt, 0, in1); + return subpiece(in1, 0, in1.size() - amt); } /** @@ -286,14 +314,14 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @param v the input value * @return the output value */ - public JitVal subpiece(int size, int offset, JitVal v) { + public JitVal subpiece(JitVal v, int offset, int size) { if (v instanceof JitConstVal c) { return new JitConstVal(size, OB_SUBPIECE.evaluateBinary(size, v.size(), c.value(), BigInteger.valueOf(offset))); } if (v instanceof JitVarnodeVar vv) { Varnode inVn = vv.varnode(); - Varnode outVn = subPieceVn(size, offset, inVn); + Varnode outVn = subPieceVn(endian, inVn, offset, size); return subpiece(outVn, offset, v); } throw new UnsupportedOperationException("unsupported subpiece of " + v); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java index cdf7f4e8b5..08be520340 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitType.java @@ -15,13 +15,15 @@ */ package ghidra.pcode.emu.jit.analysis; -import static org.objectweb.asm.Opcodes.*; - import java.util.*; import org.objectweb.asm.Opcodes; import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.util.Types; +import ghidra.pcode.emu.jit.gen.util.Types.*; /** * The p-code type of an operand. @@ -35,6 +37,42 @@ import ghidra.lifecycle.Unfinished; */ public interface JitType { + /** + * Get the smallest type to which both of the given types can be converted without loss. + *

+ * When the given types are a mix of integral and floating-point, this chooses an integral type + * whose size is the greater of the two. + * + * @param a the first type + * @param b the second type + * @return the uniform type + */ + static JitType unify(JitType a, JitType b) { + if (a == b) { + return a; + } + int size = Math.max(a.size(), b.size()); + return JitTypeBehavior.INTEGER.type(size); + } + + /** + * Similar to {@link #unify(JitType, JitType)}, except that it takes the lesser size. + *

+ * This is used when culling of unnecessary loads is desired and loss of precision is + * acceptable. + * + * @param a the first type + * @param b the second type + * @return the uniform type + */ + static JitType unifyLeast(JitType a, JitType b) { + if (a == b) { + return a; + } + int size = Math.min(a.size(), b.size()); + return JitTypeBehavior.INTEGER.type(size); + } + /** * Compare two types by preference. The type with the more preferred behavior then smaller size * is preferred. @@ -67,54 +105,88 @@ public interface JitType { * @see JitDataFlowUseropLibrary */ public static JitType forJavaType(Class cls) { - if (cls == boolean.class) { - return IntJitType.I4; + return SimpleJitType.forJavaType(cls); + } + + /** + * A type comprising of legs, each of simple type + * + * @param the JVM type of each leg + * @param the p-code type of each leg + */ + public interface LeggedJitType, LT extends SimpleJitType> + extends JitType { + + @Override + List legTypesBE(); + + /** + * Cast the given operand's legs as having this type's leg type. + *

+ * This is (sadly) necessary because of the loss of type information in {@link Opnd} when it + * has a legged type. + * + * @param opnd the operand whose legs to cast + * @return the legs in little-endian order. + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + default List> castLegsLE(Opnd> opnd) { + return (List) opnd.legsLE(); } - if (cls == byte.class) { - return IntJitType.I1; - } - if (cls == short.class) { - return IntJitType.I2; - } - if (cls == int.class) { - return IntJitType.I4; - } - if (cls == long.class) { - return LongJitType.I8; - } - if (cls == float.class) { - return FloatJitType.F4; - } - if (cls == double.class) { - return DoubleJitType.F8; - } - throw new IllegalArgumentException(); } /** * A p-code type that can be represented in a single JVM variable. + * + * @param the JVM type for this JIT type + * @param this JIT type (recursive) */ - public interface SimpleJitType extends JitType { + public interface SimpleJitType, JT extends SimpleJitType> + extends LeggedJitType { + + /** + * Identify the p-code type that is exactly represented by the given JVM type. + * + *

+ * This is used during Direct userop invocation to convert the arguments and return value. + * + * @param cls the primitive class (not boxed) + * @return the p-code type + * @see JitDataFlowUseropLibrary + */ + @SuppressWarnings("unchecked") + public static , JT extends SimpleJitType> JT forJavaType( + Class cls) { + if (cls == boolean.class) { + return (JT) IntJitType.I1; + } + if (cls == byte.class) { + return (JT) IntJitType.I1; + } + if (cls == short.class) { + return (JT) IntJitType.I2; + } + if (cls == int.class) { + return (JT) IntJitType.I4; + } + if (cls == long.class) { + return (JT) LongJitType.I8; + } + if (cls == float.class) { + return (JT) FloatJitType.F4; + } + if (cls == double.class) { + return (JT) DoubleJitType.F8; + } + throw new IllegalArgumentException(); + } + /** * The JVM type of the variable that can represent a p-code variable of this type * - * @return the primitive class (not boxed) + * @return the primitive type (not boxed) */ - Class javaType(); - - /** - * The JVM opcode to load a local variable of this type onto the stack - * - * @return the opcode - */ - int opcodeLoad(); - - /** - * The JVM opcode to store a local variable of this type from the stack - * - * @return the opcode - */ - int opcodeStore(); + T bType(); /** * Re-apply the {@link JitTypeBehavior#INTEGER integer} behavior to this type @@ -125,10 +197,10 @@ public interface JitType { * * @return this type as an int */ - SimpleJitType asInt(); + SimpleJitType asInt(); @Override - SimpleJitType ext(); + SimpleJitType ext(); } /** @@ -136,7 +208,7 @@ public interface JitType { * * @param size the size in bytes */ - public record IntJitType(int size) implements SimpleJitType { + public record IntJitType(int size) implements SimpleJitType { /** {@code int1}: a 1-byte integer */ public static final IntJitType I1 = new IntJitType(1); /** {@code int2}: a 2-byte integer */ @@ -183,18 +255,8 @@ public interface JitType { } @Override - public Class javaType() { - return int.class; - } - - @Override - public int opcodeLoad() { - return ILOAD; - } - - @Override - public int opcodeStore() { - return ISTORE; + public TInt bType() { + return Types.T_INT; } @Override @@ -208,7 +270,12 @@ public interface JitType { } @Override - public List legTypes() { + public List legTypesBE() { + return List.of(this); + } + + @Override + public List legTypesLE() { return List.of(this); } } @@ -218,7 +285,7 @@ public interface JitType { * * @param size the size in bytes */ - public record LongJitType(int size) implements SimpleJitType { + public record LongJitType(int size) implements SimpleJitType { /** {@code int5}: a 5-byte integer */ public static final LongJitType I5 = new LongJitType(5); /** {@code int6}: a 6-byte integer */ @@ -228,6 +295,12 @@ public interface JitType { /** {@code int8}: a 8-byte integer */ public static final LongJitType I8 = new LongJitType(8); + // These are needed only as intermediates during conversion + public static final LongJitType I1 = new LongJitType(1); + public static final LongJitType I2 = new LongJitType(2); + public static final LongJitType I3 = new LongJitType(3); + public static final LongJitType I4 = new LongJitType(4); + /** * Get the type for an integer of the given size 5 through 8 * @@ -241,6 +314,11 @@ public interface JitType { case 6 -> I6; case 7 -> I7; case 8 -> I8; + // For intermediate conversion only + case 1 -> I1; + case 2 -> I2; + case 3 -> I3; + case 4 -> I4; default -> throw new IllegalArgumentException("size:" + size); }; } @@ -265,18 +343,8 @@ public interface JitType { } @Override - public Class javaType() { - return long.class; - } - - @Override - public int opcodeLoad() { - return LLOAD; - } - - @Override - public int opcodeStore() { - return LSTORE; + public TLong bType() { + return Types.T_LONG; } @Override @@ -290,7 +358,12 @@ public interface JitType { } @Override - public List legTypes() { + public List legTypesBE() { + return List.of(this); + } + + @Override + public List legTypesLE() { return List.of(this); } } @@ -298,7 +371,7 @@ public interface JitType { /** * The p-code type for floating-point of size 4, i.e., that fits in a JVM float. */ - public enum FloatJitType implements SimpleJitType { + public enum FloatJitType implements SimpleJitType { /** {@code float4}: a 4-byte float */ F4; @@ -318,18 +391,8 @@ public interface JitType { } @Override - public Class javaType() { - return float.class; - } - - @Override - public int opcodeLoad() { - return FLOAD; - } - - @Override - public int opcodeStore() { - return FSTORE; + public TFloat bType() { + return Types.T_FLOAT; } @Override @@ -343,7 +406,12 @@ public interface JitType { } @Override - public List legTypes() { + public List legTypesBE() { + return List.of(this); + } + + @Override + public List legTypesLE() { return List.of(this); } } @@ -351,7 +419,7 @@ public interface JitType { /** * The p-code type for floating-point of size 8, i.e., that fits in a JVM double. */ - public enum DoubleJitType implements SimpleJitType { + public enum DoubleJitType implements SimpleJitType { /** {@code float8}: a 8-byte float */ F8; @@ -371,18 +439,8 @@ public interface JitType { } @Override - public Class javaType() { - return double.class; - } - - @Override - public int opcodeLoad() { - return DLOAD; - } - - @Override - public int opcodeStore() { - return DSTORE; + public TDouble bType() { + return Types.T_DOUBLE; } @Override @@ -396,7 +454,12 @@ public interface JitType { } @Override - public List legTypes() { + public List legTypesBE() { + return List.of(this); + } + + @Override + public List legTypesLE() { return List.of(this); } } @@ -410,10 +473,33 @@ public interface JitType { * no matter the language endianness. * * @param size the size in bytes + * @param legTypesBE the type of each leg, in big-endian order + * @param legTypesLE the type of each leg, in little-endian order */ - public record MpIntJitType(int size) implements JitType { + public record MpIntJitType(int size, List legTypesBE, List legTypesLE) + implements LeggedJitType { private static final Map FOR_SIZES = new HashMap<>(); + private static int legsAlloc(int size) { + return (size + Integer.BYTES - 1) / Integer.BYTES; + } + + private static int partialSize(int size) { + return size % Integer.BYTES; + } + + private static List computeLegTypesBE(int size) { + IntJitType[] types = new IntJitType[legsAlloc(size)]; + int i = 0; + if (partialSize(size) != 0) { + types[i++] = IntJitType.forSize(partialSize(size)); + } + for (; i < types.length; i++) { + types[i] = IntJitType.I4; + } + return Arrays.asList(types); + } + /** * Get the type for an integer of the given size 9 or greater * @@ -425,6 +511,14 @@ public interface JitType { return FOR_SIZES.computeIfAbsent(size, MpIntJitType::new); } + private MpIntJitType(int size, List legTypesBE) { + this(size, legTypesBE, legTypesBE.reversed()); + } + + private MpIntJitType(int size) { + this(size, computeLegTypesBE(size)); + } + @Override public int pref() { return 4; @@ -441,7 +535,7 @@ public interface JitType { * @return the total number of legs */ public int legsAlloc() { - return (size + Integer.BYTES - 1) / Integer.BYTES; + return legsAlloc(size); } /** @@ -459,20 +553,7 @@ public interface JitType { * @return the number of bytes in the partial leg, or 0 if all legs are whole */ public int partialSize() { - return size % Integer.BYTES; - } - - @Override - public List legTypes() { - IntJitType[] types = new IntJitType[legsAlloc()]; - int i = 0; - if (partialSize() != 0) { - types[i++] = IntJitType.forSize(partialSize()); - } - for (; i < types.length; i++) { - types[i] = IntJitType.I4; - } - return Arrays.asList(types); + return partialSize(size); } @Override @@ -486,7 +567,7 @@ public interface JitType { * * @param size the size in bytes */ - public record MpFloatJitType(int size) implements JitType { + public record MpFloatJitType(int size) implements LeggedJitType { private static final Map FOR_SIZES = new HashMap<>(); /** @@ -516,7 +597,12 @@ public interface JitType { } @Override - public List legTypes() { + public List legTypesBE() { + return Unfinished.TODO("MpFloat"); + } + + @Override + public List legTypesLE() { return Unfinished.TODO("MpFloat"); } } @@ -563,5 +649,16 @@ public interface JitType { * * @return the list of types, each fitting in a JVM int, in big-endian order. */ - List legTypes(); + List> legTypesBE(); + + /** + * Get the p-code type that describes the part of the variable in each leg + * + *

+ * Each whole leg will have the type {@link IntJitType#I4}, and the partial leg, if applicable, + * will have its appropriate smaller integer type. + * + * @return the list of types, each fitting in a JVM int, in little-endian order. + */ + List> legTypesLE(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java index 29254e2d70..a695179eb2 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeModel.java @@ -69,9 +69,8 @@ import ghidra.program.model.pcode.PcodeOp; * 16-byte quadruple-precision, and 32-byte octuple-precision. Some p-code types map precisely to * JVM counterparts: The 4- and 8-byte integer types map precisely to the JVM's {@code int} and * {@code long} types. Similarly, the 4- and 8-byte float types map precisely to {@code float} and - * {@code double}. TODO: The JIT translator does not currently support integral types greater - * than 8 bytes (64 bits) in size nor floating-point types other than 4 and 8 bytes (single and - * double precision) in size. + * {@code double}. TODO: The JIT translator does not currently support floating-point types + * other than 4 and 8 bytes (single and double precision) in size. * *

Signedness

*

diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java index a46fcf0d44..6a7de75cd3 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/ExceptionHandler.java @@ -15,14 +15,13 @@ */ package ghidra.pcode.emu.jit.gen; -import static org.objectweb.asm.Opcodes.ATHROW; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.program.model.pcode.PcodeOp; /** @@ -42,9 +41,9 @@ import ghidra.program.model.pcode.PcodeOp; * * @param op the op which may cause an exception * @param block the block containing the op - * @param label the label at the start of the handler + * @param lbl the label at the start of the handler */ -public record ExceptionHandler(PcodeOp op, JitBlock block, Label label) { +public record ExceptionHandler(PcodeOp op, JitBlock block, Lbl>> lbl) { /** * Construct a handler, generating a new label * @@ -52,23 +51,24 @@ public record ExceptionHandler(PcodeOp op, JitBlock block, Label label) { * @param block the block containing the op */ public ExceptionHandler(PcodeOp op, JitBlock block) { - this(op, block, new Label()); + this(op, block, Lbl.create()); } /** * Emit the handler's code into the {@link JitCompiledPassage#run(int) run} method. * + * @param the type of the compiled passage + * @param em the dead emitter + * @param localThis a handle to the local holding the {@code this} reference * @param gen the code generator - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the dead emitter */ - public void generateRunCode(JitCodeGenerator gen, MethodVisitor rv) { - rv.visitLabel(label); - // [exc] - gen.generatePassageExit(block, () -> { - rv.visitLdcInsn(gen.getAddressForOp(op).getOffset()); - }, gen.getExitContext(op), rv); - // [exc] - rv.visitInsn(ATHROW); - // [] + public Emitter genRun(Emitter em, + Local> localThis, JitCodeGenerator gen) { + return em + .emit(Lbl::placeDead, lbl) + .emit(gen::genExit, localThis, block, PcGen.loadOffset(gen.getAddressForOp(op)), + gen.getExitContext(op)) + .emit(Op::athrow); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java index 8b7624968d..503ab4445b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForArrDirect.java @@ -15,15 +15,18 @@ */ package ghidra.pcode.emu.jit.gen; -import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.program.model.address.Address; /** @@ -35,7 +38,7 @@ import ghidra.program.model.address.Address; * * @param address the address contained by the page to pre-fetch */ -public record FieldForArrDirect(Address address) implements InstanceFieldReq { +public record FieldForArrDirect(Address address) implements InstanceFieldReq> { @Override public String name() { return "arrDir_%s_%x".formatted(address.getAddressSpace().getName(), @@ -61,29 +64,27 @@ public record FieldForArrDirect(Address address) implements InstanceFieldReq { * */ @Override - public void generateInitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor iv) { - cv.visitField(ACC_PRIVATE | ACC_FINAL, name(), TDESC_BYTE_ARR, null, null); - - // [...] - InitFixedLocal.THIS.generateLoadCode(iv); - // [...,this] - gen.generateLoadJitStateSpace(address.getAddressSpace(), iv); - // [...,jitspace] - iv.visitLdcInsn(address.getOffset()); - // [...,arr] - iv.visitMethodInsn(INVOKEVIRTUAL, NAME_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, - "getDirect", MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__GET_DIRECT, false); - iv.visitFieldInsn(PUTFIELD, gen.nameThis, name(), TDESC_BYTE_ARR); - // [...] + public Emitter genInit(Emitter em, + Local> localThis, JitCodeGenerator gen, ClassVisitor cv) { + Fld.decl(cv, ACC_PRIVATE | ACC_FINAL, Types.T_BYTE_ARR, name()); + return em + .emit(Op::aload, localThis) + .emit(gen::genLoadJitStateSpace, localThis, address.getAddressSpace()) + .emit(Op::ldc__l, address.getOffset()) + .emit(Op::invokevirtual, GenConsts.T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, + "getDirect", GenConsts.MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__GET_DIRECT, + false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::putfield, gen.typeThis, name(), Types.T_BYTE_ARR); } @Override - public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { - // [...] - RunFixedLocal.THIS.generateLoadCode(rv); - // [...,this] - rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), - TDESC_BYTE_ARR); - // [...,arr] + public Emitter>> + genLoad(Emitter em, Local> localThis, JitCodeGenerator gen) { + return em + .emit(Op::aload, localThis) + .emit(Op::getfield, gen.typeThis, name(), Types.T_BYTE_ARR); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForContext.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForContext.java index a606bfc755..57a54f1e6d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForContext.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForContext.java @@ -19,16 +19,20 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.RegisterValue; /** * A field request for pre-constructed contextreg value */ -record FieldForContext(RegisterValue ctx) implements StaticFieldReq { +record FieldForContext(RegisterValue ctx) implements StaticFieldReq> { @Override public String name() { return "CTX_%s".formatted(ctx.getUnsignedValue().toString(16)); @@ -45,33 +49,28 @@ record FieldForContext(RegisterValue ctx) implements StaticFieldReq { * */ @Override - public void generateClinitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor sv) { + public Emitter genClInitCode(Emitter em, JitCodeGenerator gen, + ClassVisitor cv) { if (ctx == null) { - return; + return em; } - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, name(), TDESC_REGISTER_VALUE, null, - null); - - // [] - sv.visitFieldInsn(GETSTATIC, gen.nameThis, "LANGUAGE", TDESC_LANGUAGE); - // [language] - sv.visitLdcInsn(ctx.getUnsignedValue().toString(16)); - // [language,ctx:STR] - sv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "createContext", - MDESC_JIT_COMPILED_PASSAGE__CREATE_CONTEXT, true); - // [ctx:RV] - sv.visitFieldInsn(PUTSTATIC, gen.nameThis, name(), TDESC_REGISTER_VALUE); + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, T_REGISTER_VALUE, name()); + return em + .emit(Op::getstatic, gen.typeThis, "LANGUAGE", T_LANGUAGE) + .emit(Op::ldc__a, ctx.getUnsignedValue().toString(16)) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "createContext", + MDESC_JIT_COMPILED_PASSAGE__CREATE_CONTEXT, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::putstatic, gen.typeThis, name(), T_REGISTER_VALUE); } @Override - public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { - // [...] - if (ctx == null) { - rv.visitInsn(ACONST_NULL); - } - else { - rv.visitFieldInsn(GETSTATIC, gen.nameThis, name(), TDESC_REGISTER_VALUE); - } - // [...,ctx] + public Emitter>> genLoad(Emitter em, + JitCodeGenerator gen) { + return ctx == null + ? em.emit(Op::aconst_null, T_REGISTER_VALUE) + : em.emit(Op::getstatic, gen.typeThis, name(), T_REGISTER_VALUE); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java index 1ca157b891..0fc95b36c1 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForExitSlot.java @@ -16,19 +16,22 @@ package ghidra.pcode.emu.jit.gen; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.JitPassage.ExtBranch; import ghidra.pcode.emu.jit.JitPcodeThread; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.program.model.lang.RegisterValue; /** @@ -44,7 +47,7 @@ import ghidra.program.model.lang.RegisterValue; * * @param target the target address-contextreg pair of the branch exiting via this slot */ -public record FieldForExitSlot(AddrCtx target) implements InstanceFieldReq { +public record FieldForExitSlot(AddrCtx target) implements InstanceFieldReq> { @Override public String name() { return "exit_%x_%s".formatted(target.address.getOffset(), target.biCtx.toString(16)); @@ -72,32 +75,29 @@ public record FieldForExitSlot(AddrCtx target) implements InstanceFieldReq { * as needed. */ @Override - public void generateInitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor iv) { + public Emitter genInit(Emitter em, + Local> localThis, JitCodeGenerator gen, ClassVisitor cv) { FieldForContext ctxField = gen.requestStaticFieldForContext(target.rvCtx); - cv.visitField(ACC_PRIVATE | ACC_FINAL, name(), TDESC_EXIT_SLOT, null, null); - - // [] - InitFixedLocal.THIS.generateLoadCode(iv); - // [this] - iv.visitInsn(DUP); - // [this,this] - iv.visitLdcInsn(target.address.getOffset()); - // [this,this,target:LONG] - ctxField.generateLoadCode(gen, iv); - // [this,this,target:LONG,ctx:RV] - iv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "createExitSlot", - MDESC_JIT_COMPILED_PASSAGE__CREATE_EXIT_SLOT, true); - // [this,slot] - iv.visitFieldInsn(PUTFIELD, gen.nameThis, name(), TDESC_EXIT_SLOT); - // [] + Fld.decl(cv, ACC_PRIVATE | ACC_FINAL, T_EXIT_SLOT, name()); + return em + .emit(Op::aload, localThis) + .emit(Op::dup) + .emit(Op::ldc__l, target.address.getOffset()) + .emit(ctxField::genLoad, gen) + .emit(Op::invokeinterface, T_JIT_COMPILED_PASSAGE, "createExitSlot", + MDESC_JIT_COMPILED_PASSAGE__CREATE_EXIT_SLOT) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::putfield, gen.typeThis, name(), T_EXIT_SLOT); } @Override - public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { - // [] - RunFixedLocal.THIS.generateLoadCode(rv); - // [this] - rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), TDESC_EXIT_SLOT); - // [slot] + public Emitter>> + genLoad(Emitter em, Local> localThis, JitCodeGenerator gen) { + return em + .emit(Op::aload, localThis) + .emit(Op::getfield, gen.typeThis, name(), T_EXIT_SLOT); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java index a8776ac593..cba6e6dfa0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForSpaceIndirect.java @@ -15,15 +15,18 @@ */ package ghidra.pcode.emu.jit.gen; -import static ghidra.pcode.emu.jit.gen.GenConsts.TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE; -import static org.objectweb.asm.Opcodes.*; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.program.model.address.AddressSpace; /** @@ -36,7 +39,8 @@ import ghidra.program.model.address.AddressSpace; * * @param space the address space of the state space to pre-fetch */ -public record FieldForSpaceIndirect(AddressSpace space) implements InstanceFieldReq { +public record FieldForSpaceIndirect(AddressSpace space) + implements InstanceFieldReq> { @Override public String name() { return "spaceInd_" + space.getName(); @@ -60,27 +64,21 @@ public record FieldForSpaceIndirect(AddressSpace space) implements InstanceField * */ @Override - public void generateInitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor iv) { - cv.visitField(ACC_PRIVATE | ACC_FINAL, name(), - TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, null, null); - - // [...] - InitFixedLocal.THIS.generateLoadCode(iv); - // [...,this] - gen.generateLoadJitStateSpace(space, iv); - // [...,this,jitspace] - iv.visitFieldInsn(PUTFIELD, gen.nameThis, name(), - TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE); - // [...] + public Emitter genInit(Emitter em, + Local> localThis, JitCodeGenerator gen, ClassVisitor cv) { + Fld.decl(cv, ACC_PRIVATE | ACC_FINAL, T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, name()); + return em + .emit(Op::aload, localThis) + .emit(gen::genLoadJitStateSpace, localThis, space) + .emit(Op::putfield, gen.typeThis, name(), T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE); } @Override - public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { - // [...] - RunFixedLocal.THIS.generateLoadCode(rv); - // [...,this] - rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), - TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE); - // [...,jitspace] + public + Emitter>> + genLoad(Emitter em, Local> localThis, JitCodeGenerator gen) { + return em + .emit(Op::aload, localThis) + .emit(Op::getfield, gen.typeThis, name(), T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java index 97413715d4..03e994e11d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForUserop.java @@ -16,15 +16,18 @@ package ghidra.pcode.emu.jit.gen; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.InitFixedLocal; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitDataFlowUseropLibrary; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; /** @@ -36,7 +39,8 @@ import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; * @param userop the definition to pre-fetch * @see JitDataFlowUseropLibrary */ -public record FieldForUserop(PcodeUseropDefinition userop) implements InstanceFieldReq { +public record FieldForUserop(PcodeUseropDefinition userop) + implements InstanceFieldReq>> { @Override public String name() { return "userop_" + userop.getName(); @@ -60,30 +64,27 @@ public record FieldForUserop(PcodeUseropDefinition userop) implements Instanc * */ @Override - public void generateInitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor iv) { - cv.visitField(ACC_PRIVATE | ACC_FINAL, name(), TDESC_PCODE_USEROP_DEFINITION, null, - null); - - // [] - InitFixedLocal.THIS.generateLoadCode(iv); - // [this] - iv.visitInsn(DUP); - // [this,this] - iv.visitLdcInsn(userop.getName()); - // [this,this,name] - iv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "getUseropDefinition", - MDESC_JIT_COMPILED_PASSAGE__GET_USEROP_DEFINITION, true); - // [this,userop] - iv.visitFieldInsn(PUTFIELD, gen.nameThis, name(), TDESC_PCODE_USEROP_DEFINITION); - // [] + public Emitter genInit(Emitter em, + Local> localThis, JitCodeGenerator gen, ClassVisitor cv) { + Fld.decl(cv, ACC_PRIVATE | ACC_FINAL, T_PCODE_USEROP_DEFINITION, name()); + return em + .emit(Op::aload, localThis) + .emit(Op::dup) + .emit(Op::ldc__a, userop.getName()) + .emit(Op::invokeinterface, T_JIT_COMPILED_PASSAGE, "getUseropDefinition", + MDESC_JIT_COMPILED_PASSAGE__GET_USEROP_DEFINITION) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::putfield, gen.typeThis, name(), T_PCODE_USEROP_DEFINITION); } @Override - public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { - // [] - RunFixedLocal.THIS.generateLoadCode(rv); - // [this] - rv.visitFieldInsn(GETFIELD, gen.nameThis, name(), TDESC_PCODE_USEROP_DEFINITION); - // [userop] + public + Emitter>>> + genLoad(Emitter em, Local> localThis, JitCodeGenerator gen) { + return em + .emit(Op::aload, localThis) + .emit(Op::getfield, gen.typeThis, name(), T_PCODE_USEROP_DEFINITION); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForVarnode.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForVarnode.java index c8795beed4..a58577495f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForVarnode.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldForVarnode.java @@ -19,10 +19,14 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; import ghidra.pcode.emu.jit.analysis.JitDataFlowUseropLibrary; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressFactory; @@ -37,7 +41,7 @@ import ghidra.program.model.pcode.Varnode; * @param vn the varnode to pre-construct * @see JitDataFlowUseropLibrary */ -public record FieldForVarnode(Varnode vn) implements StaticFieldReq { +public record FieldForVarnode(Varnode vn) implements StaticFieldReq> { @Override public String name() { Address addr = vn.getAddress(); @@ -56,16 +60,22 @@ public record FieldForVarnode(Varnode vn) implements StaticFieldReq { * */ @Override - public void generateClinitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor sv) { - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, name(), TDESC_VARNODE, null, null); - - sv.visitFieldInsn(GETSTATIC, gen.nameThis, "ADDRESS_FACTORY", TDESC_ADDRESS_FACTORY); - sv.visitLdcInsn(vn.getAddress().getAddressSpace().getName()); - sv.visitLdcInsn(vn.getAddress().getOffset()); - sv.visitLdcInsn(vn.getSize()); - sv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "createVarnode", - MDESC_JIT_COMPILED_PASSAGE__CREATE_VARNODE, true); - sv.visitFieldInsn(PUTSTATIC, gen.nameThis, name(), TDESC_VARNODE); + public Emitter genClInitCode(Emitter em, JitCodeGenerator gen, + ClassVisitor cv) { + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, T_VARNODE, name()); + return em + .emit(Op::getstatic, gen.typeThis, "ADDRESS_FACTORY", T_ADDRESS_FACTORY) + .emit(Op::ldc__a, vn.getAddress().getAddressSpace().getName()) + .emit(Op::ldc__l, vn.getAddress().getOffset()) + .emit(Op::ldc__i, vn.getSize()) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "createVarnode", + MDESC_JIT_COMPILED_PASSAGE__CREATE_VARNODE, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::putstatic, gen.typeThis, name(), T_VARNODE); } /** @@ -79,7 +89,9 @@ public record FieldForVarnode(Varnode vn) implements StaticFieldReq { * */ @Override - public void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv) { - rv.visitFieldInsn(GETSTATIC, gen.nameThis, name(), TDESC_VARNODE); + public Emitter>> genLoad(Emitter em, + JitCodeGenerator gen) { + return em + .emit(Op::getstatic, gen.typeThis, name(), T_VARNODE); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldReq.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldReq.java index 1b088352a7..9963e33664 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldReq.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/FieldReq.java @@ -15,14 +15,14 @@ */ package ghidra.pcode.emu.jit.gen; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.Types.BNonVoid; /** * A field request for a pre-fetched or pre-constructed element + * + * @param the type of the field */ -public interface FieldReq { +public interface FieldReq { /** * Derive a suitable name for the field * @@ -30,12 +30,4 @@ public interface FieldReq { */ String name(); - /** - * Emit code to load the field onto the JVM stack - * - * @param gen the code generator - * @param rv the visitor often for the {@link JitCompiledPassage#run(int) run} method, but could - * be the static initializer or constructor - */ - void generateLoadCode(JitCodeGenerator gen, MethodVisitor rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java index 15f679fee8..1a8201ec48 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/GenConsts.java @@ -15,10 +15,10 @@ */ package ghidra.pcode.emu.jit.gen; +import java.io.PrintStream; import java.util.ArrayList; import java.util.List; -import org.apache.commons.lang3.reflect.TypeLiteral; import org.objectweb.asm.Type; import ghidra.generic.util.datastruct.SemisparseByteArray; @@ -28,6 +28,11 @@ import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Methods.MthDesc; +import ghidra.pcode.emu.jit.gen.util.Types; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.error.LowlevelError; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; @@ -44,201 +49,374 @@ import ghidra.program.model.pcode.Varnode; public interface GenConsts { public static final int BLOCK_SIZE = SemisparseByteArray.BLOCK_SIZE; - public static final String TDESC_ADDRESS = Type.getDescriptor(Address.class); - public static final String TDESC_ADDRESS_FACTORY = Type.getDescriptor(AddressFactory.class); - public static final String TDESC_ADDRESS_SPACE = Type.getDescriptor(AddressSpace.class); - public static final String TDESC_BYTE_ARR = Type.getDescriptor(byte[].class); - public static final String TDESC_EXIT_SLOT = Type.getDescriptor(ExitSlot.class); - public static final String TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE = - Type.getDescriptor(JitBytesPcodeExecutorState.class); - public static final String TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE = - Type.getDescriptor(JitBytesPcodeExecutorStateSpace.class); - public static final String TDESC_JIT_PCODE_THREAD = Type.getDescriptor(JitPcodeThread.class); - public static final String TDESC_LANGUAGE = Type.getDescriptor(Language.class); - public static final String TDESC_LIST = Type.getDescriptor(List.class); - public static final String TDESC_PCODE_USEROP_DEFINITION = - Type.getDescriptor(PcodeUseropDefinition.class); - public static final String TDESC_REGISTER_VALUE = Type.getDescriptor(RegisterValue.class); - public static final String TDESC_STRING = Type.getDescriptor(String.class); - public static final String TDESC_VARNODE = Type.getDescriptor(Varnode.class); + public static final TRef TARR_OBJECT = Types.refOf(Object[].class); + public static final TRef TARR_VARNODE = Types.refOf(Varnode[].class); + public static final TRef TR_DOUBLE = Types.refOf(Double.class); + public static final TRef TR_FLOAT = Types.refOf(Float.class); + public static final TRef TR_INTEGER = Types.refOf(Integer.class); + public static final TRef TR_LONG = Types.refOf(Long.class); + public static final TRef

T_ADDRESS = Types.refOf(Address.class); + public static final TRef T_ADDRESS_FACTORY = + Types.refOf(AddressFactory.class); + public static final TRef T_ADDRESS_SPACE = Types.refOf(AddressSpace.class); + public static final TRef T_ADDR_CTX = Types.refOf(AddrCtx.class); + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final TRef> T_ARRAY_LIST = (TRef) Types.refOf(ArrayList.class); + public static final TRef T_ASSERTION_ERROR = + Types.refOf(AssertionError.class); + public static final TRef T_DECODE_PCODE_EXECUTION_EXCEPTION = + Types.refOf(DecodePcodeExecutionException.class); + public static final TRef T_ENTRY_POINT = Types.refOf(EntryPoint.class); + public static final TRef T_EXIT_SLOT = Types.refOf(ExitSlot.class); + public static final TRef T_ILLEGAL_ARGUMENT_EXCEPTION = + Types.refOf(IllegalArgumentException.class); + public static final TRef T_JIT_BYTES_PCODE_EXECUTOR_STATE = + Types.refOf(JitBytesPcodeExecutorState.class); + public static final TRef // + T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE = Types.refOf(JitBytesPcodeExecutorStateSpace.class); + public static final TRef T_JIT_COMPILED_PASSAGE = + Types.refOf(JitCompiledPassage.class); + public static final TRef T_JIT_PCODE_THREAD = + Types.refOf(JitPcodeThread.class); + public static final TRef // + T_JIT_THREAD_BYTES_PCODE_EXECUTOR_STATE = Types.refOf(JitThreadBytesPcodeExecutorState.class); + public static final TRef T_LANGUAGE = Types.refOf(Language.class); + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final TRef> T_LIST = (TRef) Types.refOf(List.class); + public static final TRef T_LOWLEVEL_ERROR = Types.refOf(LowlevelError.class); + public static final TRef T_MATH = Types.refOf(Math.class); + public static final TRef T_OBJECT = Types.refOf(Object.class); + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final TRef> T_PCODE_USEROP_DEFINITION = + (TRef) Types.refOf(PcodeUseropDefinition.class); + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static final TRef> T_PCODE_USEROP_LIBRARY = + (TRef) Types.refOf(PcodeUseropLibrary.class); + public static final TRef T_PRINT_STREAM = Types.refOf(PrintStream.class); + public static final TRef T_REGISTER_VALUE = Types.refOf(RegisterValue.class); + public static final TRef T_SLEIGH_LINK_EXCEPTION = + Types.refOf(SleighLinkException.class); + public static final TRef T_STRING = Types.refOf(String.class); + public static final TRef T_SYSTEM = Types.refOf(System.class); + public static final TRef T_THROWABLE = Types.refOf(Throwable.class); + public static final TRef T_VARNODE = Types.refOf(Varnode.class); - public static final String TSIG_LIST_ADDRCTX = - JitJvmTypeUtils.typeToSignature(new TypeLiteral>() {}.value); - - public static final String MDESC_ADDR_CTX__$INIT = Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(RegisterValue.class), Type.getType(Address.class)); - public static final String MDESC_ADDRESS_FACTORY__GET_ADDRESS_SPACE = - Type.getMethodDescriptor(Type.getType(AddressSpace.class), Type.INT_TYPE); - public static final String MDESC_ADDRESS_SPACE__GET_ADDRESS = - Type.getMethodDescriptor(Type.getType(Address.class), Type.LONG_TYPE); - public static final String MDESC_ARRAY_LIST__$INIT = Type.getMethodDescriptor(Type.VOID_TYPE); + public static final MthDesc>, TRef
>> MDESC_ADDR_CTX__$INIT = + MthDesc.returns(Types.T_VOID).param(T_REGISTER_VALUE).param(T_ADDRESS).build(); + public static final MthDesc, + Ent> MDESC_ADDRESS_FACTORY__GET_ADDRESS_SPACE = + MthDesc.returns(T_ADDRESS_SPACE).param(Types.T_INT).build(); + public static final MthDesc, Ent> MDESC_ADDRESS_SPACE__GET_ADDRESS = + MthDesc.returns(T_ADDRESS).param(Types.T_LONG).build(); + public static final MthDesc MDESC_ARRAY_LIST__$INIT = + MthDesc.returns(Types.T_VOID).build(); // NOTE: The void (String) form is private.... - public static final String MDESC_ASSERTION_ERROR__$INIT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Object.class)); - public static final String MDESC_DOUBLE__DOUBLE_TO_RAW_LONG_BITS = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.DOUBLE_TYPE); - public static final String MDESC_DOUBLE__IS_NAN = - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE); - public static final String MDESC_DOUBLE__LONG_BITS_TO_DOUBLE = - Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.LONG_TYPE); - public static final String MDESC_FLOAT__FLOAT_TO_RAW_INT_BITS = - Type.getMethodDescriptor(Type.INT_TYPE, Type.FLOAT_TYPE); - public static final String MDESC_FLOAT__INT_BITS_TO_FLOAT = - Type.getMethodDescriptor(Type.FLOAT_TYPE, Type.INT_TYPE); - public static final String MDESC_FLOAT__IS_NAN = - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.FLOAT_TYPE); - public static final String MDESC_ILLEGAL_ARGUMENT_EXCEPTION__$INIT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); - public static final String MDESC_INTEGER__BIT_COUNT = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_INTEGER__COMPARE = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_INTEGER__TO_UNSIGNED_LONG = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.INT_TYPE); + public static final MthDesc>> MDESC_ASSERTION_ERROR__$INIT = + MthDesc.returns(Types.T_VOID).param(T_OBJECT).build(); + public static final MthDesc> MDESC_DOUBLE__DOUBLE_TO_RAW_LONG_BITS = + MthDesc.returns(Types.T_LONG).param(Types.T_DOUBLE).build(); + public static final MthDesc> MDESC_DOUBLE__IS_NAN = + MthDesc. derive(d -> Double.isNaN(d)) + .check(MthDesc::returns, Types.T_BOOL) + .check(MthDesc::param, Types.T_DOUBLE) + .check(MthDesc::build); + public static final MthDesc> MDESC_DOUBLE__LONG_BITS_TO_DOUBLE = + MthDesc.returns(Types.T_DOUBLE).param(Types.T_LONG).build(); + public static final MthDesc> MDESC_FLOAT__FLOAT_TO_RAW_INT_BITS = + MthDesc.returns(Types.T_INT).param(Types.T_FLOAT).build(); + public static final MthDesc> MDESC_FLOAT__INT_BITS_TO_FLOAT = + MthDesc.returns(Types.T_FLOAT).param(Types.T_INT).build(); + public static final MthDesc> MDESC_FLOAT__IS_NAN = + MthDesc. derive(d -> Float.isNaN(d)) + .check(MthDesc::returns, Types.T_BOOL) + .check(MthDesc::param, Types.T_FLOAT) + .check(MthDesc::build); + public static final MthDesc>> MDESC_ILLEGAL_ARGUMENT_EXCEPTION__$INIT = + MthDesc.returns(Types.T_VOID).param(T_STRING).build(); + public static final MthDesc> MDESC_INTEGER__BIT_COUNT = + MthDesc.derive(Integer::bitCount) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); + public static final MthDesc, TInt>> MDESC_INTEGER__COMPARE = + MthDesc.returns(Types.T_INT).param(Types.T_INT).param(Types.T_INT).build(); + public static final MthDesc> MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS = + MthDesc.derive(Integer::numberOfLeadingZeros) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); + public static final MthDesc> MDESC_INTEGER__TO_UNSIGNED_LONG = + MthDesc.returns(Types.T_LONG).param(Types.T_INT).build(); + public static final MthDesc, Ent> MDESC_INTEGER__VALUE_OF = + MthDesc.returns(TR_INTEGER).param(Types.T_INT).build(); public static final String MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE__GET_LANGUAGE = Type.getMethodDescriptor(Type.getType(Language.class)); - public static final String MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE__GET_SPACE_FOR = - Type.getMethodDescriptor(Type.getType(JitBytesPcodeExecutorStateSpace.class), - Type.getType(AddressSpace.class)); - public static final String MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__GET_DIRECT = - Type.getMethodDescriptor(Type.getType(byte[].class), Type.LONG_TYPE); - public static final String MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__READ = - Type.getMethodDescriptor(Type.getType(byte[].class), Type.LONG_TYPE, Type.INT_TYPE); - public static final String MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__WRITE = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE, Type.getType(byte[].class), - Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__COUNT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__CREATE_CONTEXT = - Type.getMethodDescriptor(Type.getType(RegisterValue.class), Type.getType(Language.class), - Type.getType(String.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__CREATE_DECODE_ERROR = - Type.getMethodDescriptor(Type.getType(DecodePcodeExecutionException.class), - Type.getType(String.class), Type.LONG_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__CREATE_EXIT_SLOT = - Type.getMethodDescriptor(Type.getType(ExitSlot.class), Type.LONG_TYPE, - Type.getType(RegisterValue.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__CREATE_VARNODE = - Type.getMethodDescriptor(Type.getType(Varnode.class), Type.getType(AddressFactory.class), - Type.getType(String.class), Type.LONG_TYPE, Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED = - Type.getMethodDescriptor(Type.getType(EntryPoint.class), Type.getType(ExitSlot.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__GET_LANGUAGE = - Type.getMethodDescriptor(Type.getType(Language.class), Type.getType(String.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__GET_USEROP_DEFINITION = - Type.getMethodDescriptor(Type.getType(PcodeUseropDefinition.class), - Type.getType(String.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PcodeUseropDefinition.class), - Type.getType(Varnode.class), Type.getType(Varnode[].class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), - Type.getType(int[].class), Type.getType(int[].class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__READ_INTX = - Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(byte[].class), Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__READ_LONGX = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.getType(byte[].class), Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__WRITE_COUNTER_AND_CONTEXT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE, Type.getType(RegisterValue.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__SET_COUNTER_AND_CONTEXT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE, Type.getType(RegisterValue.class)); - public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_INT_RAW = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_LONG_RAW = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_MP_INT = - Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(int[].class), - Type.getType(int[].class), Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(byte[].class), - Type.INT_TYPE); - public static final String MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE, Type.getType(byte[].class), - Type.INT_TYPE); - public static final String MDESC_JIT_PCODE_THREAD__GET_STATE = - Type.getMethodDescriptor(Type.getType(JitThreadBytesPcodeExecutorState.class)); - public static final String MDESC_LANGUAGE__GET_ADDRESS_FACTORY = - Type.getMethodDescriptor(Type.getType(AddressFactory.class)); + public static final MthDesc, + Ent>> MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE__GET_SPACE_FOR = + MthDesc.returns(T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE).param(T_ADDRESS_SPACE).build(); + public static final MthDesc, + Ent> MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__GET_DIRECT = + MthDesc.returns(Types.T_BYTE_ARR).param(Types.T_LONG).build(); + public static final MthDesc, + Ent, TInt>> MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__READ = + MthDesc.returns(Types.T_BYTE_ARR).param(Types.T_LONG).param(Types.T_INT).build(); + public static final MthDesc, TRef>, TInt>, + TInt>> MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__WRITE = + MthDesc.returns(Types.T_VOID) + .param(Types.T_LONG) + .param(Types.T_BYTE_ARR) + .param(Types.T_INT) + .param(Types.T_INT) + .build(); + public static final MthDesc, TInt>> MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG = + MthDesc.returns(Types.T_LONG).param(Types.T_INT).param(Types.T_INT).build(); + public static final MthDesc, TInt>> MDESC_JIT_COMPILED_PASSAGE__COUNT = + MthDesc.returns(Types.T_VOID).param(Types.T_INT).param(Types.T_INT).build(); + public static final MthDesc, + Ent>, TRef>> MDESC_JIT_COMPILED_PASSAGE__CREATE_CONTEXT = + MthDesc.returns(T_REGISTER_VALUE).param(T_LANGUAGE).param(T_STRING).build(); + public static final MthDesc, + Ent>, TLong>> MDESC_JIT_COMPILED_PASSAGE__CREATE_DECODE_ERROR = + MthDesc.returns(T_DECODE_PCODE_EXECUTION_EXCEPTION) + .param(T_STRING) + .param(Types.T_LONG) + .build(); + public static final MthDesc, + Ent, TRef>> MDESC_JIT_COMPILED_PASSAGE__CREATE_EXIT_SLOT = + MthDesc.returns(T_EXIT_SLOT).param(Types.T_LONG).param(T_REGISTER_VALUE).build(); + public static final MthDesc, + Ent>, TRef>, TLong>, + TInt>> MDESC_JIT_COMPILED_PASSAGE__CREATE_VARNODE = + MthDesc.returns(T_VARNODE) + .param(T_ADDRESS_FACTORY) + .param(T_STRING) + .param(Types.T_LONG) + .param(Types.T_INT) + .build(); + public static final MthDesc, + Ent>> MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED = + MthDesc.returns(T_ENTRY_POINT).param(T_EXIT_SLOT).build(); + public static final MthDesc, + Ent>> MDESC_JIT_COMPILED_PASSAGE__GET_LANGUAGE = + MthDesc.returns(T_LANGUAGE).param(T_STRING).build(); + public static final MthDesc>, + Ent>> MDESC_JIT_COMPILED_PASSAGE__GET_USEROP_DEFINITION = + MthDesc.returns(T_PCODE_USEROP_DEFINITION).param(T_STRING).build(); + public static final MthDesc>>, TRef>, + TRef>> MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP = + MthDesc.returns(Types.T_VOID) + .param(T_PCODE_USEROP_DEFINITION) + .param(T_VARNODE) + .param(TARR_VARNODE) + .build(); + public static final MthDesc>, TRef>, + TRef>> MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP = + MthDesc.returns(Types.T_VOID) + .param(Types.T_INT_ARR) + .param(Types.T_INT_ARR) + .param(Types.T_INT_ARR) + .build(); + public static final MthDesc>, TInt>, TInt>> MDESC_JIT_COMPILED_PASSAGE__READ_BOOL_N = + MthDesc.returns(Types.T_BOOL) + .param(Types.T_BYTE_ARR) + .param(Types.T_INT) + .param(Types.T_INT) + .build(); + public static final MthDesc>, TInt>> MDESC_JIT_COMPILED_PASSAGE__READ_INTX = + MthDesc.returns(Types.T_INT).param(Types.T_BYTE_ARR).param(Types.T_INT).build(); + public static final MthDesc>, TInt>> MDESC_JIT_COMPILED_PASSAGE__READ_LONGX = + MthDesc.returns(Types.T_LONG).param(Types.T_BYTE_ARR).param(Types.T_INT).build(); + public static final MthDesc, + TRef>> MDESC_JIT_COMPILED_PASSAGE__SET_$OR_WRITE_COUNTER_AND_CONTEXT = + MthDesc.returns(Types.T_VOID).param(Types.T_LONG).param(T_REGISTER_VALUE).build(); + public static final MthDesc, + TInt>> MDESC_JIT_COMPILED_PASSAGE__$FLAGBIT_INT_RAW = + MthDesc.returns(Types.T_INT).param(Types.T_INT).param(Types.T_INT).build(); + public static final MthDesc, + TLong>> MDESC_JIT_COMPILED_PASSAGE__$FLAGBIT_LONG_RAW = + MthDesc.returns(Types.T_LONG).param(Types.T_LONG).param(Types.T_LONG).build(); + public static final MthDesc>, TRef>, + TInt>> MDESC_JIT_COMPILED_PASSAGE__$FLAGBIT_MP_INT = + MthDesc.returns(Types.T_INT) + .param(Types.T_INT_ARR) + .param(Types.T_INT_ARR) + .param(Types.T_INT) + .build(); + + /** + * This is just to assure all the methods referred to below have the same signature. The fields + * here should not be used in any code, written, generated, or otherwise. + */ + interface WriteIntX { + WriteIntX AE1 = JitCompiledPassage::writeInt1; + WriteIntX BE2 = JitCompiledPassage::writeIntBE2; + WriteIntX BE3 = JitCompiledPassage::writeIntBE3; + WriteIntX BE4 = JitCompiledPassage::writeIntBE4; + + WriteIntX LE2 = JitCompiledPassage::writeIntLE2; + WriteIntX LE3 = JitCompiledPassage::writeIntLE3; + WriteIntX LE4 = JitCompiledPassage::writeIntLE4; + + void writeIntX(int value, byte[] arr, int offset); + } + + /** + * This is just to assure all the methods referred to below have the same signature. The fields + * here should not be used in any code, written, generated, or otherwise. + */ + interface WriteLongX { + WriteLongX AE1 = JitCompiledPassage::writeLong1; + WriteLongX BE2 = JitCompiledPassage::writeLongBE2; + WriteLongX BE3 = JitCompiledPassage::writeLongBE3; + WriteLongX BE4 = JitCompiledPassage::writeLongBE4; + WriteLongX BE5 = JitCompiledPassage::writeLongBE5; + WriteLongX BE6 = JitCompiledPassage::writeLongBE6; + WriteLongX BE7 = JitCompiledPassage::writeLongBE7; + WriteLongX BE8 = JitCompiledPassage::writeLongBE8; + + WriteLongX LE2 = JitCompiledPassage::writeLongLE2; + WriteLongX LE3 = JitCompiledPassage::writeLongLE3; + WriteLongX LE4 = JitCompiledPassage::writeLongLE4; + WriteLongX LE5 = JitCompiledPassage::writeLongLE5; + WriteLongX LE6 = JitCompiledPassage::writeLongLE6; + WriteLongX LE7 = JitCompiledPassage::writeLongLE7; + WriteLongX LE8 = JitCompiledPassage::writeLongLE8; + + void writeLongX(long value, byte[] arr, int offset); + } + + public static final MthDesc, TRef>, TInt>> MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX = + MthDesc.derive(JitCompiledPassage::writeInt1) + .check(MthDesc::returns, Types.T_VOID) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_BYTE_ARR) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); + public static final MthDesc, TRef>, TInt>> MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX = + MthDesc.derive(JitCompiledPassage::writeLong1) + .check(MthDesc::returns, Types.T_VOID) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::param, Types.T_BYTE_ARR) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); + public static final MthDesc, + Bot> MDESC_JIT_PCODE_THREAD__GET_STATE = + MthDesc.returns(T_JIT_THREAD_BYTES_PCODE_EXECUTOR_STATE).build(); + public static final MthDesc, Bot> MDESC_LANGUAGE__GET_ADDRESS_FACTORY = + MthDesc.returns(T_ADDRESS_FACTORY).build(); public static final String MDESC_LANGUAGE__GET_DEFAULT_SPACE = Type.getMethodDescriptor(Type.getType(AddressSpace.class)); - public static final String MDESC_LIST__ADD = - Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Object.class)); - public static final String MDESC_LONG__BIT_COUNT = - Type.getMethodDescriptor(Type.INT_TYPE, Type.LONG_TYPE); - public static final String MDESC_LONG__COMPARE_UNSIGNED = - Type.getMethodDescriptor(Type.INT_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); - public static final String MDESC_LONG__NUMBER_OF_LEADING_ZEROS = - Type.getMethodDescriptor(Type.INT_TYPE, Type.LONG_TYPE); - public static final String MDESC_LOW_LEVEL_ERROR__$INIT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); - public static final String MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY = - Type.getMethodDescriptor(Type.getType(PcodeUseropLibrary.class)); - public static final String MDESC_SLEIGH_LINK_EXCEPTION__$INIT = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); + public static final MthDesc>> MDESC_LIST__ADD = + MthDesc.returns(Types.T_BOOL).param(T_OBJECT).build(); + public static final MthDesc> MDESC_LONG__BIT_COUNT = + MthDesc.derive(Long::bitCount) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::build); + public static final MthDesc, TLong>> MDESC_LONG__COMPARE = + MthDesc.returns(Types.T_INT).param(Types.T_LONG).param(Types.T_LONG).build(); + public static final MthDesc> MDESC_LONG__NUMBER_OF_LEADING_ZEROS = + MthDesc.derive(Long::numberOfLeadingZeros) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::build); + public static final MthDesc>> MDESC_LOWLEVEL_ERROR__$INIT = + MthDesc.returns(Types.T_VOID).param(T_STRING).build(); + public static final MthDesc MDESC_OBJECT__$INIT = + MthDesc.returns(Types.T_VOID).build(); + public static final MthDesc>, + Bot> MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY = + MthDesc.returns(T_PCODE_USEROP_LIBRARY).build(); + public static final MthDesc>> MDESC_PRINT_STREAM__PRINTLN = + MthDesc.returns(Types.T_VOID).param(T_STRING).build(); + public static final MthDesc>> MDESC_SLEIGH_LINK_EXCEPTION__$INIT = + MthDesc.returns(Types.T_VOID).param(T_STRING).build(); + public static final MthDesc, Ent>> MDESC_STRING__FORMATTED = + MthDesc.returns(T_STRING).param(TARR_OBJECT).build(); - public static final String MDESC_$DOUBLE_UNOP = - Type.getMethodDescriptor(Type.DOUBLE_TYPE, Type.DOUBLE_TYPE); - public static final String MDESC_$FLOAT_UNOP = - Type.getMethodDescriptor(Type.FLOAT_TYPE, Type.FLOAT_TYPE); - public static final String MDESC_$INT_BINOP = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); - public static final String MDESC_$LONG_BINOP = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); - public static final String MDESC_$SHIFT_AA = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.INT_TYPE, - Type.getType(int[].class), Type.getType(int[].class)); - public static final String MDESC_$SHIFT_AJ = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.INT_TYPE, - Type.getType(int[].class), Type.LONG_TYPE); - public static final String MDESC_$SHIFT_AI = - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(int[].class), Type.INT_TYPE, - Type.getType(int[].class), Type.INT_TYPE); - public static final String MDESC_$SHIFT_JA = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.getType(int[].class)); - public static final String MDESC_$SHIFT_JJ = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); - public static final String MDESC_$SHIFT_JI = - Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.INT_TYPE); - public static final String MDESC_$SHIFT_IA = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.getType(int[].class)); - public static final String MDESC_$SHIFT_IJ = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.LONG_TYPE); - public static final String MDESC_$SHIFT_II = - Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); + public static final MthDesc> MDESC_$DOUBLE_UNOP = + MthDesc.returns(Types.T_DOUBLE).param(Types.T_DOUBLE).build(); + public static final MthDesc> MDESC_$FLOAT_UNOP = + MthDesc.returns(Types.T_FLOAT).param(Types.T_FLOAT).build(); + public static final MthDesc, TInt>> MDESC_$INT_BINOP = + MthDesc.returns(Types.T_INT).param(Types.T_INT).param(Types.T_INT).build(); + public static final MthDesc, TLong>> MDESC_$LONG_BINOP = + MthDesc.returns(Types.T_LONG).param(Types.T_LONG).param(Types.T_LONG).build(); - public static final String NAME_ADDR_CTX = Type.getInternalName(AddrCtx.class); - public static final String NAME_ADDRESS = Type.getInternalName(Address.class); - public static final String NAME_ADDRESS_FACTORY = Type.getInternalName(AddressFactory.class); - public static final String NAME_ADDRESS_SPACE = Type.getInternalName(AddressSpace.class); - public static final String NAME_ARRAY_LIST = Type.getInternalName(ArrayList.class); - public static final String NAME_ASSERTION_ERROR = Type.getInternalName(AssertionError.class); - public static final String NAME_DOUBLE = Type.getInternalName(Double.class); - public static final String NAME_EXIT_SLOT = Type.getInternalName(ExitSlot.class); - public static final String NAME_FLOAT = Type.getInternalName(Float.class); - public static final String NAME_ILLEGAL_ARGUMENT_EXCEPTION = - Type.getInternalName(IllegalArgumentException.class); - public static final String NAME_INTEGER = Type.getInternalName(Integer.class); - public static final String NAME_JIT_BYTES_PCODE_EXECUTOR_STATE = - Type.getInternalName(JitBytesPcodeExecutorState.class); - public static final String NAME_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE = - Type.getInternalName(JitBytesPcodeExecutorStateSpace.class); - public static final String NAME_JIT_COMPILED_PASSAGE = - Type.getInternalName(JitCompiledPassage.class); - public static final String NAME_JIT_PCODE_THREAD = Type.getInternalName(JitPcodeThread.class); - public static final String NAME_LANGUAGE = Type.getInternalName(Language.class); - public static final String NAME_LIST = Type.getInternalName(List.class); - public static final String NAME_LONG = Type.getInternalName(Long.class); - public static final String NAME_LOW_LEVEL_ERROR = Type.getInternalName(LowlevelError.class); - public static final String NAME_MATH = Type.getInternalName(Math.class); - public static final String NAME_OBJECT = Type.getInternalName(Object.class); - public static final String NAME_PCODE_USEROP_DEFINITION = - Type.getInternalName(PcodeUseropDefinition.class); - public static final String NAME_SLEIGH_LINK_EXCEPTION = - Type.getInternalName(SleighLinkException.class); - public static final String NAME_THROWABLE = Type.getInternalName(Throwable.class); - public static final String NAME_VARNODE = Type.getInternalName(Varnode.class); + public static final MthDesc>, TInt>, TRef>, TRef>> MDESC_$SHIFT_AA = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_VOID) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::build); + public static final MthDesc>, TInt>, TRef>, TLong>> MDESC_$SHIFT_AJ = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_VOID) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::build); + public static final MthDesc>, TInt>, TRef>, TInt>> MDESC_$SHIFT_AI = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_VOID) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); + public static final MthDesc, TRef>> MDESC_$SHIFT_JA = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_LONG) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::build); + public static final MthDesc, TLong>> MDESC_$SHIFT_JJ = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_LONG) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::build); + public static final MthDesc, TInt>> MDESC_$SHIFT_JI = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_LONG) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); + public static final MthDesc, TRef>> MDESC_$SHIFT_IA = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_INT_ARR) + .check(MthDesc::build); + public static final MthDesc, TLong>> MDESC_$SHIFT_IJ = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_LONG) + .check(MthDesc::build); + public static final MthDesc, TInt>> MDESC_$SHIFT_II = + MthDesc. derive(JitCompiledPassage::intLeft) + .check(MthDesc::returns, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::param, Types.T_INT) + .check(MthDesc::build); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/InstanceFieldReq.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/InstanceFieldReq.java index da08b69893..9df88a8cfe 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/InstanceFieldReq.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/InstanceFieldReq.java @@ -16,12 +16,21 @@ package ghidra.pcode.emu.jit.gen; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; + +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Types.BNonVoid; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; /** * An instance field request initialized in the class constructor + * + * @param the JVM type of the field */ -public interface InstanceFieldReq extends FieldReq { +public interface InstanceFieldReq extends FieldReq { /** * Emit the field declaration and its initialization bytecode * @@ -29,9 +38,27 @@ public interface InstanceFieldReq extends FieldReq { * The declaration is emitted into the class definition, and the initialization code is emitted * into the class constructor. * + * @param the type of the compiled passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference * @param gen the code generator * @param cv the visitor for the class definition - * @param iv the visitor for the class constructor + * @return the emitter typed with the incoming stack */ - void generateInitCode(JitCodeGenerator gen, ClassVisitor cv, MethodVisitor iv); + Emitter genInit(Emitter em, + Local> localThis, JitCodeGenerator gen, ClassVisitor cv); + + /** + * Emit code to load the field onto the JVM stack + * + * @param the type of the compiled passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @return the emitter typed with the resulting stack, i.e., having pushed the value + */ + Emitter> genLoad(Emitter em, + Local> localThis, JitCodeGenerator gen); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java index 4a51e8fb6f..ded00e1043 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/JitCodeGenerator.java @@ -22,6 +22,7 @@ import java.io.*; import java.lang.invoke.MethodHandles.Lookup; import java.util.*; +import org.apache.commons.lang3.reflect.TypeLiteral; import org.objectweb.asm.*; import org.objectweb.asm.util.TraceClassVisitor; @@ -31,15 +32,24 @@ import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecuto import ghidra.pcode.emu.jit.JitCompiler.Diag; import ghidra.pcode.emu.jit.JitPassage.AddrCtx; import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; +import ghidra.pcode.emu.jit.alloc.JvmLocal; import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.*; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.op.IntMultOpGen; import ghidra.pcode.emu.jit.gen.op.OpGen; +import ghidra.pcode.emu.jit.gen.op.OpGen.*; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.gen.var.ValGen; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; @@ -78,7 +88,7 @@ import ghidra.util.Msg; * target *
  • {@code static }{@link AddressFactory}{@code ADDRESS_FACTORY} - The address factory of * the language
  • - *
  • {@code static }{@link List}{@code <}{@link AddrCtx}{@code > ENTRIES} - The lsit of + *
  • {@code static }{@link List}{@code <}{@link AddrCtx}{@code > ENTRIES} - The list of * entry points
  • *
  • {@link JitPcodeThread}{@code thread} - The bound thread for this instance of the * compiled passage
  • @@ -119,13 +129,13 @@ import ghidra.util.Msg; * structure is as follows: * *
      - *
    1. Parameter declarations - {@code this} and {@code blockId}
    2. - *
    3. Allocated local declarations - declares all locals allocated by - * {@link JitAllocationModel}
    4. *
    5. Entry point dispatch - a large {@code switch} statement on the entry {@code blockId}
    6. *
    7. P-code translation - the block-by-block op-by-op translation of the p-code to bytecode
    8. *
    9. Exception handlers - exception handlers as requested by various elements of the p-code * translation
    10. + *
    11. Parameter declarations - {@code this} and {@code blockId}
    12. + *
    13. Allocated local declarations - declares all locals allocated by + * {@link JitAllocationModel}
    14. *
    * *

    Entry Point Dispatch

    @@ -174,8 +184,9 @@ import ghidra.util.Msg; * variable. TODO: It'd be nice to have a bytecode API that enforces stack structure using * the compiler (somehow), but that's probably overkill. Also, I have yet to see what the * official classfile API will bring. + * @param the type of the generated passage */ -public class JitCodeGenerator { +public class JitCodeGenerator { /** * The key for a varnode, to ensure we control the definition of {@link Object#equals(Object) * equality}. @@ -200,8 +211,10 @@ public class JitCodeGenerator { final JitAllocationModel am; final JitOpUseModel oum; - private final Map blockLabels = new HashMap<>(); + private final Map> blockLabels = new HashMap<>(); + private final Map excHandlers = new LinkedHashMap<>(); + private final Map fieldsForSpaceIndirect = new HashMap<>(); private final Map fieldsForArrDirect = new HashMap<>(); private final Map fieldsForContext = new HashMap<>(); @@ -210,12 +223,10 @@ public class JitCodeGenerator { private final Map fieldsForExitSlot = new HashMap<>(); final String nameThis; + final TRef typeThis; private final ClassWriter cw; private final ClassVisitor cv; - private final MethodVisitor clinitMv; - private final MethodVisitor initMv; - private final MethodVisitor runMv; /** * Construct a code generator for the given passage's target classfile @@ -258,6 +269,7 @@ public class JitCodeGenerator { this.nameThis = (pkgThis + "Passage$at_" + entry.address + "_" + entry.biCtx.toString(16)) .replace(":", "_") .replace(" ", "_"); + this.typeThis = Types.refExtends(JitCompiledPassage.class, "L" + nameThis + ";"); int flags = entry.address.getOffset() == JitCompiler.EXCLUDE_MAXS ? 0 : ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS; @@ -273,41 +285,26 @@ public class JitCodeGenerator { Type.getInternalName(JitCompiledPassage.class), }); - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "LANGUAGE_ID", TDESC_STRING, null, + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, T_STRING, "LANGUAGE_ID", context.getLanguage().getLanguageID().toString()); - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "LANGUAGE", TDESC_LANGUAGE, null, null); - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "ADDRESS_FACTORY", - TDESC_ADDRESS_FACTORY, null, null); - cv.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, "ENTRIES", TDESC_LIST, - TSIG_LIST_ADDRCTX, null); + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, T_LANGUAGE, "LANGUAGE"); + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, T_ADDRESS_FACTORY, "ADDRESS_FACTORY"); + Fld.decl(cv, ACC_PRIVATE | ACC_STATIC | ACC_FINAL, new TypeLiteral>() {}, + "ENTRIES"); + Fld.decl(cv, ACC_PRIVATE | ACC_FINAL, T_JIT_PCODE_THREAD, "thread"); + Fld.decl(cv, ACC_PRIVATE | ACC_FINAL, T_JIT_BYTES_PCODE_EXECUTOR_STATE, "state"); - cv.visitField(ACC_PRIVATE | ACC_FINAL, "thread", TDESC_JIT_PCODE_THREAD, null, null); - cv.visitField(ACC_PRIVATE | ACC_FINAL, "state", TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE, null, - null); - - clinitMv = cv.visitMethod(ACC_PUBLIC | ACC_STATIC, "", - Type.getMethodDescriptor(Type.VOID_TYPE), null, null); - - initMv = cv.visitMethod(ACC_PUBLIC, "", Type.getMethodDescriptor(Type.VOID_TYPE, - Type.getType(JitPcodeThread.class)), null, null); - runMv = cv.visitMethod(ACC_PUBLIC, "run", - Type.getMethodDescriptor(Type.getType(EntryPoint.class), Type.INT_TYPE), null, null); - - startStaticInitializer(); - startConstructor(); - - MethodVisitor gtMv = cw.visitMethod(ACC_PUBLIC, "thread", - Type.getMethodDescriptor(Type.getType(JitPcodeThread.class)), null, null); - gtMv.visitCode(); - // [] - RunFixedLocal.THIS.generateLoadCode(gtMv); - // [this] - gtMv.visitFieldInsn(GETFIELD, nameThis, "thread", TDESC_JIT_PCODE_THREAD); - // [thread] - gtMv.visitInsn(ARETURN); - // [] - gtMv.visitMaxs(20, 20); - gtMv.visitEnd(); + var paramsThread = new Object() { + Local> this_; + }; + var retThread = Emitter.start(typeThis, cv, ACC_PUBLIC, "thread", + MthDesc.returns(T_JIT_PCODE_THREAD).build()) + .param(Def::done, typeThis, l -> paramsThread.this_ = l); + retThread.em() + .emit(Op::aload, paramsThread.this_) + .emit(Op::getfield, typeThis, "thread", T_JIT_PCODE_THREAD) + .emit(Op::areturn, retThread.ret()) + .emit(Misc::finish); } /** @@ -364,23 +361,20 @@ public class JitCodeGenerator { * Additional {@link StaticFieldReq static fields} may be requested as the p-code translation is * emitted. */ - protected void startStaticInitializer() { - clinitMv.visitCode(); - - // [] - clinitMv.visitFieldInsn(GETSTATIC, nameThis, "LANGUAGE_ID", TDESC_STRING); - // [langID:STR] - clinitMv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "getLanguage", - MDESC_JIT_COMPILED_PASSAGE__GET_LANGUAGE, true); - // [language] - clinitMv.visitInsn(DUP); - // [language,language] - clinitMv.visitFieldInsn(PUTSTATIC, nameThis, "LANGUAGE", TDESC_LANGUAGE); - // [language] - clinitMv.visitMethodInsn(INVOKEINTERFACE, NAME_LANGUAGE, "getAddressFactory", - MDESC_LANGUAGE__GET_ADDRESS_FACTORY, true); - clinitMv.visitFieldInsn(PUTSTATIC, nameThis, "ADDRESS_FACTORY", TDESC_ADDRESS_FACTORY); - // [] + protected Emitter startClInitMethod(Emitter em) { + return em + .emit(Op::getstatic, typeThis, "LANGUAGE_ID", T_STRING) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "getLanguage", + MDESC_JIT_COMPILED_PASSAGE__GET_LANGUAGE, true) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::dup) + .emit(Op::putstatic, typeThis, "LANGUAGE", T_LANGUAGE) + .emit(Op::invokeinterface, T_LANGUAGE, "getAddressFactory", + MDESC_LANGUAGE__GET_ADDRESS_FACTORY) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::putstatic, typeThis, "ADDRESS_FACTORY", T_ADDRESS_FACTORY); } /** @@ -401,36 +395,26 @@ public class JitCodeGenerator { * Additional {@link InstanceFieldReq instance fields} may be requested as the p-code * translation is emitted. */ - protected void startConstructor() { - initMv.visitCode(); - // Object.super() - // [] - InitFixedLocal.THIS.generateLoadCode(initMv); - // [this] - initMv.visitMethodInsn(INVOKESPECIAL, NAME_OBJECT, "", - Type.getMethodDescriptor(Type.VOID_TYPE), false); - // [] - - // this.thread = thread - // [] - InitFixedLocal.THIS.generateLoadCode(initMv); - // [this] - InitFixedLocal.THREAD.generateLoadCode(initMv); - // [this,thread] - initMv.visitFieldInsn(PUTFIELD, nameThis, "thread", TDESC_JIT_PCODE_THREAD); - // [] - - // this.state = thread.getState() - // [] - InitFixedLocal.THIS.generateLoadCode(initMv); - // [this] - InitFixedLocal.THREAD.generateLoadCode(initMv); - // [this,thread] - initMv.visitMethodInsn(INVOKEVIRTUAL, NAME_JIT_PCODE_THREAD, "getState", - MDESC_JIT_PCODE_THREAD__GET_STATE, false); - // [this,state] - initMv.visitFieldInsn(PUTFIELD, nameThis, "state", TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE); - // [] + protected Emitter startInitMethod(Emitter em, Local> localThis, + Local> localThread) { + return em + // super(); + .emit(Op::aload, localThis) + .emit(Op::invokespecial, T_OBJECT, "", MDESC_OBJECT__$INIT, false) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + // this.thread = thread; + .emit(Op::aload, localThis) + .emit(Op::aload, localThread) + .emit(Op::putfield, typeThis, "thread", T_JIT_PCODE_THREAD) + // this.state = thread.getState(); + .emit(Op::aload, localThis) + .emit(Op::aload, localThread) + .emit(Op::invokevirtual, T_JIT_PCODE_THREAD, "getState", + MDESC_JIT_PCODE_THREAD__GET_STATE, false) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::putfield, typeThis, "state", T_JIT_BYTES_PCODE_EXECUTOR_STATE); } /** @@ -442,29 +426,33 @@ public class JitCodeGenerator { * {@code space} is encoded as an immediate or in the constant pool and is represented as * {@code spaceId}. * + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} * @param space the space to load at run time - * @param iv the visitor for the class constructor + * @return the emitter with ..., stateSpace */ - protected void generateLoadJitStateSpace(AddressSpace space, MethodVisitor iv) { - /** - * this.spaceInd_`space` = - * this.state.getForSpace(ADDRESS_FACTORY.getAddressSpace(`space.getSpaceID()`); - */ - InitFixedLocal.THIS.generateLoadCode(initMv); - // [...,this] - iv.visitFieldInsn(GETFIELD, nameThis, "state", - TDESC_JIT_BYTES_PCODE_EXECUTOR_STATE); - // [...,state] - iv.visitFieldInsn(GETSTATIC, nameThis, "ADDRESS_FACTORY", TDESC_ADDRESS_FACTORY); - // [...,state,factory] - iv.visitLdcInsn(space.getSpaceID()); - // [...,state,factory,spaceid] - iv.visitMethodInsn(INVOKEINTERFACE, NAME_ADDRESS_FACTORY, "getAddressSpace", - MDESC_ADDRESS_FACTORY__GET_ADDRESS_SPACE, true); - // [...,state,space] - iv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_BYTES_PCODE_EXECUTOR_STATE, "getForSpace", - MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE__GET_SPACE_FOR, true); - // [...,jitspace] + protected Emitter>> + genLoadJitStateSpace(Emitter em, Local> localThis, AddressSpace space) { + return em + /** + * return + * this.state.getForSpace(ADDRESS_FACTORY.getAddressSpace(`space.getSpaceID()`); + */ + .emit(Op::aload, localThis) + .emit(Op::getfield, typeThis, "state", T_JIT_BYTES_PCODE_EXECUTOR_STATE) + .emit(Op::getstatic, typeThis, "ADDRESS_FACTORY", T_ADDRESS_FACTORY) + .emit(Op::ldc__i, space.getSpaceID()) + .emit(Op::invokeinterface, T_ADDRESS_FACTORY, "getAddressSpace", + MDESC_ADDRESS_FACTORY__GET_ADDRESS_SPACE) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::invokeinterface, T_JIT_BYTES_PCODE_EXECUTOR_STATE, "getForSpace", + MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE__GET_SPACE_FOR) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret); } /** @@ -476,7 +464,6 @@ public class JitCodeGenerator { public FieldForSpaceIndirect requestFieldForSpaceIndirect(AddressSpace space) { return fieldsForSpaceIndirect.computeIfAbsent(space, s -> { FieldForSpaceIndirect f = new FieldForSpaceIndirect(s); - f.generateInitCode(this, cv, initMv); return f; }); } @@ -490,7 +477,6 @@ public class JitCodeGenerator { public FieldForArrDirect requestFieldForArrDirect(Address address) { return fieldsForArrDirect.computeIfAbsent(address, a -> { FieldForArrDirect f = new FieldForArrDirect(a); - f.generateInitCode(this, cv, initMv); return f; }); } @@ -504,7 +490,6 @@ public class JitCodeGenerator { protected FieldForContext requestStaticFieldForContext(RegisterValue ctx) { return fieldsForContext.computeIfAbsent(ctx, c -> { FieldForContext f = new FieldForContext(ctx); - f.generateClinitCode(this, cv, clinitMv); return f; }); } @@ -518,7 +503,6 @@ public class JitCodeGenerator { public FieldForVarnode requestStaticFieldForVarnode(Varnode vn) { return fieldsForVarnode.computeIfAbsent(new VarnodeKey(vn), vk -> { FieldForVarnode f = new FieldForVarnode(vn); - f.generateClinitCode(this, cv, clinitMv); return f; }); } @@ -532,7 +516,6 @@ public class JitCodeGenerator { public FieldForUserop requestFieldForUserop(PcodeUseropDefinition userop) { return fieldsForUserop.computeIfAbsent(userop.getName(), n -> { FieldForUserop f = new FieldForUserop(userop); - f.generateInitCode(this, cv, initMv); return f; }); } @@ -546,7 +529,6 @@ public class JitCodeGenerator { public FieldForExitSlot requestFieldForExitSlot(AddrCtx target) { return fieldsForExitSlot.computeIfAbsent(target, t -> { FieldForExitSlot f = new FieldForExitSlot(t); - f.generateInitCode(this, cv, initMv); return f; }); } @@ -557,8 +539,8 @@ public class JitCodeGenerator { * @param block the block * @return the label */ - public Label labelForBlock(JitBlock block) { - return blockLabels.computeIfAbsent(block, b -> new Label()); + public Lbl labelForBlock(JitBlock block) { + return blockLabels.computeIfAbsent(block, b -> Lbl.create()); } /** @@ -577,74 +559,187 @@ public class JitCodeGenerator { * * @param v the value from the use-def graph */ - protected void generateValInitCode(JitVal v) { - ValGen.lookup(v).generateValInitCode(this, v, initMv); + protected Emitter genValInit(Emitter em, Local> localThis, + JitVal v) { + return ValGen.lookup(v).genValInit(em, localThis, this, v); } /** - * Emit into the {@link JitCompiledPassage#run(int) run} method the bytecode to read the given - * value onto the JVM stack. + * Emit bytecode to read the given value onto the JVM stack. * *

    * Although the value may be assigned a type by the {@link JitTypeModel}, the type needed by a - * given op might be different. This method accepts the {@link JitTypeBehavior} for the operand - * and will ensure the value pushed onto the JVM stack is compatible with that type. + * given op might be different. * - * @param v the value to read - * @param typeReq the required type of the value - * @param ext the kind of extension to apply when adjusting from JVM size to varnode size - * @return the actual type of the value on the stack + * @param the required JVM type of the value + * @param the required p-code type of the value + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (source) value to read + * @param type the required p-code type of the value + * @param ext the kind of extension to apply + * @return the code visitor typed with the resulting stack, i.e., having pushed the value */ - public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq, Ext ext) { - return ValGen.lookup(v).generateValReadCode(this, v, typeReq, ext, runMv); + public , JT extends SimpleJitType, N extends Next> Emitter> + genReadToStack(Emitter em, Local> localThis, JitVal v, JT type, Ext ext) { + return ValGen.lookup(v).genReadToStack(em, localThis, this, v, type, ext); } /** - * Emit into the {@link JitCompiledPassage#run(int) run} method the bytecode to write the value - * on the JVM stack into the given variable. + * Emit bytecode to read the given value into a series of locals + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (source) value to read + * @param type the required p-code type of the value + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the operand containing the locals, and the emitter typed with the incoming stack + */ + public OpndEm genReadToOpnd(Emitter em, + Local> localThis, JitVal v, MpIntJitType type, Ext ext, Scope scope) { + return ValGen.lookup(v).genReadToOpnd(em, localThis, this, v, type, ext, scope); + } + + /** + * Emit bytecode to load one leg of a multi-precision value from the varnode onto the JVM stack. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (source) value to read + * @param type the p-code type of the complete multi-precision value + * @param leg the index of the leg to load, 0 being least significant + * @param ext the kind of extension to apply + * @return the emitter typed with the resulting stack, i.e., having the int leg pushed onto it + */ + public Emitter> genReadLegToStack(Emitter em, + Local> localThis, JitVal v, MpIntJitType type, int leg, Ext ext) { + return ValGen.lookup(v).genReadLegToStack(em, localThis, this, v, type, leg, ext); + } + + /** + * Emit bytecode to load the varnode's value into an integer array in little-endian order, + * pushing its ref onto the JVM stack. + *

    + * Ideally, multi-precision integers should be loaded into a series of locals, i.e., using + * {@link #genReadToOpnd(Emitter, Local, JitVal, MpIntJitType, Ext, Scope)}, but this may not + * always be the best course of action. The first case is for userops, where it'd be onerous and + * counter-intuitive for a user to receive a single varnode in several parameters. The + * annotation system to sort that all out would also be atrocious and not easily made compatible + * with non-JIT emulation. Instead, mp-int arguments are received via {@code int[]} parameters. + * + * The second case is for more complicated p-code ops. One notable example is + * {@link IntMultOpGen int_mult}. Theoretically, yes, we could emit all of the operations to + * compute the product using long multiplication inline; however, for large operands, that would + * produce an enormous number of bytecodes. Given the 64KB-per-method limit, we could quickly + * squeeze ourselves out of efficient translation of lengthy passages. The {@code slack} + * parameter is provided since some of these algorithms (e.g., division) need an extra leg as + * scratch space. If we don't allocate it here, we force complexity into the implementation, as + * it would need to provide its own locals or re-allocate and copy the array. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (source) value to read + * @param type the p-code type of the complete multi-precision value + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @param slack the number of additional, more significant, elements to allocate in the array + * @return the emitter typed with the resulting stack, i.e., having the ref pushed onto it + */ + public Emitter>> genReadToArray(Emitter em, + Local> localThis, JitVal v, MpIntJitType type, Ext ext, Scope scope, + int slack) { + return ValGen.lookup(v).genReadToArray(em, localThis, this, v, type, ext, scope, slack); + } + + /** + * Emit bytecode to load the varnode's value, interpreted as a boolean, as an integer onto the + * JVM stack. + *

    + * Any non-zero value is considered true, though ideally, slaspec authors should ensure all + * booleans are 1) 1-byte ints, and 2) only ever take the value 0 (false) or 1 (true). + * Nevertheless, we can't assume this guidance is followed. When we know a large (esp. + * multi-precision) variable is being used as a boolean, we have some opportunity for + * short-circuiting. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (source) value to read + * @return the emitter typed with the resulting stack, i.e., having the int boolean pushed onto + * it + */ + public Emitter> genReadToBool(Emitter em, + Local> localThis, JitVal v) { + return ValGen.lookup(v).genReadToBool(em, localThis, this, v); + } + + /** + * Emit bytecode to write the value on the JVM stack into the given variable. * *

    * Although the destination variable may be assigned a type by the {@link JitTypeModel}, the * type of the value on the stack may not match. This method needs to know that type so that, if - * necessary, it can convert it to the appropriate JVM type for local variable that holds it. + * necessary, it can convert it to the appropriate JVM type for the local variable that holds + * it. * - * @param v the variable to write - * @param type the actual type of the value on the stack - * @param ext the kind of extension to apply when adjusting from varnode size to JVM size + * @param the JVM type of the value on the stack + * @param the p-code type of the value on the stack + * @param the tail of the incoming stack + * @param the incoming stack having the value on top + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (destination) variable to write + * @param type the p-code type of the value on the stack + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., having popped the value */ - public void generateVarWriteCode(JitVar v, JitType type, Ext ext) { - VarGen.lookup(v).generateVarWriteCode(this, v, type, ext, runMv); + public , JT extends SimpleJitType, N1 extends Next, + N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitVar v, JT type, Ext ext, Scope scope) { + return VarGen.lookup(v).genWriteFromStack(em, localThis, this, v, type, ext, scope); } /** - * Emit all the bytecode for the constructor + * Emit bytecode to store a varnode's value from several locals. * - *

    - * Note that some elements of the p-code translation may request additional bytecodes to be - * emitted, even after this method is finished. That code will be emitted at the time requested. - * - *

    - * To ensure a reasonable order, for debugging's sake, we request fields (and their - * initializations) for all the variables and values before iterating over the ops. This - * ensures, e.g., locals are declared in order of address for the varnodes they hold. Similarly, - * the pre-fetched byte arrays, whether for uniques, registers, or memory are initialized in - * order of address. Were these requests not made, they'd still get requested by the op - * generators, but the order would be less helpful. + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (destination) variable to write + * @param opnd the operand whose locals contain the value to be stored + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack */ - protected void generateInitCode() { - for (JvmLocal local : am.allLocals()) { - local.generateInitCode(this, initMv); - } - for (JitVal v : dfm.allValuesSorted()) { - generateValInitCode(v); - } - for (PcodeOp op : context.getPassage().getCode()) { - JitOp jitOp = dfm.getJitOp(op); - if (!oum.isUsed(jitOp)) { - continue; - } - OpGen.lookup(jitOp).generateInitCode(this, jitOp, initMv); - } + public Emitter genWriteFromOpnd(Emitter em, Local> localThis, + JitVar v, Opnd opnd, Ext ext, Scope scope) { + return VarGen.lookup(v).genWriteFromOpnd(em, localThis, this, v, opnd, ext, scope); + } + + /** + * Emit bytecode to store a varnode's value from an array of integer legs, in little endian + * order + * + * @param the tail of the incoming stack + * @param the incoming stack having the array ref on top + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param v the (destination) variable to write + * @param type the p-code type of the value on the stack + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., having popped the array + */ + public >> Emitter genWriteFromArray( + Emitter em, Local> localThis, JitVar v, MpIntJitType type, Ext ext, + Scope scope) { + return VarGen.lookup(v).genWriteFromArray(em, localThis, this, v, type, ext, scope); } /** @@ -663,76 +758,121 @@ public class JitCodeGenerator { * (ab)use a filename field to encode debug information. We can encode the op index into the * (integer) line number, although we have to add 1 to make it strictly positive. * + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param localCtxmod a handle to the local holding {@code ctxmod} + * @param retReq an indication of what must be returned by this + * {@link JitCompiledPassage#run(int)} method. * @param op the op * @param block the block containing the op * @param opIdx the index of the op within the whole passage */ - protected void generateCodeForOp(PcodeOp op, JitBlock block, int opIdx) { + protected OpResult genOp(Emitter em, Local> localThis, Local localCtxmod, + RetReq> retReq, PcodeOp op, JitBlock block, int opIdx) { JitOp jitOp = dfm.getJitOp(op); if (!oum.isUsed(jitOp)) { - return; + return new LiveOpResult(em); + } + try (SubScope scope = em.rootScope().sub()) { + return em + .emit(Misc::lineNumber, opIdx) + .emit(OpGen.lookup(jitOp)::genRun, localThis, localCtxmod, retReq, this, jitOp, + block, scope); } - Label lblLine = new Label(); - runMv.visitLabel(lblLine); - runMv.visitLineNumber(opIdx, lblLine); - OpGen.lookup(jitOp).generateRunCode(this, jitOp, block, runMv); } /** * Emit the bytecode translation for the ops in the given p-code block * *

    - * This simply invoked {@link #generateCodeForOp(PcodeOp, JitBlock, int)} on each op in the - * block and counts up the indices. Other per-block instrumentation is not included. + * This simply invokes {@link #genOp(Emitter, Local, Local, RetReq, PcodeOp, JitBlock, int)} on + * each op in the block and counts up the indices. Other per-block instrumentation is not + * included. * + * @param em the emitter + * @param localThis a handle to {@code this} + * @param localCtxmod a handle to {@code ctxmod} + * @param retReq the required return type, in case an op needs to exit the passage * @param block the block * @param opIdx the index, within the whole passage, of the first op in the block - * @return the index, within the whole passage, of the op immediately after the block - * @see #generateCodeForBlock(JitBlock, int) + * @return the result of block generation + * @see #genBlock(OpResult, Local, Local, RetReq, JitBlock, int) */ - protected int generateCodeForBlockOps(JitBlock block, int opIdx) { + protected GenBlockResult genBlockOps(Emitter em, Local> localThis, + Local localCtxmod, RetReq> retReq, JitBlock block, int opIdx) { + OpResult result = new LiveOpResult(em); for (PcodeOp op : block.getCode()) { - generateCodeForOp(op, block, opIdx); + if (!(result instanceof LiveOpResult live)) { + throw new AssertionError("Control flow died mid-block"); + } + result = genOp(live.em(), localThis, localCtxmod, retReq, op, block, opIdx); opIdx++; } - return opIdx; + return new GenBlockResult(opIdx, result); } + /** + * The result of generating code for a block of p-code ops + * + * @param opIdx the index of the next op + * @param opResult the result of op generation, indicating whether or not control flow can fall + * through + */ + record GenBlockResult(int opIdx, OpResult opResult) {} + /** * Emit the bytecode translation for the given p-code block * *

    * This checks if the block needs a label, i.e., it is an entry or the target of a branch, and * then optionally emits an invocation of {@link JitCompiledPassage#count(int, int)}. Finally, - * it emits the actual ops' translations via {@link #generateCodeForBlockOps(JitBlock, int)}. + * it emits the actual ops' translations via + * {@link #genBlockOps(Emitter, Local, Local, RetReq, JitBlock, int)}. * * @param block the block * @param opIdx the index, within the whole passage, of the first op in the block * @return the index, within the whole passage, of the op immediately after the block */ - protected int generateCodeForBlock(JitBlock block, int opIdx) { + protected GenBlockResult genBlock(OpResult prev, Local> localThis, + Local localCtxmod, RetReq> retReq, JitBlock block, int opIdx) { + LiveOpResult live; if (block.hasJumpTo() || getOpEntry(block.first()) != null) { - Label start = labelForBlock(block); - runMv.visitLabel(start); + live = new LiveOpResult(switch (prev) { + case DeadOpResult r -> r.em().emit(Lbl::placeDead, labelForBlock(block)); + case LiveOpResult r -> r.em().emit(Lbl::place, labelForBlock(block)); + }); + } + else if (prev instanceof LiveOpResult r) { + live = r; + } + else { + Msg.warn(this, "No control flow into block " + block.start()); + return new GenBlockResult(opIdx, prev); + //throw new AssertionError("No control flow into a block"); } + Emitter em; if (block.first() instanceof DecodedPcodeOp first && context.getConfiguration().emitCounters()) { - final Label tryStart = new Label(); - final Label tryEnd = new Label(); - runMv.visitTryCatchBlock(tryStart, tryEnd, - requestExceptionHandler(first, block).label(), NAME_THROWABLE); + ExceptionHandler handler = requestExceptionHandler(first, block); - runMv.visitLabel(tryStart); - RunFixedLocal.THIS.generateLoadCode(runMv); - runMv.visitLdcInsn(block.instructionCount()); - runMv.visitLdcInsn(block.trailingOpCount()); - runMv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "count", - MDESC_JIT_COMPILED_PASSAGE__COUNT, true); - runMv.visitLabel(tryEnd); + var tryCatch = Misc.tryCatch(live.em(), Lbl.create(), handler.lbl(), T_THROWABLE); + em = tryCatch.em() + .emit(Op::aload, localThis) + .emit(Op::ldc__i, block.instructionCount()) + .emit(Op::ldc__i, block.trailingOpCount()) + .emit(Op::invokeinterface, T_JIT_COMPILED_PASSAGE, "count", + MDESC_JIT_COMPILED_PASSAGE__COUNT) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Lbl::place, tryCatch.end()); } - - return generateCodeForBlockOps(block, opIdx); + else { + em = live.em(); + } + return genBlockOps(em, localThis, localCtxmod, retReq, block, opIdx); } /** @@ -741,28 +881,31 @@ public class JitCodeGenerator { *

    * Note this does not load the identical address, but reconstructs it at run time. * + * @param the tail of the stack (...) + * @param em the emitter * @param address the address to load - * @param mv the visitor for the method being generated + * @return the emitter with ..., address */ - protected void generateAddress(Address address, MethodVisitor mv) { + protected Emitter>> genAddress(Emitter em, + Address address) { if (address == Address.NO_ADDRESS) { - mv.visitFieldInsn(GETSTATIC, NAME_ADDRESS, "NO_ADDRESS", TDESC_ADDRESS); - return; + return em + .emit(Op::getstatic, T_ADDRESS, "NO_ADDRESS", T_ADDRESS); } - - // [] - mv.visitFieldInsn(GETSTATIC, nameThis, "ADDRESS_FACTORY", TDESC_ADDRESS_FACTORY); - // [factory] - mv.visitLdcInsn(address.getAddressSpace().getSpaceID()); - // [factory,spaceid] - mv.visitMethodInsn(INVOKEINTERFACE, NAME_ADDRESS_FACTORY, "getAddressSpace", - MDESC_ADDRESS_FACTORY__GET_ADDRESS_SPACE, true); - // [space] - mv.visitLdcInsn(address.getOffset()); - // [space,offset] - mv.visitMethodInsn(INVOKEINTERFACE, NAME_ADDRESS_SPACE, "getAddress", - MDESC_ADDRESS_SPACE__GET_ADDRESS, true); - // [addr] + return em + .emit(Op::getstatic, typeThis, "ADDRESS_FACTORY", T_ADDRESS_FACTORY) + .emit(Op::ldc__i, address.getAddressSpace().getSpaceID()) + .emit(Op::invokeinterface, T_ADDRESS_FACTORY, "getAddressSpace", + MDESC_ADDRESS_FACTORY__GET_ADDRESS_SPACE) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::ldc__l, address.getOffset()) + .emit(Op::invokeinterface, T_ADDRESS_SPACE, "getAddress", + MDESC_ADDRESS_SPACE__GET_ADDRESS) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret); } /** @@ -784,27 +927,24 @@ public class JitCodeGenerator { * * @param entry the entry point to add */ - protected void generateStaticEntry(AddrCtx entry) { + protected Emitter genStaticEntry(Emitter em, AddrCtx entry) { FieldForContext ctxField = requestStaticFieldForContext(entry.rvCtx); - - // [] - clinitMv.visitFieldInsn(GETSTATIC, nameThis, "ENTRIES", TDESC_LIST); - // [entries] - clinitMv.visitTypeInsn(NEW, NAME_ADDR_CTX); - // [entries,addrCtx:NEW] - clinitMv.visitInsn(DUP); - // [entries,addrCtx:NEW,addrCtx:NEW] - ctxField.generateLoadCode(this, clinitMv); - // [entries,addrCtx:NEW,addrCtx:NEW,ctx] - generateAddress(entry.address, clinitMv); - // [entries,addrCtx:NEW,addrCtx:NEW,ctx,addr] - clinitMv.visitMethodInsn(INVOKESPECIAL, NAME_ADDR_CTX, "", MDESC_ADDR_CTX__$INIT, - false); - // [entries,addrCtx:NEW] - clinitMv.visitMethodInsn(INVOKEINTERFACE, NAME_LIST, "add", MDESC_LIST__ADD, true); - // [result:BOOL] - clinitMv.visitInsn(POP); - // [] + return em + .emit(Op::getstatic, typeThis, "ENTRIES", T_LIST) + .emit(Op::new_, T_ADDR_CTX) + .emit(Op::dup) + .emit(ctxField::genLoad, this) + .emit(this::genAddress, entry.address) + .emit(Op::invokespecial, T_ADDR_CTX, "", MDESC_ADDR_CTX__$INIT, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::invokeinterface, T_LIST, "add", MDESC_LIST__ADD) + .step(Inv::takeRefArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::pop); } /** @@ -815,24 +955,83 @@ public class JitCodeGenerator { * block representing a possible entry, it adds an element giving the address and contextreg * value for the first op of that block. */ - protected void generateStaticEntries() { - // [] - clinitMv.visitTypeInsn(NEW, NAME_ARRAY_LIST); - // [entries:NEW] - clinitMv.visitInsn(DUP); - // [entries:NEW,entries:NEW] - clinitMv.visitMethodInsn(INVOKESPECIAL, NAME_ARRAY_LIST, "", MDESC_ARRAY_LIST__$INIT, - false); - // [entries:NEW] - clinitMv.visitFieldInsn(PUTSTATIC, nameThis, "ENTRIES", TDESC_LIST); - // [] + protected Emitter genStaticEntries(Emitter em) { + em = em + .emit(Op::new_, T_ARRAY_LIST) + .emit(Op::dup) + .emit(Op::invokespecial, T_ARRAY_LIST, "", MDESC_ARRAY_LIST__$INIT, false) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::putstatic, typeThis, "ENTRIES", T_LIST); for (JitBlock block : cfm.getBlocks()) { AddrCtx entry = getOpEntry(block.first()); if (entry != null) { - generateStaticEntry(entry); + em = genStaticEntry(em, entry); } } + return em; + } + + /** + * Emit bytecode for the static initializer + *

    + * Note that this method must be called after the bytecode for the run method is generated, + * because that generator may request various static fields to be created and initialized. Those + * requests are not known until the run-method generator has finished. + * + * @param em the emitter + * @return the emitter + */ + protected Emitter genClInitMethod(Emitter em) { + for (FieldForContext fCtx : fieldsForContext.values()) { + em = fCtx.genClInitCode(em, this, cv); + } + for (FieldForVarnode fVn : fieldsForVarnode.values()) { + em = fVn.genClInitCode(em, this, cv); + } + return em; + } + + /** + * Emit all the bytecode for the constructor + *

    + * Note that this must be called after the bytecode for the run method is generated, because + * that generator may request various instance fields to be created and initialized. Those + * requests are not known until the run-method generator has finished. + *

    + * To ensure a reasonable order, for debugging's sake, we request fields (and their + * initializations) for all the variables and values before iterating over the ops. This + * ensures, e.g., locals are declared in order of address for the varnodes they hold. Similarly, + * the pre-fetched byte arrays, whether for uniques, registers, or memory are initialized in + * order of address. Were these requests not made, they'd still get requested by the op + * generators, but the order would be less helpful. + */ + protected Emitter genInitMethod(Emitter em, Local> localThis) { + // NOTE: Ops don't need init. They'll invoke field requests as needed. + + // Locals and values first, because they may request fields + for (JvmLocal local : am.allLocals()) { + em = local.genInit(em, this); + } + for (JitVal v : dfm.allValuesSorted()) { + em = genValInit(em, localThis, v); + } + + for (FieldForArrDirect fArr : fieldsForArrDirect.values()) { + em = fArr.genInit(em, localThis, this, cv); + } + for (FieldForExitSlot fExit : fieldsForExitSlot.values()) { + em = fExit.genInit(em, localThis, this, cv); + } + for (FieldForSpaceIndirect fSpace : fieldsForSpaceIndirect.values()) { + em = fSpace.genInit(em, localThis, this, cv); + } + for (FieldForUserop fUserop : fieldsForUserop.values()) { + em = fUserop.genInit(em, localThis, this, cv); + } + + return em; } /** @@ -843,101 +1042,106 @@ public class JitCodeGenerator { * all the locals allocated by the {@link JitAllocationModel}. It then collects the list of * entries points and assigns a label to each. These are used when emitting the entry dispatch * code. Several of those labels may also be re-used when translating branch ops. We must - * iterate over the blocks in the same order as {@link #generateStaticEntries()}, so that our - * indices and its match. Thus, we emit a {@link Opcodes#TABLESWITCH tableswitch} where each - * value maps to the blocks label identified in the same position of the {@code ENTRIES} field. - * We also provide a default case that just throws an {@link IllegalArgumentException}. We do - * not jump directly to the block's translation. Instead we emit a prologue for each block, - * wherein we birth the variables that block expects to be live, and then jump to the - * translation. Then, we emit the translation for each block using - * {@link #generateCodeForBlock(JitBlock, int)}, placing transitions between those connected by - * fall through using - * {@link VarGen#computeBlockTransition(JitCodeGenerator, JitBlock, JitBlock)}. Finally, we emit - * each requested exception handler using - * {@link ExceptionHandler#generateRunCode(JitCodeGenerator, MethodVisitor)}. + * iterate over the blocks in the same order as {@link #genStaticEntries(Emitter)}, so that our + * indices and its match. Thus, we emit a {@link Op#tableswitch(Emitter, int, Lbl, List) + * tableswitch} where each value maps to the blocks label identified in the same position of the + * {@code ENTRIES} field. We also provide a default case that just throws an + * {@link IllegalArgumentException}. We do not jump directly to the block's translation. Instead + * we emit a prologue for each block, wherein we birth the variables that block expects to be + * live, and then jump to the translation. Then, we emit the translation for each block using + * {@link #genBlock(OpResult, Local, Local, RetReq, JitBlock, int)}, placing transitions between + * those connected by fall through using + * {@link VarGen#computeBlockTransition(Local, JitCodeGenerator, JitBlock, JitBlock)}. Finally, + * we emit each requested exception handler using + * {@link ExceptionHandler#genRun(Emitter, Local, JitCodeGenerator)}. */ - protected void generateRunCode() { - runMv.visitCode(); - final Label startLocals = new Label(); - runMv.visitLabel(startLocals); + protected Emitter genRunMethod(Emitter em, Local> localThis, + Local localBlockId, RetReq> retReq) { + + Local localCtxmod = em.rootScope().decl(Types.T_INT, "ctxmod"); + em = em + .emit(Op::ldc__i, 0) + .emit(Op::istore, localCtxmod); + + am.allocate(em.rootScope()); + + Map> entries = new LinkedHashMap<>(); + for (JitBlock block : cfm.getBlocks()) { + AddrCtx entry = getOpEntry(block.first()); + if (entry != null) { + entries.put(block, Lbl.create()); + } + } + Lbl lblBadEntry = Lbl.create(); + + var dead = em + .emit(Op::iload, localBlockId) + .emit(Op::tableswitch, 0, lblBadEntry, List.copyOf(entries.values())); + + for (Map.Entry> ent : entries.entrySet()) { + JitBlock block = ent.getKey(); + dead = dead + .emit(Lbl::placeDead, ent.getValue()) + .emit(VarGen.computeBlockTransition(localThis, this, null, block)::genFwd) + .emit(Op::goto_, labelForBlock(block)); + } + + dead = dead + .emit(Lbl::placeDead, lblBadEntry) + .emit(Op::new_, T_ILLEGAL_ARGUMENT_EXCEPTION) + .emit(Op::dup) + .emit(Op::ldc__a, "Bad entry blockId") + .emit(Op::invokespecial, T_ILLEGAL_ARGUMENT_EXCEPTION, "", + MDESC_ILLEGAL_ARGUMENT_EXCEPTION__$INIT, false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::athrow); /** * NB. opIdx starts at 1, because JVM will ignore "Line number 0" */ int opIdx = 1; - List

    + * This is used by variable birthing and retirement as well as direct memory accesses. Dynamic + * memory accesses, i.e., {@link JitStoreOp store} and {@link JitLoadOp load} do not use this, + * though they may borrow some portions. + * + * @param the JIT type of the operand + */ +public interface AccessGen { + + /** + * Lookup the generator for accessing variables for the given type and byte order + * + * @param endian the byte order + * @param type the p-code type of the variable + * @return the access generator + */ + @SuppressWarnings("unchecked") + public static AccessGen lookup(Endian endian, T type) { + return (AccessGen) switch (endian) { + case BIG -> switch (type) { + case IntJitType t -> IntAccessGen.BE; + case LongJitType t -> LongAccessGen.BE; + case FloatJitType t -> FloatAccessGen.BE; + case DoubleJitType t -> DoubleAccessGen.BE; + case MpIntJitType t -> MpIntAccessGen.BE; + default -> throw new AssertionError(); + }; + case LITTLE -> switch (type) { + case IntJitType t -> IntAccessGen.LE; + case LongJitType t -> LongAccessGen.LE; + case FloatJitType t -> FloatAccessGen.LE; + case DoubleJitType t -> DoubleAccessGen.LE; + case MpIntJitType t -> MpIntAccessGen.LE; + default -> throw new AssertionError(); + }; + }; + } + + /** + * Lookup the generator for accessing variables of simple types and the given byte order + * + * @param the JVM type of the variable + * @param the p-code type of the variable + * @param endian the byte order + * @param type the p-code type of the variable + * @return the access generator + */ + @SuppressWarnings("unchecked") + public static , JT extends SimpleJitType> SimpleAccessGen + lookupSimple(Endian endian, JT type) { + return (SimpleAccessGen) switch (endian) { + case BIG -> switch (type) { + case IntJitType t -> IntAccessGen.BE; + case LongJitType t -> LongAccessGen.BE; + case FloatJitType t -> FloatAccessGen.BE; + case DoubleJitType t -> DoubleAccessGen.BE; + default -> throw new AssertionError(); + }; + case LITTLE -> switch (type) { + case IntJitType t -> IntAccessGen.LE; + case LongJitType t -> LongAccessGen.LE; + case FloatJitType t -> FloatAccessGen.LE; + case DoubleJitType t -> DoubleAccessGen.LE; + default -> throw new AssertionError(); + }; + }; + } + + /** + * Lookup the generator for accessing variables of multi-precision integer type and the given + * byte order + * + * @param endian the byte order + * @return the access generator + */ + public static MpIntAccessGen lookupMp(Endian endian) { + return switch (endian) { + case BIG -> MpIntAccessGen.BE; + case LITTLE -> MpIntAccessGen.LE; + }; + } + + /** + * Emit bytecode to read the given varnode onto the stack as a p-code bool (JVM int) + * + * @param the type of the generated passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param vn the varnode + * @return the emitter typed with the resulting stack, i.e., having pushed the value + */ + public static Emitter> + genReadToBool(Emitter em, Local> localThis, JitCodeGenerator gen, + Varnode vn) { + AddressSpace space = vn.getAddress().getAddressSpace(); + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size < BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::ldc__i, size) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "readBoolN", + MDESC_JIT_COMPILED_PASSAGE__READ_BOOL_N, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::ldc__i, BLOCK_SIZE - off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "readBoolN", + MDESC_JIT_COMPILED_PASSAGE__READ_BOOL_N, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::ldc__i, off + size - BLOCK_SIZE) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, "readBoolN", + MDESC_JIT_COMPILED_PASSAGE__READ_BOOL_N, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ior); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/DoubleAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/DoubleAccessGen.java new file mode 100644 index 0000000000..bd1273c1ef --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/DoubleAccessGen.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.pcode.Varnode; + +/** + * The generator for accessing doubles + * + *

    + * This is accomplished by delegating to the long access generator with type conversion. + */ +public enum DoubleAccessGen implements SimpleAccessGen { + /** The big-endian instance */ + BE(LongAccessGen.BE), + /** The little-endian instance */ + LE(LongAccessGen.LE); + + final LongAccessGen longGen; + + private DoubleAccessGen(LongAccessGen longGen) { + this.longGen = longGen; + } + + @Override + public Emitter> + genReadToStack(Emitter em, Local> localThis, JitCodeGenerator gen, + Varnode vn) { + return em + .emit(longGen::genReadToStack, localThis, gen, vn) + .emit(LongToDouble.INSTANCE::convertStackToStack, LongJitType.I8, DoubleJitType.F8, + Ext.ZERO); + } + + @Override + public > + Emitter genWriteFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + return em + .emit(DoubleToLong.INSTANCE::convertStackToStack, DoubleJitType.F8, LongJitType.I8, + Ext.ZERO) + .emit(longGen::genWriteFromStack, localThis, gen, vn); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/ExportsLegAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/ExportsLegAccessGen.java new file mode 100644 index 0000000000..b8f807d5bf --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/ExportsLegAccessGen.java @@ -0,0 +1,110 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import static ghidra.pcode.emu.jit.gen.GenConsts.BLOCK_SIZE; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.pcode.Varnode; + +/** + * A generator that exports part of its implementation for use in a {@link MpIntAccessGen}. + * + *

    + * This really just avoids the re-creation of {@link Varnode} objects for each leg of a large + * varnode. The method instead takes the (space,offset,size) triple as well as the offset of the + * block containing its start. + */ +public interface ExportsLegAccessGen extends SimpleAccessGen { + /** + * Emit code to read one JVM int, either a whole variable or one leg of a multi-precision int + * variable. + * + *

    + * Legs that span blocks are handled as in + * {@link #genReadToStack(Emitter, Local, JitCodeGenerator, Varnode)} + * + * @param the type of the generated passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param space the address space of the varnode + * @param block the block offset containing the varnode (or leg) + * @param off the offset of the varnode (or leg) + * @param size the size of the varnode in bytes (or leg) + * @return the emitter typed with the resulting stack, i.e., having pushed the value + */ + Emitter> genReadLegToStack( + Emitter em, Local> localThis, JitCodeGenerator gen, + AddressSpace space, long block, int off, int size); + + /** + * Emit code to write one JVM int, either a whole variable or one leg of a multi-precision int + * variable. + * + *

    + * Legs that span blocks are handled as in + * {@link #genWriteFromStack(Emitter, Local, JitCodeGenerator, Varnode)} + * + * @param the type of the generated passage + * @param the tail of the incoming stack + * @param the incoming stack with the value on top + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param space the address space of the varnode + * @param block the block offset containing the varnode (or leg) + * @param off the offset of the varnode (or leg) + * @param size the size of the varnode in bytes (or leg) + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + > Emitter + genWriteLegFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, AddressSpace space, long block, int off, int size); + + @Override + default Emitter> genReadToStack( + Emitter em, Local> localThis, JitCodeGenerator gen, Varnode vn) { + AddressSpace space = vn.getAddress().getAddressSpace(); + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + return genReadLegToStack(em, localThis, gen, space, block, off, size); + } + + @Override + default > Emitter + genWriteFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + AddressSpace space = vn.getAddress().getAddressSpace(); + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + return genWriteLegFromStack(em, localThis, gen, space, block, off, size); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/FloatAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/FloatAccessGen.java new file mode 100644 index 0000000000..3c9b3d5b2a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/FloatAccessGen.java @@ -0,0 +1,67 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import ghidra.pcode.emu.jit.analysis.JitType.FloatJitType; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.pcode.Varnode; + +/** + * The generator for writing floats + * + *

    + * This is accomplished by delegating to the int access generator with type conversion. + */ +public enum FloatAccessGen implements SimpleAccessGen { + /** The big-endian instance */ + BE(IntAccessGen.BE), + /** The little-endian instance */ + LE(IntAccessGen.LE); + + final IntAccessGen intGen; + + private FloatAccessGen(IntAccessGen intGen) { + this.intGen = intGen; + } + + @Override + public Emitter> genReadToStack( + Emitter em, Local> localThis, JitCodeGenerator gen, Varnode vn) { + return em + .emit(intGen::genReadToStack, localThis, gen, vn) + .emit(IntToFloat.INSTANCE::convertStackToStack, IntJitType.I4, FloatJitType.F4, + Ext.ZERO); + } + + @Override + public > + Emitter genWriteFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + return em + .emit(FloatToInt.INSTANCE::convertStackToStack, FloatJitType.F4, IntJitType.I4, + Ext.ZERO) + .emit(intGen::genWriteFromStack, localThis, gen, vn); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/IntAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/IntAccessGen.java new file mode 100644 index 0000000000..f3463ab3cd --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/IntAccessGen.java @@ -0,0 +1,265 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import static ghidra.pcode.emu.jit.gen.GenConsts.*; + +import ghidra.pcode.emu.jit.gen.FieldForArrDirect; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Endian; + +/** + * The generator for writing integers. + */ +public enum IntAccessGen implements MethodAccessGen, ExportsLegAccessGen { + /** The big-endian instance */ + BE { + @Override + public String chooseReadName(int size) { + return switch (size) { + case 1 -> "readInt1"; + case 2 -> "readIntBE2"; + case 3 -> "readIntBE3"; + case 4 -> "readIntBE4"; + default -> throw new AssertionError(); + }; + } + + @Override + public Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, AddressSpace space, long block, int off, + int size) { + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(size), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ldc__i, off + size - BLOCK_SIZE) + .emit(Op::ishl) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ior); + } + + @Override + public String chooseWriteName(int size) { + return switch (size) { + case 1 -> "writeInt1"; + case 2 -> "writeIntBE2"; + case 3 -> "writeIntBE3"; + case 4 -> "writeIntBE4"; + default -> throw new AssertionError(); + }; + } + + @Override + public > + Emitter genWriteLegFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, AddressSpace space, long block, int off, + int size) { + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(size), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(Op::dup) + .emit(Op::ldc__i, off + size - BLOCK_SIZE) + .emit(Op::iushr) + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + }, + /** The little-endian instance */ + LE { + @Override + public String chooseReadName(int size) { + return switch (size) { + case 1 -> "readInt1"; + case 2 -> "readIntLE2"; + case 3 -> "readIntLE3"; + case 4 -> "readIntLE4"; + default -> throw new AssertionError(); + }; + } + + @Override + public Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, AddressSpace space, long block, int off, + int size) { + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(size), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ior); + } + + @Override + public String chooseWriteName(int size) { + return switch (size) { + case 1 -> "writeInt1"; + case 2 -> "writeIntLE2"; + case 3 -> "writeIntLE3"; + case 4 -> "writeIntLE4"; + default -> throw new AssertionError(); + }; + } + + @Override + public > + Emitter genWriteLegFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, AddressSpace space, long block, int off, + int size) { + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(size), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(Op::dup) + .emit(Op::ldc__i, BLOCK_SIZE - off) + .emit(Op::iushr) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + }; + + /** + * Get the {@code int} access generator for the given byte order + * + * @param endian the byte order + * @return the access generator + */ + public static IntAccessGen forEndian(Endian endian) { + return switch (endian) { + case BIG -> BE; + case LITTLE -> LE; + }; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/LongAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/LongAccessGen.java new file mode 100644 index 0000000000..201038d172 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/LongAccessGen.java @@ -0,0 +1,299 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import static ghidra.pcode.emu.jit.gen.GenConsts.*; + +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.FieldForArrDirect; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.Varnode; + +/** + * Bytes writer for longs in big endian order. + */ +public enum LongAccessGen implements MethodAccessGen, SimpleAccessGen { + /** The big-endian instance */ + BE { + @Override + public String chooseReadName(int size) { + return switch (size) { + case 1 -> "readLong1"; + case 2 -> "readLongBE2"; + case 3 -> "readLongBE3"; + case 4 -> "readLongBE4"; + case 5 -> "readLongBE5"; + case 6 -> "readLongBE6"; + case 7 -> "readLongBE7"; + case 8 -> "readLongBE8"; + default -> throw new AssertionError(); + }; + } + + @Override + public Emitter> + genReadToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + AddressSpace space = vn.getAddress().getAddressSpace(); + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(size), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ldc__i, off + size - BLOCK_SIZE) + .emit(Op::lshl) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::lor); + } + + @Override + public String chooseWriteName(int size) { + return switch (size) { + case 1 -> "writeLong1"; + case 2 -> "writeLongBE2"; + case 3 -> "writeLongBE3"; + case 4 -> "writeLongBE4"; + case 5 -> "writeLongBE5"; + case 6 -> "writeLongBE6"; + case 7 -> "writeLongBE7"; + case 8 -> "writeLongBE8"; + default -> throw new AssertionError(); + }; + } + + @Override + public > + Emitter genWriteFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + AddressSpace space = vn.getAddress().getAddressSpace(); + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(size), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(Op::dup2__2) + .emit(Op::ldc__i, off + size - BLOCK_SIZE) + .emit(Op::lushr) + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + }, + /** The little-endian instance */ + LE { + @Override + public String chooseReadName(int size) { + return switch (size) { + case 1 -> "readLong1"; + case 2 -> "readLongLE2"; + case 3 -> "readLongLE3"; + case 4 -> "readLongLE4"; + case 5 -> "readLongLE5"; + case 6 -> "readLongLE6"; + case 7 -> "readLongLE7"; + case 8 -> "readLongLE8"; + default -> throw new AssertionError(); + }; + } + + @Override + public Emitter> + genReadToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + AddressSpace space = vn.getAddress().getAddressSpace(); + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(size), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseReadName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::lor); + } + + @Override + public String chooseWriteName(int size) { + return switch (size) { + case 1 -> "writeLong1"; + case 2 -> "writeLongLE2"; + case 3 -> "writeLongLE3"; + case 4 -> "writeLongLE4"; + case 5 -> "writeLongLE5"; + case 6 -> "writeLongLE6"; + case 7 -> "writeLongLE7"; + case 8 -> "writeLongLE8"; + default -> throw new AssertionError(); + }; + } + + @Override + public > + Emitter genWriteFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + long offset = vn.getOffset(); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int size = vn.getSize(); + AddressSpace space = vn.getAddress().getAddressSpace(); + FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); + if (off + size <= BLOCK_SIZE) { + return em + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(size), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + FieldForArrDirect nxtField = + gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); + return em + .emit(Op::dup2__2) + .emit(Op::ldc__i, BLOCK_SIZE - off) + .emit(Op::lushr) + .emit(nxtField::genLoad, localThis, gen) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(off + size - BLOCK_SIZE), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(blkField::genLoad, localThis, gen) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, + chooseWriteName(BLOCK_SIZE - off), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); + } + }; + + /** + * Get the {@code long} access generator for the given byte order + * + * @param endian the byte order + * @return the access generator + */ + public static LongAccessGen forEndian(Endian endian) { + return switch (endian) { + case BIG -> BE; + case LITTLE -> LE; + }; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MethodAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MethodAccessGen.java similarity index 66% rename from Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MethodAccessGen.java rename to Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MethodAccessGen.java index 8b6567d05e..06ac50a800 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MethodAccessGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MethodAccessGen.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.jit.gen.type; +package ghidra.pcode.emu.jit.gen.access; import ghidra.pcode.emu.jit.gen.op.LoadOpGen; import ghidra.pcode.emu.jit.gen.op.StoreOpGen; @@ -26,13 +26,22 @@ import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; *

    * This is needed by {@link LoadOpGen} and {@link StoreOpGen}. */ -public interface MethodAccessGen extends TypedAccessGen { +public interface MethodAccessGen { /** - * Choose the name of a method, e.g. {@link JitCompiledPassage#readInt1(byte[], int)} to use for - * the given variable size. + * Choose the name of the read method, e.g. {@link JitCompiledPassage#readInt1(byte[], int)} to + * use for the given variable size. * * @param size the size in bytes * @return the name of the method */ - String chooseName(int size); + String chooseReadName(int size); + + /** + * Choose the name of the write method, e.g. + * {@link JitCompiledPassage#writeInt1(int,byte[], int)} to use for the given variable size. + * + * @param size the size in bytes + * @return the name of the method + */ + String chooseWriteName(int size); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MpAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MpAccessGen.java new file mode 100644 index 0000000000..5864a47c49 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MpAccessGen.java @@ -0,0 +1,106 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.pcode.Varnode; + +/** + * An access generator for a multi-precision integer variable + */ +public interface MpAccessGen extends AccessGen { + + /** + * Emit bytecode to load the varnode's value into several locals. + * + * @param the type of the generated passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param vn the varnode + * @param type desired the p-code type of the value + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @return the operand containing the locals, and the emitter typed with the incoming stack + */ + OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, Varnode vn, + MpIntJitType type, Ext ext, Scope scope); + + /** + * Emit bytecode to load the varnode's value into an integer array in little-endian order, + * pushing its ref onto the JVM stack. + * + * @param the type of the generated passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param vn the varnode + * @param type desired the p-code type of the value + * @param ext the kind of extension to apply + * @param scope a scope for generating temporary local storage + * @param slack the number of additional, more significant, elements to allocate in the array + * @return the emitter typed with the resulting stack, i.e., having the ref pushed onto it + */ + Emitter>> genReadToArray( + Emitter em, Local> localThis, JitCodeGenerator gen, Varnode vn, + MpIntJitType type, Ext ext, Scope scope, int slack); + + /** + * Emit bytecode to store a value into a variable from the JVM stack. + * + * @param the type of the generated passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param opnd the operand whose locals contain the value to be stored + * @param vn the varnode + * @return the emitter typed with the incoming stack + */ + Emitter genWriteFromOpnd(Emitter em, + Local> localThis, JitCodeGenerator gen, Opnd opnd, + Varnode vn); + + /** + * Emit bytecode to store a varnode's value from an array of integer legs, in little endian + * order + * + * @param the type of the generated passage + * @param the tail of the incoming stack + * @param the incoming stack having the array ref on top + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param vn the varnode + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., having popped the array + */ + >> Emitter + genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn, Scope scope); +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MpIntAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MpIntAccessGen.java new file mode 100644 index 0000000000..6d45478b76 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/MpIntAccessGen.java @@ -0,0 +1,194 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import static ghidra.pcode.emu.jit.gen.GenConsts.BLOCK_SIZE; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.*; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.pcode.Varnode; + +/** + * The generator for writing multi-precision ints. + */ +public enum MpIntAccessGen implements MpAccessGen { + /** The big-endian instance */ + BE(IntAccessGen.BE) { + @Override + protected List orderedLegTypes(MpIntJitType type) { + return type.legTypesBE(); + } + + @Override + protected List> orderedLegs(Opnd opnd) { + return opnd.type().castLegsLE(opnd).reversed(); + } + }, + /** The little-endian instance */ + LE(IntAccessGen.LE) { + @Override + protected List orderedLegTypes(MpIntJitType type) { + return type.legTypesLE(); + } + + @Override + protected List> orderedLegs(Opnd opnd) { + return opnd.type().castLegsLE(opnd); + } + }; + + final IntAccessGen legGen; + + private MpIntAccessGen(IntAccessGen legGen) { + this.legGen = legGen; + } + + /** + * Arrange the leg types so that the least-significant one is first + * + * @param type the mp-int type + * @return the leg types in little-endian order + */ + protected abstract List orderedLegTypes(MpIntJitType type); + + /** + * Arrange the operand legs so that the least-significant one is first + * + * @param opnd the mp-int operand + * @return the legs in little-endian order + */ + protected abstract List> orderedLegs(Opnd opnd); + + @Override + public OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, Varnode vn, + MpIntJitType type, Ext ext, Scope scope) { + AddressSpace space = vn.getAddress().getAddressSpace(); + List> legs = new ArrayList<>(); + MpIntJitType fromType = MpIntJitType.forSize(vn.getSize()); + long offset = vn.getOffset(); + for (IntJitType t : orderedLegTypes(fromType)) { + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + String name = "mpl_%s_%x_%d_leg%x".formatted(space.getName(), vn.getOffset(), + vn.getSize(), offset); + int legSize = t.size(); + var result = em + .emit(legGen::genReadLegToStack, localThis, gen, space, block, off, legSize) + .emit(Opnd::createInt, t, name, scope); + legs.add(result.opnd()); + em = result.em(); + offset += legSize; + } + MpIntLocalOpnd temp = MpIntLocalOpnd.of(fromType, + "mem_%s_%x_%d".formatted(space.getName(), vn.getOffset(), vn.getSize()), legs); + return MpIntToMpInt.INSTANCE.convertOpndToOpnd(em, temp, type, ext, scope); + } + + @Override + public Emitter>> + genReadToArray(Emitter em, Local> localThis, JitCodeGenerator gen, + Varnode vn, MpIntJitType type, Ext ext, Scope scope, int slack) { + AddressSpace space = vn.getAddress().getAddressSpace(); + Local> arr = scope.decl(Types.T_INT_ARR, + "mpa_mem_%s_%x_%d".formatted(space.getName(), vn.getOffset(), vn.getSize())); + List fromLegTypes = orderedLegTypes(MpIntJitType.forSize(vn.getSize())); + List toLegTypes = orderedLegTypes(type); + int legsOut = toLegTypes.size(); + int legsIn = fromLegTypes.size(); + int defLegs = Integer.min(legsIn, legsOut); + em = em + .emit(Op::ldc__i, defLegs + slack) + .emit(Op::newarray, Types.T_INT) + .emit(Op::astore, arr); + long offset = vn.getOffset(); + for (int i = 0; i < defLegs; i++) { + IntJitType fromLegType = fromLegTypes.get(i); + IntJitType toLegType = toLegTypes.get(i); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int legSize = fromLegType.size(); + em = em + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(legGen::genReadLegToStack, localThis, gen, space, block, off, legSize) + .emit(Opnd::convertIntToInt, fromLegType, toLegType, ext) + .emit(Op::iastore); + offset += legSize; + } + return em + .emit(MpIntToMpInt::doGenArrExt, arr, legsOut, defLegs, ext, scope) + .emit(Op::aload, arr); + } + + @Override + public Emitter genWriteFromOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, + Opnd opnd, Varnode vn) { + AddressSpace space = vn.getAddress().getAddressSpace(); + long offset = vn.getOffset(); + for (SimpleOpnd leg : orderedLegs(opnd)) { + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int legSize = leg.type().size(); + em = em + .emit(leg::read) + .emit(legGen::genWriteLegFromStack, localThis, gen, space, block, off, legSize); + offset += legSize; + } + return em; + } + + @Override + public >> + Emitter genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn, Scope scope) { + AddressSpace space = vn.getAddress().getAddressSpace(); + Local> arr = scope.decl(Types.T_INT_ARR, + "mpa_mem_%s_%x_%d".formatted(space.getName(), vn.getOffset(), vn.getSize())); + var em1 = em + .emit(Op::astore, arr); + List legTypes = orderedLegTypes(MpIntJitType.forSize(vn.getSize())); + final int legCount = legTypes.size(); + long offset = vn.getOffset(); + for (int i = 0; i < legCount; i++) { + IntJitType t = legTypes.get(i); + long block = offset / BLOCK_SIZE * BLOCK_SIZE; + int off = (int) (offset - block); + int legSize = t.size(); + em1 = em1 + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(Op::iaload) + .emit(legGen::genWriteLegFromStack, localThis, gen, space, block, off, legSize); + offset += legSize; + } + return em1; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/SimpleAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/SimpleAccessGen.java new file mode 100644 index 0000000000..040d1d1385 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/access/SimpleAccessGen.java @@ -0,0 +1,76 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.access; + +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.program.model.pcode.Varnode; + +/** + * An access generator for simple-typed variables + * + * @param the JVM type of the variable + * @param the p-code type of the variable + */ +public interface SimpleAccessGen, JT extends SimpleJitType> + extends AccessGen { + + /** + * Emit code to read a varnode + *

    + * If the varnode fits completely in the block (the common case), then this accesses the bytes + * from the one block, using the method chosen by size. If the varnode extends into the next + * block, then this will split the varnode into two portions according to machine byte order. + * Each portion is accessed using the method for the size of that portion. The results are + * reassembled into a single operand. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param vn the varnode + * @return the code generator with the resulting stack, i.e., having pushed the value + */ + Emitter> genReadToStack( + Emitter em, Local> localThis, JitCodeGenerator gen, Varnode vn); + + /** + * Emit code to write a varnode + *

    + * If the varnode fits completely in the block (the common case), then this accesses the bytes + * from the one block, using the method chosen by size. If the varnode extends into the next + * block, then this will split the varnode into two portions according to machine byte order. + * Each portion is accessed using the method for the size of that portion. + * + * @param the tail of the incoming stack + * @param the incoming stack having the value on top + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param vn the varnode + * @return the code generator with the resulting stack, i.e., having popped the value + */ + > Emitter + genWriteFromStack(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn); +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java index cc53bfb6a5..7877df8dbb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BinOpGen.java @@ -15,19 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_JIT_COMPILED_PASSAGE; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitBinOp; /** @@ -66,74 +62,54 @@ public interface BinOpGen extends OpGen { * arrays, invoke the method, and then place the result legs on the stack, least-significant leg * on top. * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack * @param gen the code generator + * @param localThis a handle to the local holding the {@code this} reference * @param type the type of the operands * @param methodName the name of the method in {@link JitCompiledPassage} to invoke - * @param mv the method visitor - * @param overProvisionLeft the number of extra ints to allocate for the left operand's array. - * This is to facilitate Knuth's division algorithm, which may require an extra - * leading leg in the dividend after normalization. + * @param op the p-code op + * @param slackLeft the number of extra ints to allocate for the left operand's array. This is + * to facilitate Knuth's division algorithm, which may require an extra leading leg + * in the dividend after normalization. * @param takeOut indicates which operand of the static method to actually take for the output. * This is to facilitate the remainder operator, because Knuth's algorithm leaves the * remainder where there dividend was. + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the empty stack */ - static void generateMpDelegationToStaticMethod(JitCodeGenerator gen, MpIntJitType type, - String methodName, MethodVisitor mv, int overProvisionLeft, TakeOut takeOut) { + default Emitter genMpDelegationToStaticMethod( + Emitter em, JitCodeGenerator gen, Local> localThis, + MpIntJitType type, String methodName, JitBinOp op, int slackLeft, TakeOut takeOut, + Scope scope) { /** * The strategy here will be to allocate an array for each of the operands (output and 2 * inputs) and then invoke a static method to do the actual operation. It might be nice to * generate inline code for small multiplications, but we're going to leave that for later. */ - // [lleg1,...,llegN,rleg1,...,rlegN] - JitAllocationModel am = gen.getAllocationModel(); int legCount = type.legsAlloc(); - try ( - JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", legCount); - JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", legCount)) { - // [rleg1,...,rlegN,lleg1,...,llegN] - OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); - // [lleg1,...,llegN] - OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); - // [] - switch (takeOut) { - case OUT -> { - // [] - mv.visitLdcInsn(legCount); - // [count:INT] - mv.visitIntInsn(NEWARRAY, T_INT); - // [out:INT[count]] - mv.visitInsn(DUP); - // [out,out] - - OpGen.generateMpLegsIntoArray(tmpL, legCount + overProvisionLeft, legCount, mv); - // [inL,out,out] - OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); - // [inR,inL,out,out] - } - case LEFT -> { - // [] - mv.visitLdcInsn(legCount); - // [count:INT] - mv.visitIntInsn(NEWARRAY, T_INT); - // [out] - OpGen.generateMpLegsIntoArray(tmpL, legCount + overProvisionLeft, legCount, mv); - // [inL,out] - mv.visitInsn(DUP_X1); - // [inL,out,inL] - OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); - // [inR,inL,out,inL] - } - default -> throw new AssertionError(); - } - } - - mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName, - MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP, true); - // [out||inL:INT[count]] - - // Push the result back, in reverse order - OpGen.generateMpLegsFromArray(legCount, mv); + var emParams = switch (takeOut) { + case OUT -> em + .emit(Op::ldc__i, legCount) + .emit(Op::newarray, Types.T_INT) + .emit(Op::dup) + .emit(gen::genReadToArray, localThis, op.l(), type, ext(), scope, slackLeft) + .emit(gen::genReadToArray, localThis, op.r(), type, rExt(), scope, 0); + case LEFT -> em + .emit(Op::aconst_null, Types.T_INT_ARR) + .emit(gen::genReadToArray, localThis, op.l(), type, ext(), scope, slackLeft) + .emit(Op::dup_x1) + .emit(gen::genReadToArray, localThis, op.r(), type, rExt(), scope, 0); + }; + return emParams + .emit(Op::invokestatic, GenConsts.T_JIT_COMPILED_PASSAGE, methodName, + GenConsts.MDESC_JIT_COMPILED_PASSAGE__MP_INT_BINOP, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(gen::genWriteFromArray, localThis, op.out(), type, ext(), scope); } /** @@ -164,63 +140,4 @@ public interface BinOpGen extends OpGen { default Ext rExt() { return ext(); } - - /** - * Emit code between reading the left and right operands - * - *

    - * This is invoked immediately after emitting code to push the left operand onto the stack, - * giving the implementation an opportunity to perform any manipulations of that operand - * necessary to set up the operation, before code to push the right operand is emitted. - * - * @param gen the code generator - * @param op the operator - * @param lType the actual type of the left operand - * @param rType the actual type of the right operand - * @param rv the method visitor - * @return the new actual type of the left operand - */ - default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, - MethodVisitor rv) { - return lType; - } - - /** - * Emit code for the binary operator - * - *

    - * At this point both operands are on the stack. After this returns, code to write the result - * from the stack into the destination operand will be emitted. - * - * @param gen the code generator - * @param op the operator - * @param block the block containing the operator - * @param lType the actual type of the left operand - * @param rType the actual type of the right operand - * @param rv the method visitor - * @return the actual type of the result - */ - JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, - JitType rType, MethodVisitor rv); - - /** - * {@inheritDoc} - * - *

    - * This default implementation emits code to load the left operand, invokes the - * {@link #afterLeft(JitCodeGenerator, JitBinOp, JitType, JitType, MethodVisitor) after-left} - * hook point, emits code to load the right operand, invokes - * {@link #generateBinOpRunCode(JitCodeGenerator, JitBinOp, JitBlock, JitType, JitType, MethodVisitor) - * generate-binop}, and finally emits code to write the destination operand. - */ - @Override - default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { - JitType lType = gen.generateValReadCode(op.l(), op.lType(), ext()); - JitType rType = op.rType().resolve(gen.getTypeModel().typeOf(op.r())); - lType = afterLeft(gen, op, lType, rType, rv); - JitType checkRType = gen.generateValReadCode(op.r(), op.rType(), rExt()); - assert checkRType == rType; - JitType outType = generateBinOpRunCode(gen, op, block, lType, rType, rv); - gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java deleted file mode 100644 index 056d80fa05..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BitwiseBinOpGen.java +++ /dev/null @@ -1,117 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.op; - -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; -import ghidra.pcode.emu.jit.op.JitBinOp; - -/** - * An extension for bitwise binary operators - * - * @param the class of p-code op node in the use-def graph - */ -public interface BitwiseBinOpGen extends IntBinOpGen { - @Override - default boolean isSigned() { - return false; - } - - /** - * The JVM opcode to implement this operator with int operands on the stack. - * - * @return the opcode - */ - int intOpcode(); - - /** - * The JVM opcode to implement this operator with long operands on the stack. - * - * @return the opcode - */ - int longOpcode(); - - /** - * The implementation for multi-precision ints. - * - * @param gen the code generator - * @param type the type of each operand, including the reuslt - * @param mv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - default void generateMpIntBinOp(JitCodeGenerator gen, MpIntJitType type, - MethodVisitor mv) { - /** - * We need temp locals to get things in order. Read in right operand, do the op as we pop - * each left op. Then push it all back. - * - * No masking of the result is required, since both operands should already be masked, and - * the bitwise op cannot generate bits of more significance. - */ - // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) - int legCount = type.legsAlloc(); - try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { - OpGen.generateMpLegsIntoTemp(result, legCount, mv); - for (int i = 0; i < legCount; i++) { - // [lleg1,...,llegN:INT] - mv.visitVarInsn(ILOAD, result.idx(i)); - // [lleg1,...,llegN:INT,rlegN:INT] - mv.visitInsn(intOpcode()); - // [lleg1,...,olegN:INT] - mv.visitVarInsn(ISTORE, result.idx(i)); - // [lleg1,...] - } - OpGen.generateMpLegsFromTemp(result, legCount, mv); - } - } - - @Override - default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); - } - - /** - * {@inheritDoc} - * - *

    - * This implementation reduces the need to just the JVM opcode. We simply ensure both operands - * have the same size and JVM type, select and emit the correct opcode, and return the type of - * the result. - */ - @Override - default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, - JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); - switch (rType) { - case IntJitType t -> rv.visitInsn(intOpcode()); - case LongJitType t -> rv.visitInsn(longOpcode()); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntBinOp(gen, t, rv); - case MpIntJitType t -> TODO("MpInt of differing sizes"); - default -> throw new AssertionError(); - } - return rType; - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java index 68d228440c..22af763282 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolAndOpGen.java @@ -15,6 +15,14 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitBoolAndOp; import ghidra.pcode.opbehavior.OpBehaviorBoolAnd; @@ -28,17 +36,19 @@ import ghidra.pcode.opbehavior.OpBehaviorBoolAnd; * behavior," we could technically optimize this by only ANDing the least significant leg * when we're dealing with mp-ints. */ -public enum BoolAndOpGen implements BitwiseBinOpGen { +public enum BoolAndOpGen implements IntBitwiseBinOpGen { /** The generator singleton */ GEN; @Override - public int intOpcode() { - return IAND; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.iand(em); } @Override - public int longOpcode() { - return LAND; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.land(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java index c97e13507c..90c07b2696 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolNegateOpGen.java @@ -15,23 +15,27 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitBoolNegateOp; import ghidra.pcode.opbehavior.OpBehaviorBoolNegate; /** * The generator for a {@link JitBoolNegateOp bool_negate}. + *

    + * This emits ^1, as observed in code emitted by {@code javac}. For multi-precision, we perform that + * operation only on the least-significant leg. * * @implNote It is the responsibility of the slaspec author to ensure boolean values are 0 or 1. * This allows us to use bitwise logic instead of having to check for any non-zero value, - * just like {@link OpBehaviorBoolNegate}. + * just like {@link OpBehaviorBoolNegate}. Additionally, boolean operands ought to be a + * byte, but certainly no larger than an int (4 bytes). */ -public enum BoolNegateOpGen implements UnOpGen { +public enum BoolNegateOpGen implements IntOpUnOpGen { /** The generator singleton */ GEN; @@ -41,24 +45,34 @@ public enum BoolNegateOpGen implements UnOpGen { } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitBoolNegateOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case IntJitType t -> { - rv.visitLdcInsn(1); - rv.visitInsn(IXOR); - } - case LongJitType t -> { - rv.visitLdcInsn(1L); - rv.visitInsn(LXOR); - } - case MpIntJitType t -> { - // Least-sig leg is on top, and it's an int. - rv.visitLdcInsn(1); - rv.visitInsn(IXOR); - } - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForInt(Emitter em) { + return em + .emit(Op::ldc__i, 1) + .emit(Op::ixor); + } + + @Override + public > Emitter> + opForLong(Emitter em) { + return em + .emit(Op::ldc__l, 1) + .emit(Op::lxor); + } + + @Override + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitBoolNegateOp op, + MpIntJitType type, Scope scope) { + /** + * NOTE: This will needlessly overwrite the upper legs of the mp-int output. That said, + * Sleigh-spec authors should keep "boolean" operands no larger than an int, preferably a + * byte. + */ + return em + .emit(gen::genReadLegToStack, localThis, op.u(), type, 0, ext()) + .emit(this::opForInt) + .emit(gen::genWriteFromStack, localThis, op.out(), type.legTypesLE().getFirst(), + ext(), scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolOrOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolOrOpGen.java index ee64b909e8..88667f2873 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolOrOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolOrOpGen.java @@ -15,6 +15,14 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitBoolOrOp; import ghidra.pcode.opbehavior.OpBehaviorBoolOr; @@ -24,18 +32,23 @@ import ghidra.pcode.opbehavior.OpBehaviorBoolOr; * @implNote It is the responsibility of the slaspec author to ensure boolean values are 0 or 1. * This allows us to use bitwise logic instead of having to check for any non-zero value, * just like {@link OpBehaviorBoolOr}. Thus, this is identical to {@link IntOrOpGen}. + * @implNote Because having bits other than the least significant set in the inputs is "undefined + * behavior," we could technically optimize this by only ANDing the least significant leg + * when we're dealing with mp-ints. */ -public enum BoolOrOpGen implements BitwiseBinOpGen { +public enum BoolOrOpGen implements IntBitwiseBinOpGen { /** The generator singleton */ GEN; @Override - public int intOpcode() { - return IOR; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.ior(em); } @Override - public int longOpcode() { - return LOR; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lor(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolXorOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolXorOpGen.java index 1ac776cb74..93795a7da3 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolXorOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BoolXorOpGen.java @@ -15,6 +15,14 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitBoolXorOp; import ghidra.pcode.opbehavior.OpBehaviorBoolXor; @@ -24,18 +32,23 @@ import ghidra.pcode.opbehavior.OpBehaviorBoolXor; * @implNote It is the responsibility of the slaspec author to ensure boolean values are 0 or 1. * This allows us to use bitwise logic instead of having to check for any non-zero value, * just like {@link OpBehaviorBoolXor}. Thus, this is identical to {@link IntXorOpGen}. + * @implNote Because having bits other than the least significant set in the inputs is "undefined + * behavior," we could technically optimize this by only ANDing the least significant leg + * when we're dealing with mp-ints. */ -public enum BoolXorOpGen implements BitwiseBinOpGen { +public enum BoolXorOpGen implements IntBitwiseBinOpGen { /** The generator singleton */ GEN; @Override - public int intOpcode() { - return IXOR; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.ixor(em); } @Override - public int longOpcode() { - return LXOR; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lxor(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java index 6bb9f16edc..4bb225858b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchIndOpGen.java @@ -15,17 +15,21 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitPassage.RIndBranch; import ghidra.pcode.emu.jit.JitPcodeThread; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.op.BranchOpGen.BranchGen; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen; +import ghidra.pcode.emu.jit.gen.op.BranchOpGen.UBranchGen; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Emitter.Dead; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitBranchIndOp; import ghidra.program.model.address.Address; import ghidra.program.model.lang.RegisterValue; @@ -52,48 +56,49 @@ public enum BranchIndOpGen implements OpGen { * @param block the block containing the op * @param rv the run method visitor */ - static void generateExitCode(JitCodeGenerator gen, JitBranchIndOp op, RegisterValue ctx, - JitBlock block, MethodVisitor rv) { - gen.generatePassageExit(block, () -> { - // [...] - JitType targetType = gen.generateValReadCode(op.target(), op.targetType(), Ext.ZERO); - // [...,target:?] - TypeConversions.generateToLong(targetType, LongJitType.I8, Ext.ZERO, rv); - // [...,target:LONG] - }, ctx, rv); - - rv.visitInsn(ACONST_NULL); - rv.visitInsn(ARETURN); + static Emitter genExit(Emitter em, + Local> localThis, RetReq> retReq, + JitCodeGenerator gen, JitBranchIndOp op, RegisterValue ctx, JitBlock block) { + PcGen tgtGen = PcGen.loadTarget(localThis, gen, op.target()); + return em + .emit(gen::genExit, localThis, block, tgtGen, ctx) + .emit(Op::aconst_null, GenConsts.T_ENTRY_POINT) + .emit(Op::areturn, retReq); } /** * A branch code generator for indirect branches */ - static class IndBranchGen extends BranchGen { + static class IndBranchGen extends UBranchGen { /** Singleton */ static final IndBranchGen IND = new IndBranchGen(); @Override - Address exit(JitCodeGenerator gen, RIndBranch branch) { + Address exit(JitCodeGenerator gen, RIndBranch branch) { return null; } @Override - void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitBranchIndOp op, RIndBranch branch, - JitBlock block, MethodVisitor rv) { - generateExitCode(gen, op, branch.flowCtx(), block, rv); + Emitter genRunWithoutCtxmod(Emitter em, + Local> localThis, RetReq> retReq, + JitCodeGenerator gen, JitBranchIndOp op, RIndBranch branch, JitBlock block) { + return genExit(em, localThis, retReq, gen, op, branch.flowCtx(), block); } @Override - void generateCodeWithCtxmod(JitCodeGenerator gen, JitBranchIndOp op, Address exit, - JitBlock block, MethodVisitor rv) { - generateExitCode(gen, op, null, block, rv); + Emitter genRunWithCtxmod(Emitter em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, JitBranchIndOp op, + Address exit, JitBlock block) { + return genExit(em, localThis, retReq, gen, op, null, block); } } @Override - public void generateRunCode(JitCodeGenerator gen, JitBranchIndOp op, JitBlock block, - MethodVisitor rv) { - IndBranchGen.IND.generateCode(gen, op, op.branch(), block, rv); + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitBranchIndOp op, JitBlock block, Scope scope) { + return new DeadOpResult(IndBranchGen.IND.genRun( + em, localThis, localCtxmod, retReq, gen, op, op.branch(), block)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java index 6a082c9104..a58202737c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/BranchOpGen.java @@ -15,15 +15,21 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitPassage.*; import ghidra.pcode.emu.jit.JitPcodeThread; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen; +import ghidra.pcode.emu.jit.gen.op.CBranchOpGen.ExtCBranchGen; +import ghidra.pcode.emu.jit.gen.op.CBranchOpGen.IntCBranchGen; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.op.JitBranchOp; import ghidra.pcode.emu.jit.op.JitOp; @@ -35,7 +41,7 @@ import ghidra.program.model.lang.RegisterValue; * *

    * With an {@link IntBranch} record, this simply looks up the label for the target block and emits a - * block transition followed by a {@link #GOTO goto}. + * block transition followed by a {@link Op#goto_(Emitter) goto}. * *

    * With an {@link ExtBranch} record, this emits code to retire the target to the program counter, @@ -56,13 +62,10 @@ public enum BranchOpGen implements OpGen { * @param block the block containing the op * @param rv the run method visitor */ - static void generateRetireCode(JitCodeGenerator gen, Address exit, RegisterValue ctx, - JitBlock block, MethodVisitor rv) { - gen.generatePassageExit(block, () -> { - // [...] - rv.visitLdcInsn(exit.getOffset()); - // [...,target:LONG] - }, ctx, rv); + static Emitter genRetire(Emitter em, + Local> localThis, JitCodeGenerator gen, Address exit, + RegisterValue ctx, JitBlock block) { + return gen.genExit(em, localThis, block, PcGen.loadOffset(exit), ctx); } /** @@ -76,20 +79,24 @@ public enum BranchOpGen implements OpGen { * @param block the block containing the op * @param rv the run method visitor */ - static void generateExitCode(JitCodeGenerator gen, Address exit, JitBlock block, - MethodVisitor rv) { - generateRetireCode(gen, exit, null, block, rv); - rv.visitInsn(ACONST_NULL); - rv.visitInsn(ARETURN); + static Emitter genExit(Emitter em, + Local> localThis, RetReq> retReq, + JitCodeGenerator gen, Address exit, JitBlock block) { + return em + .emit(BranchOpGen::genRetire, localThis, gen, exit, (RegisterValue) null, block) + .emit(Op::aconst_null, GenConsts.T_ENTRY_POINT) + .emit(Op::areturn, retReq); } /** * A branch code generator * + * @param the stack after the JVM branch bytecode (may be {@link Dead}) + * @param the stack before the JVM branch bytecode (cannot be {@link Dead}) * @param the type of branch * @param the type of op */ - static abstract class BranchGen { + static abstract class BranchGen { /** * Get the target address of the branch * @@ -97,7 +104,7 @@ public enum BranchOpGen implements OpGen { * @param branch the branch * @return the target address */ - abstract Address exit(JitCodeGenerator gen, TB branch); + abstract Address exit(JitCodeGenerator gen, TB branch); /** * Generate code for the branch in the case a context modification has not occurred. @@ -111,8 +118,9 @@ public enum BranchOpGen implements OpGen { * @param block the block containing the op * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - abstract void generateCodeWithoutCtxmod(JitCodeGenerator gen, TO op, TB branch, - JitBlock block, MethodVisitor rv); + abstract Emitter genRunWithoutCtxmod(Emitter em, + Local> localThis, RetReq> retReq, + JitCodeGenerator gen, TO op, TB branch, JitBlock block); /** * Generate code for the branch in the case a context modification may have occurred. @@ -127,8 +135,10 @@ public enum BranchOpGen implements OpGen { * @param block the block containing the op * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - abstract void generateCodeWithCtxmod(JitCodeGenerator gen, TO op, Address exit, - JitBlock block, MethodVisitor rv); + abstract Emitter genRunWithCtxmod(Emitter em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, TO op, Address exit, + JitBlock block); /** * Emit code that jumps or exits via a direct branch @@ -139,95 +149,140 @@ public enum BranchOpGen implements OpGen { * @param block the block containing the op * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method */ - void generateCode(JitCodeGenerator gen, TO op, TB branch, JitBlock block, - MethodVisitor rv) { - switch (branch.reach()) { - case WITH_CTXMOD -> generateCodeWithCtxmod(gen, op, exit(gen, branch), block, rv); - case WITHOUT_CTXMOD -> generateCodeWithoutCtxmod(gen, op, branch, block, rv); + abstract Emitter genRun(Emitter em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, TO op, TB branch, + JitBlock block); + } + + /** + * An abstract branch code generator for unconditional branches. + * + * @param the type of branch + * @param the type of op + */ + abstract static class UBranchGen + extends BranchGen { + /** + * Emit code that jumps or exits via a direct branch + * + * @param gen the code generator + * @param op the branch op + * @param branch the branch from the op + * @param block the block containing the op + * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + */ + @Override + Emitter genRun(Emitter em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, TO op, TB branch, + JitBlock block) { + return switch (branch.reach()) { + case WITH_CTXMOD -> genRunWithCtxmod(em, localThis, localCtxmod, retReq, gen, op, + exit(gen, branch), block); + case WITHOUT_CTXMOD -> genRunWithoutCtxmod(em, localThis, retReq, gen, op, branch, + block); case MAYBE_CTXMOD -> { - Label withModctx = new Label(); - RunFixedLocal.CTXMOD.generateLoadCode(rv); - rv.visitJumpInsn(IFNE, withModctx); - generateCodeWithoutCtxmod(gen, op, branch, block, rv); - rv.visitLabel(withModctx); - generateCodeWithCtxmod(gen, op, exit(gen, branch), block, rv); + var emIf = em + .emit(Op::iload, localCtxmod) + .emit(Op::ifne); + yield emIf.em() + .emit(this::genRunWithoutCtxmod, localThis, retReq, gen, op, branch, + block) + .emit(Lbl::placeDead, emIf.lbl()) + // NB. genRun is already branching. No need for if-else construct. + .emit(this::genRunWithCtxmod, localThis, localCtxmod, retReq, gen, op, + exit(gen, branch), block); } - default -> throw new AssertionError(); - } + }; } } /** * A branch code generator for internal branches + * + * @implNote We leave {@code TO:=}{@link JitOp} here, because we want {@link IntCBranchGen} to + * be able to delegate to this instance. */ - static class IntBranchGen extends BranchGen { + static class IntBranchGen extends UBranchGen { /** Singleton */ static final IntBranchGen INT = new IntBranchGen(); @Override - Address exit(JitCodeGenerator gen, RIntBranch branch) { + Address exit(JitCodeGenerator gen, RIntBranch branch) { return gen.getAddressForOp(branch.to()); } @Override - void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RIntBranch branch, - JitBlock block, MethodVisitor rv) { + Emitter genRunWithoutCtxmod(Emitter em, + Local> localThis, RetReq> retReq, + JitCodeGenerator gen, JitOp op, RIntBranch branch, JitBlock block) { JitBlock target = block.getTargetBlock(branch); - Label label = gen.labelForBlock(target); - VarGen.computeBlockTransition(gen, block, target).generate(rv); - rv.visitJumpInsn(GOTO, label); + Lbl label = gen.labelForBlock(target); + return em + .emit(VarGen.computeBlockTransition(localThis, gen, block, target)::genFwd) + .emit(Op::goto_, label); } @Override - void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, - MethodVisitor rv) { - generateExitCode(gen, exit, block, rv); + Emitter genRunWithCtxmod(Emitter em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, JitOp op, Address exit, + JitBlock block) { + return genExit(em, localThis, retReq, gen, exit, block); } } /** * A branch code generator for external branches + * + * @implNote We leave {@code TO:=}{@link JitOp} here, because we want {@link ExtCBranchGen} to + * be able to delegate to this instance. */ - static class ExtBranchGen extends BranchGen { + static class ExtBranchGen extends UBranchGen { /** Singleton */ static final ExtBranchGen EXT = new ExtBranchGen(); @Override - Address exit(JitCodeGenerator gen, RExtBranch branch) { + Address exit(JitCodeGenerator gen, RExtBranch branch) { return branch.to().address; } @Override - void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RExtBranch branch, - JitBlock block, MethodVisitor rv) { + Emitter genRunWithoutCtxmod(Emitter em, + Local> localThis, RetReq> retReq, + JitCodeGenerator gen, JitOp op, RExtBranch branch, JitBlock block) { AddrCtx exit = branch.to(); FieldForExitSlot slotField = gen.requestFieldForExitSlot(exit); - - generateRetireCode(gen, exit.address, exit.rvCtx, block, rv); - - // [] - slotField.generateLoadCode(gen, rv); - // [slot] - rv.visitMethodInsn(INVOKESTATIC, GenConsts.NAME_JIT_COMPILED_PASSAGE, "getChained", - GenConsts.MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED, true); - // [chained:ENTRY] - rv.visitInsn(ARETURN); + return em + .emit(BranchOpGen::genRetire, localThis, gen, exit.address, exit.rvCtx, block) + .emit(slotField::genLoad, localThis, gen) + .emit(Op::invokestatic, GenConsts.T_JIT_COMPILED_PASSAGE, "getChained", + GenConsts.MDESC_JIT_COMPILED_PASSAGE__GET_CHAINED, true) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::areturn, retReq); } @Override - void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, - MethodVisitor rv) { - generateExitCode(gen, exit, block, rv); + Emitter genRunWithCtxmod(Emitter em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, JitOp op, Address exit, + JitBlock block) { + return genExit(em, localThis, retReq, gen, exit, block); } } @Override - public void generateRunCode(JitCodeGenerator gen, JitBranchOp op, JitBlock block, - MethodVisitor rv) { - switch (op.branch()) { - case RIntBranch ib -> IntBranchGen.INT.generateCode(gen, op, ib, block, rv); - case RExtBranch eb -> ExtBranchGen.EXT.generateCode(gen, op, eb, block, rv); + public DeadOpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitBranchOp op, JitBlock block, Scope scope) { + return new DeadOpResult(switch (op.branch()) { + case RIntBranch ib -> IntBranchGen.INT.genRun(em, localThis, localCtxmod, retReq, gen, + op, ib, block); + case RExtBranch eb -> ExtBranchGen.EXT.genRun(em, localThis, localCtxmod, retReq, gen, + op, eb, block); default -> throw new AssertionError("Branch type confusion"); - } + }); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java index 1416e4fb81..a9aa4caf42 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CBranchOpGen.java @@ -15,23 +15,22 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitPassage.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitDataFlowModel; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.op.BranchOpGen.ExtBranchGen; -import ghidra.pcode.emu.jit.gen.op.BranchOpGen.IntBranchGen; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.op.BranchOpGen.*; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.op.JitCBranchOp; -import ghidra.pcode.emu.jit.op.JitOp; import ghidra.pcode.emu.jit.var.JitFailVal; import ghidra.program.model.address.Address; import ghidra.program.model.pcode.PcodeOp; @@ -44,76 +43,138 @@ import ghidra.program.model.pcode.PcodeOp; * *

    * With an {@link IntBranch} record, this looks up the label for the target block and checks if a - * transition is necessary. If one is necessary, it emits an {@link #IFEQ ifeq} with the transition - * and {@link #GOTO goto} it guards. The {@code ifeq} skips to the fall-through case. If a - * transition is not necessary, it simply emits an {@link #IFNE ifne} to the target label. + * transition is necessary. If one is necessary, it emits an {@link Op#ifeq(Emitter) ifeq} with the + * transition and {@link Op#goto_(Emitter) goto} it guards. The {@code ifeq} skips to the + * fall-through case. If a transition is not necessary, it simply emits an {@link Op#ifne(Emitter) + * ifne} to the target label. * *

    * With an {@link ExtBranch} record, this does the same as {@link BranchOpGen} but guarded by an - * {@link #IFEQ ifeq} that skips to the fall-through case. + * {@link Op#ifeq(Emitter) ifeq} that skips to the fall-through case. */ public enum CBranchOpGen implements OpGen { /** The generator singleton */ GEN; + /** + * An abstract branch code generator for conditional branches. + * + * @param the type of branch + * @param the type of op + */ + abstract static class CBranchGen + extends BranchGen, TB, TO> { + @Override + Emitter genRun(Emitter> em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, TO op, TB branch, + JitBlock block) { + return switch (branch.reach()) { + case WITH_CTXMOD -> genRunWithCtxmod(em, localThis, localCtxmod, retReq, gen, op, + exit(gen, branch), block); + case WITHOUT_CTXMOD -> genRunWithoutCtxmod(em, localThis, retReq, gen, op, branch, + block); + case MAYBE_CTXMOD -> { + var lblIf = em.emit(Op::iload, localCtxmod) + .emit(Op::ifne); + var lblGoto = lblIf.em() + .emit(this::genRunWithoutCtxmod, localThis, retReq, gen, op, branch, + block) + .emit(Op::goto_); + yield lblGoto.em() + .emit(Lbl::placeDead, lblIf.lbl()) + .emit(this::genRunWithCtxmod, localThis, localCtxmod, retReq, gen, op, + exit(gen, branch), block) + .emit(Lbl::place, lblGoto.lbl()); + } + }; + } + } + /** * A branch code generator for internal conditional branches */ - static class IntCBranchGen extends IntBranchGen { + static class IntCBranchGen extends CBranchGen { /** Singleton */ static final IntCBranchGen C_INT = new IntCBranchGen(); @Override - void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RIntBranch branch, - JitBlock block, MethodVisitor rv) { - JitBlock target = block.getTargetBlock(branch); - Label label = gen.labelForBlock(target); - BlockTransition transition = VarGen.computeBlockTransition(gen, block, target); - if (transition.needed()) { - Label fall = new Label(); - rv.visitJumpInsn(IFEQ, fall); - transition.generate(rv); - rv.visitJumpInsn(GOTO, label); - rv.visitLabel(fall); - } - else { - rv.visitJumpInsn(IFNE, label); - } + Address exit(JitCodeGenerator gen, RIntBranch branch) { + return IntBranchGen.INT.exit(gen, branch); } @Override - void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, - MethodVisitor rv) { - Label fall = new Label(); - rv.visitJumpInsn(IFEQ, fall); - super.generateCodeWithCtxmod(gen, op, exit, block, rv); - rv.visitLabel(fall); + Emitter genRunWithoutCtxmod( + Emitter> em, Local> localThis, + RetReq> retReq, JitCodeGenerator gen, JitCBranchOp op, + RIntBranch branch, JitBlock block) { + JitBlock target = block.getTargetBlock(branch); + Lbl label = gen.labelForBlock(target); + BlockTransition transition = + VarGen.computeBlockTransition(localThis, gen, block, target); + + if (!transition.needed()) { + return em + .emit(Op::ifne, label); + } + var lblFall = em + .emit(Op::ifeq); + return lblFall.em() + .emit(transition::genFwd) + .emit(Op::goto_, label) + .emit(Lbl::placeDead, lblFall.lbl()); + } + + @Override + Emitter genRunWithCtxmod(Emitter> em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, JitCBranchOp op, + Address exit, JitBlock block) { + var lblFall = em + .emit(Op::ifeq); + return lblFall.em() + .emit(IntBranchGen.INT::genRunWithCtxmod, localThis, localCtxmod, retReq, gen, + op, exit, block) + .emit(Lbl::placeDead, lblFall.lbl()); } } /** * A branch code generator for external conditional branches */ - static class ExtCBranchGen extends ExtBranchGen { + static class ExtCBranchGen extends CBranchGen { /** Singleton */ static final ExtCBranchGen C_EXT = new ExtCBranchGen(); @Override - void generateCodeWithoutCtxmod(JitCodeGenerator gen, JitOp op, RExtBranch branch, - JitBlock block, MethodVisitor rv) { - Label fall = new Label(); - rv.visitJumpInsn(IFEQ, fall); - super.generateCodeWithoutCtxmod(gen, op, branch, block, rv); - rv.visitLabel(fall); + Address exit(JitCodeGenerator gen, RExtBranch branch) { + return ExtBranchGen.EXT.exit(gen, branch); } @Override - void generateCodeWithCtxmod(JitCodeGenerator gen, JitOp op, Address exit, JitBlock block, - MethodVisitor rv) { - Label fall = new Label(); - rv.visitJumpInsn(IFEQ, fall); - super.generateCodeWithCtxmod(gen, op, exit, block, rv); - rv.visitLabel(fall); + Emitter genRunWithoutCtxmod( + Emitter> em, Local> localThis, + RetReq> retReq, JitCodeGenerator gen, JitCBranchOp op, + RExtBranch branch, JitBlock block) { + var lblFall = em + .emit(Op::ifeq); + return lblFall.em() + .emit(ExtBranchGen.EXT::genRunWithoutCtxmod, localThis, retReq, gen, op, branch, + block) + .emit(Lbl::placeDead, lblFall.lbl()); + } + + @Override + Emitter genRunWithCtxmod(Emitter> em, + Local> localThis, Local localCtxmod, + RetReq> retReq, JitCodeGenerator gen, JitCBranchOp op, + Address exit, JitBlock block) { + var lblFall = em + .emit(Op::ifeq); + return lblFall.em() + .emit(ExtBranchGen.EXT::genRunWithCtxmod, localThis, localCtxmod, retReq, gen, + op, exit, block) + .emit(Lbl::placeDead, lblFall.lbl()); } } @@ -135,24 +196,27 @@ public enum CBranchOpGen implements OpGen { * which will ensure we apply special handling here. */ @Override - public void generateRunCode(JitCodeGenerator gen, JitCBranchOp op, JitBlock block, - MethodVisitor rv) { + public LiveOpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitCBranchOp op, JitBlock block, Scope scope) { if (op.op() instanceof ExitPcodeOp && op.branch() instanceof RExtBranch eb) { assert eb.reach() == Reachability.MAYBE_CTXMOD; - Label fall = new Label(); - RunFixedLocal.CTXMOD.generateLoadCode(rv); - rv.visitJumpInsn(IFEQ, fall); - BranchOpGen.generateExitCode(gen, eb.to().address, block, rv); - rv.visitLabel(fall); - return; + var lblFall = em + .emit(Op::iload, localCtxmod) + .emit(Op::ifeq); + return new LiveOpResult(lblFall.em() + .emit(BranchOpGen::genExit, localThis, retReq, gen, eb.to().address, block) + .emit(Lbl::placeDead, lblFall.lbl())); } - JitType cType = gen.generateValReadCode(op.cond(), op.condType(), Ext.ZERO); - TypeConversions.generateIntToBool(cType, rv); - switch (op.branch()) { - case RIntBranch ib -> IntCBranchGen.C_INT.generateCode(gen, op, ib, block, rv); - case RExtBranch eb -> ExtCBranchGen.C_EXT.generateCode(gen, op, eb, block, rv); - default -> throw new AssertionError("Branch type confusion"); - } + var emBool = gen.genReadToBool(em, localThis, op.cond()); + + return new LiveOpResult(switch (op.branch()) { + case RIntBranch ib -> IntCBranchGen.C_INT.genRun(emBool, localThis, localCtxmod, retReq, + gen, op, ib, block); + case RExtBranch eb -> ExtCBranchGen.C_EXT.genRun(emBool, localThis, localCtxmod, retReq, + gen, op, eb, block); + default -> throw new AssertionError(); + }); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherMissingOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherMissingOpGen.java index 7c671f4f89..55d1fee715 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherMissingOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherMissingOpGen.java @@ -16,12 +16,19 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_SLEIGH_LINK_EXCEPTION__$INIT; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_SLEIGH_LINK_EXCEPTION; - -import org.objectweb.asm.MethodVisitor; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_SLEIGH_LINK_EXCEPTION; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitCallOtherMissingOp; import ghidra.pcode.exec.SleighLinkException; @@ -37,23 +44,21 @@ public enum CallOtherMissingOpGen implements OpGen { GEN; @Override - public void generateRunCode(JitCodeGenerator gen, JitCallOtherMissingOp op, JitBlock block, - MethodVisitor rv) { - gen.generatePassageExit(block, () -> { - rv.visitLdcInsn(gen.getAddressForOp(op.op()).getOffset()); - }, gen.getExitContext(op.op()), rv); - + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitCallOtherMissingOp op, JitBlock block, Scope scope) { String message = gen.getErrorMessage(op.op()); - // [...] - rv.visitTypeInsn(NEW, NAME_SLEIGH_LINK_EXCEPTION); - // [...,error:NEW] - rv.visitInsn(DUP); - // [...,error:NEW,error:NEW] - rv.visitLdcInsn(message); - // [...,error:NEW,error:NEW,message] - rv.visitMethodInsn(INVOKESPECIAL, NAME_SLEIGH_LINK_EXCEPTION, "", - MDESC_SLEIGH_LINK_EXCEPTION__$INIT, false); - // [...,error] - rv.visitInsn(ATHROW); + PcGen pcGen = PcGen.loadOffset(gen.getAddressForOp(op.op())); + return new DeadOpResult(em + .emit(gen::genExit, localThis, block, pcGen, gen.getExitContext(op.op())) + .emit(Op::new_, T_SLEIGH_LINK_EXCEPTION) + .emit(Op::dup) + .emit(Op::ldc__a, message) + .emit(Op::invokespecial, T_SLEIGH_LINK_EXCEPTION, "", + MDESC_SLEIGH_LINK_EXCEPTION__$INIT, false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::athrow)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java index 994dd4c2b6..8cf638b3d6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CallOtherOpGen.java @@ -19,31 +19,33 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.objectweb.asm.*; - import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen; import ghidra.pcode.emu.jit.gen.JitCodeGenerator.RetireMode; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.op.JitCallOtherDefOp; import ghidra.pcode.emu.jit.op.JitCallOtherOpIf; import ghidra.pcode.emu.jit.var.JitVal; import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput; +import ghidra.pcode.exec.PcodeUseropLibrary; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; @@ -53,9 +55,9 @@ import ghidra.program.model.pcode.Varnode; * *

    * The checks if Direct invocation is possible. If so, it emits code using - * {@link #generateRunCodeUsingDirectStrategy(JitCodeGenerator, JitCallOtherOpIf, JitBlock, MethodVisitor)}. + * {@link #genRunDirectStrategy(Emitter, Local, JitCodeGenerator, JitCallOtherOpIf, JitBlock, Scope)}. * If not, it emits code using - * {@link #generateRunCodeUsingRetirementStrategy(JitCodeGenerator, PcodeOp, JitBlock, PcodeUseropDefinition, MethodVisitor)}. + * {@link #genRunRetirementStrategy(Emitter, Local, JitCodeGenerator, PcodeOp, JitBlock, PcodeUseropDefinition)}. * Direct invocation is possible when the userop is {@link PcodeUseropDefinition#isFunctional() * functional} and all of its parameters and return type have a supported primitive type. * ({@code char} is not supported.) Regarding the invocation strategies, see @@ -83,82 +85,80 @@ import ghidra.program.model.pcode.Varnode; * stack. We then emit the invocation of the Java method, guarded by the exception handler. We then * have to consider whether the userop has an output operand and whether its definition returns a * value. If both are true, we emit code to write the result. If neither is true, we're done. If a - * result is returned, but no output operand is provided, we must still emit a {@link #POP - * pop}. + * result is returned, but no output operand is provided, we must still emit a + * {@link Op#pop(Emitter) pop}. */ public enum CallOtherOpGen implements OpGen { /** The generator singleton */ GEN; + private static Emitter>> + genLoadVarnodeOrNull(Emitter em, Local> localThis, + JitCodeGenerator gen, Varnode vn) { + if (vn == null) { + return em.emit(Op::aconst_null, T_VARNODE); + } + FieldForVarnode field = gen.requestStaticFieldForVarnode(vn); + return em.emit(field::genLoad, gen); + } + /** * Emit code to implement the Standard strategy (see the class documentation) * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference * @param gen the code generator * @param op the p-code op * @param block the block containing the op * @param userop the userop definition, wrapped by the {@link JitDataFlowUseropLibrary} - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the result of emitting the userop's bytecode */ - public static void generateRunCodeUsingRetirementStrategy(JitCodeGenerator gen, PcodeOp op, - JitBlock block, PcodeUseropDefinition userop, MethodVisitor rv) { + public static OpResult genRunRetirementStrategy( + Emitter em, Local> localThis, JitCodeGenerator gen, + PcodeOp op, JitBlock block, PcodeUseropDefinition userop) { /** - * This is about the simplest (laziest) approach we could take for the moment, but it should - * suffice, depending on the frequency of CALLOTHER executions. We immediately retire all - * variables, then invoke the userop as it would be by the p-code interpreter. It can access - * its variables in the usual fashion. Although not ideal, it can also feed the executor - * (interpreter) ops to execute --- they won't be jitted here. Then, we liven the variables - * back. - * + * This is about the simplest (laziest) approach we could take, but it should suffice when + * we cannot invoke directly. We immediately retire all variables, then invoke the userop as + * it would be by the p-code interpreter. It can access its variables in the usual fashion. + * Although not ideal, it can also feed the executor (interpreter) ops to execute --- they + * won't be jitted here. Then, we liven the variables back. + *

    * NOTE: The output variable should be "alive", so we need not store it into a local. It'll * be made alive in the return block transition. - * - * TODO: Implement direct invocation for functional userops. NOTE: Cannot avoid block - * retirement and re-birth unless I also do direct invocation. Otherwise, the parameters are - * read from the state instead of from the local variables. */ - BlockTransition transition = VarGen.computeBlockTransition(gen, block, null); - transition.generate(rv); + BlockTransition transition = + VarGen.computeBlockTransition(localThis, gen, block, null); - gen.generateRetirePcCtx(() -> { - rv.visitLdcInsn(gen.getAddressForOp(op).getOffset()); - }, gen.getExitContext(op), RetireMode.SET, rv); + PcGen pcGen = PcGen.loadOffset(gen.getAddressForOp(op)); - // [] - RunFixedLocal.THIS.generateLoadCode(rv); - // [this] - gen.requestFieldForUserop(userop).generateLoadCode(gen, rv); - // [this,userop] + var emArr = em + .emit(transition::genFwd) + .emit(gen::genRetirePcCtx, localThis, pcGen, gen.getExitContext(op), + RetireMode.SET) + .emit(Op::aload, localThis) + .emit(gen.requestFieldForUserop(userop)::genLoad, localThis, gen) + .emit(CallOtherOpGen::genLoadVarnodeOrNull, localThis, gen, op.getOutput()) + .emit(Op::ldc__i, op.getNumInputs() - 1) + .emit(Op::anewarray, T_VARNODE); - if (op.getOutput() == null) { - rv.visitInsn(ACONST_NULL); - } - else { - gen.requestStaticFieldForVarnode(op.getOutput()).generateLoadCode(gen, rv); - } - // [this,userop,outVn] - - rv.visitLdcInsn(op.getNumInputs() - 1); - rv.visitTypeInsn(ANEWARRAY, NAME_VARNODE); - // [this,userop,outVn,inVns:ARR] for (int i = 1; i < op.getNumInputs(); i++) { - // [this,userop,outVn,inVns:ARR] - rv.visitInsn(DUP); - // [this,userop,outVn,inVns:ARR,inVns:ARR] - rv.visitLdcInsn(i - 1); - // [this,userop,outVn,inVns:ARR,inVns:ARR,index] - // Yes, including constants :/ - Varnode input = op.getInput(i); - gen.requestStaticFieldForVarnode(input).generateLoadCode(gen, rv); - // [this,userop,outVn,inVns:ARR,inVns:ARR,index,inVn] - rv.visitInsn(AASTORE); - // [this,userop,outVn,inVns:ARR] + emArr = emArr + .emit(Op::dup) + .emit(Op::ldc__i, i - 1) + .emit(CallOtherOpGen::genLoadVarnodeOrNull, localThis, gen, op.getInput(i)) + .emit(Op::aastore); } - // [this,userop,outVn,inVns:ARR] - rv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "invokeUserop", - MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP, true); - - transition.generateInv(rv); + return new LiveOpResult(emArr + .emit(Op::invokeinterface, T_JIT_COMPILED_PASSAGE, "invokeUserop", + MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(transition::genInv)); } static Parameter findOutputParameter(Parameter[] parameters, Method method) { @@ -189,13 +189,19 @@ public enum CallOtherOpGen implements OpGen { /** * Emit code to implement the Direct strategy (see the class documentation) * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference * @param gen the code generator * @param op the p-code op use-def node * @param block the block containing the op - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @param scope a scope for generating temporary local storage + * @return the result of emitting the userop's bytecode */ - public static void generateRunCodeUsingDirectStrategy(JitCodeGenerator gen, - JitCallOtherOpIf op, JitBlock block, MethodVisitor rv) { + public static > OpResult + genRunDirectStrategy(Emitter em, + Local> localThis, JitCodeGenerator gen, JitCallOtherOpIf op, + JitBlock block, Scope scope) { FieldForUserop useropField = gen.requestFieldForUserop(op.userop()); // Set live = gen.vsm.getLiveVars(block); @@ -203,23 +209,9 @@ public enum CallOtherOpGen implements OpGen { * NOTE: It doesn't matter if there are live variables. We still have to "retire" the * program counter and contextreg if the userop throws an exception. */ - final Label tryStart = new Label(); - final Label tryEnd = new Label(); - rv.visitTryCatchBlock(tryStart, tryEnd, - gen.requestExceptionHandler((DecodedPcodeOp) op.op(), block).label(), NAME_THROWABLE); - JitAllocationModel am = gen.getAllocationModel(); - - // [] - useropField.generateLoadCode(gen, rv); - // [userop] - rv.visitMethodInsn(INVOKEINTERFACE, NAME_PCODE_USEROP_DEFINITION, "getDefiningLibrary", - MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY, true); - // [library:PcodeUseropLibrary] Method method = op.userop().getJavaMethod(); - String owningLibName = Type.getInternalName(method.getDeclaringClass()); - rv.visitTypeInsn(CHECKCAST, owningLibName); - // [library:OWNING_TYPE] + Parameter[] parameters = method.getParameters(); Parameter outputParameter = findOutputParameter(parameters, method); if (outputParameter != null && method.getReturnType() != void.class) { @@ -228,100 +220,138 @@ public enum CallOtherOpGen implements OpGen { It's applied to %s of %s""".formatted( OpOutput.class.getSimpleName(), outputParameter, method.getName())); } - try (JvmTempAlloc out = - am.allocateTemp(rv, "out", int[].class, outputParameter == null ? 0 : 1)) { - MpIntJitType outMpType; - if (outputParameter != null) { - if (!(op instanceof JitCallOtherDefOp defOp)) { - outMpType = null; - rv.visitInsn(ACONST_NULL); - } - else { - outMpType = MpIntJitType.forSize(defOp.out().size()); - rv.visitLdcInsn(outMpType.legsAlloc()); - rv.visitIntInsn(NEWARRAY, T_INT); - } - rv.visitVarInsn(ASTORE, out.idx(0)); + + Local> localOut; + MpIntJitType outMpType; + + if (outputParameter != null) { + localOut = scope.decl(Types.T_INT_ARR, "out"); + if (op instanceof JitCallOtherDefOp defOp) { + outMpType = MpIntJitType.forSize(defOp.out().size()); + em = em + .emit(Op::ldc__i, outMpType.legsAlloc()) + .emit(Op::newarray, Types.T_INT) + .emit(Op::astore, localOut); } else { outMpType = null; - } - - int argIdx = 0; - for (int i = 0; i < parameters.length; i++) { - Parameter p = parameters[i]; - - if (p == outputParameter) { - rv.visitVarInsn(ALOAD, out.idx(0)); - continue; - } - - JitVal arg = op.args().get(argIdx++); - - // TODO: Should this always be zero extension? - JitType type = gen.generateValReadCode(arg, JitTypeBehavior.ANY, Ext.ZERO); - if (p.getType() == boolean.class) { - TypeConversions.generateIntToBool(type, rv); - continue; - } - - if (p.getType() == int[].class) { - MpIntJitType mpType = MpIntJitType.forSize(type.size()); - // NOTE: Would be nice to have annotation specify signedness - TypeConversions.generate(gen, type, mpType, Ext.ZERO, rv); - int legCount = mpType.legsAlloc(); - try (JvmTempAlloc temp = am.allocateTemp(rv, "temp", legCount)) { - OpGen.generateMpLegsIntoTemp(temp, legCount, rv); - OpGen.generateMpLegsIntoArray(temp, legCount, legCount, rv); - } - continue; - } - - // Some primitive/simple type - // TODO: Should this always be zero extension? Can annotation specify? - TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), Ext.ZERO, - rv); - } - // [library,params...] - rv.visitLabel(tryStart); - rv.visitMethodInsn(INVOKEVIRTUAL, owningLibName, method.getName(), - Type.getMethodDescriptor(method), false); - // [return?] - rv.visitLabel(tryEnd); - if (outputParameter != null) { - if (outMpType != null && op instanceof JitCallOtherDefOp defOp) { - rv.visitVarInsn(ALOAD, out.idx(0)); - OpGen.generateMpLegsFromArray(outMpType.legsAlloc(), rv); - // NOTE: Want annotation to specify signedness - gen.generateVarWriteCode(defOp.out(), outMpType, Ext.ZERO); - } - // Else there's either no @OpOutput or the output operand is absent - } - else if (op instanceof JitCallOtherDefOp defOp) { - // TODO: Can annotation specify signedness of return value? - gen.generateVarWriteCode(defOp.out(), JitType.forJavaType(method.getReturnType()), - Ext.ZERO); - } - else if (method.getReturnType() != void.class) { - TypeConversions.generatePop(JitType.forJavaType(method.getReturnType()), rv); + em = em + .emit(Op::aconst_null, Types.T_INT_ARR) + .emit(Op::astore, localOut); } } - } + else { + outMpType = null; + localOut = null; + } - static class ResourceGroup implements AutoCloseable { - private final List resources = new ArrayList<>(); + TRef libType = Types.refExtends(T_PCODE_USEROP_LIBRARY, method.getDeclaringClass()); - @Override - public void close() throws Exception { - for (AutoCloseable r : resources) { - r.close(); + var rec = new Object() { + Emitter> doReadArg(Emitter em, JitVal arg, + Parameter param) { + if (param.getType() == boolean.class) { + return gen.genReadToBool(em, localThis, arg); + } + if (param.getType() == int[].class) { + MpIntJitType t = MpIntJitType.forSize(arg.size()); + // TODO: Annotation/attribute to specify slack? + return gen.genReadToArray(em, localThis, arg, t, Ext.ZERO, scope, 0); + } + return switch (JitType.forJavaType(param.getType())) { + case IntJitType t -> gen.genReadToStack(em, localThis, arg, t, Ext.ZERO); + case LongJitType t -> gen.genReadToStack(em, localThis, arg, t, Ext.ZERO); + case FloatJitType t -> gen.genReadToStack(em, localThis, arg, t, Ext.ZERO); + case DoubleJitType t -> gen.genReadToStack(em, localThis, arg, t, Ext.ZERO); + default -> throw new AssertionError(); + }; } - } - public T add(T resource) { - resources.add(resource); - return resource; + ObjInv doInv(Emitter em, List args, + List params) { + if (params.isEmpty()) { + /** + * NOTE: Can't put try-catch block here because the handler's all expect + * Ent + */ + return Op.invokevirtual(em, libType, method.getName(), MthDesc.reflect(method), + false); + } + Parameter param = params.getFirst(); + if (param == outputParameter) { + var emOut = em.emit(Op::aload, localOut); + var inv = doInv(emOut, args, params.subList(1, params.size())); + return Inv.takeQArg(inv); + } + JitVal arg = args.getFirst(); + // TODO: Annotation/attribute to describe signedness? + // TODO: Or a way to receive the byte size + var emRead = doReadArg(em, arg, param); + var inv = + doInv(emRead, args.subList(1, args.size()), params.subList(1, params.size())); + return Inv.takeQArg(inv); + } + }; + + var tryCatchBlock = Misc.tryCatch(em, Lbl.create(), + gen.requestExceptionHandler((DecodedPcodeOp) op.op(), block).lbl(), + GenConsts.T_THROWABLE); + em = tryCatchBlock.em(); + + var emLib = em + .emit(useropField::genLoad, localThis, gen) + .emit(Op::invokeinterface, T_PCODE_USEROP_DEFINITION, "getDefiningLibrary", + MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::checkcast, libType); + var inv = rec + .doInv(emLib, op.args(), Arrays.asList(parameters)) + .step(Inv::takeQObjRef); + + if (outputParameter != null) { + if (outMpType != null && op instanceof JitCallOtherDefOp defOp) { + em = inv + .step(Inv::retQVoid) + .emit(Op::aload, localOut) + .emit(gen::genWriteFromArray, localThis, defOp.out(), outMpType, Ext.ZERO, + scope); + } + // Else there's either no @OpOutput or the output operand is absent } + else if (op instanceof JitCallOtherDefOp defOp) { + // TODO: Can annotation specify signedness of return value? + var write = new Object() { + public , JT extends SimpleJitType> Emitter doWrite( + Inv inv, Class returnType) { + JT type = SimpleJitType.forJavaType(returnType); + return inv + .step(Inv::retQ, type.bType()) + .emit(gen::genWriteFromStack, localThis, defOp.out(), type, Ext.ZERO, + scope); + } + }; + em = inv.step(write::doWrite, method.getReturnType()); + } + else if (method.getReturnType() != void.class) { + em = switch (JitType.forJavaType(method.getReturnType())) { + case IntJitType t -> inv + .step(Inv::retQ, t.bType()) + .emit(Op::pop); + case LongJitType t -> inv + .step(Inv::retQ, t.bType()) + .emit(Op::pop2__2); + case FloatJitType t -> inv + .step(Inv::retQ, t.bType()) + .emit(Op::pop); + case DoubleJitType t -> inv + .step(Inv::retQ, t.bType()) + .emit(Op::pop2__2); + default -> throw new AssertionError(); + }; + } + return new LiveOpResult(em + .emit(Lbl::place, tryCatchBlock.end())); } /** @@ -350,17 +380,17 @@ public enum CallOtherOpGen implements OpGen { } @Override - public void generateRunCode(JitCodeGenerator gen, JitCallOtherOpIf op, JitBlock block, - MethodVisitor rv) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitCallOtherOpIf op, JitBlock block, Scope scope) { if (op.userop().modifiesContext()) { - rv.visitLdcInsn(1); - RunFixedLocal.CTXMOD.generateStoreCode(rv); + em = em + .emit(Op::ldc__i, 1) + .emit(Op::istore, localCtxmod); } if (canDoDirectInvocation(op)) { - generateRunCodeUsingDirectStrategy(gen, op, block, rv); - } - else { - generateRunCodeUsingRetirementStrategy(gen, op.op(), block, op.userop(), rv); + return genRunDirectStrategy(em, localThis, gen, op, block, scope); } + return genRunRetirementStrategy(em, localThis, gen, op.op(), block, op.userop()); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CatenateOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CatenateOpGen.java index 3acab73803..dc8b2aab79 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CatenateOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CatenateOpGen.java @@ -15,11 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitVarScopeModel; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitCatenateOp; /** @@ -36,8 +41,9 @@ public enum CatenateOpGen implements OpGen { GEN; @Override - public void generateRunCode(JitCodeGenerator gen, JitCatenateOp op, JitBlock block, - MethodVisitor rv) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitCatenateOp op, JitBlock block, Scope scope) { throw new AssertionError("Cannnot generate synthetic op"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java deleted file mode 100644 index f08041e67e..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareFloatOpGen.java +++ /dev/null @@ -1,102 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.op; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - -import ghidra.lifecycle.Unfinished; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.op.JitFloatTestOp; - -/** - * An extension for float comparison operators - * - * @param the class of p-code op node in the use-def graph - */ -public interface CompareFloatOpGen extends FloatBinOpGen { - - /** - * The JVM opcode to perform the comparison with float operands on the stack. - * - * @return the opcode - */ - int fcmpOpcode(); - - /** - * The JVM opcode to perform the comparison with double operands on the stack. - * - * @return the opcode - */ - int dcmpOpcode(); - - /** - * The JVM opcode to perform the conditional jump. - * - *

    - * The condition should correspond to the true case of the p-code operator. - * - * @return the opcode - */ - int condOpcode(); - - /** - * {@inheritDoc} - * - *

    - * This implementation reduces the need to just a few opcodes: 1) the opcode for comparing in - * case of JVM {@code float}, 2) the opcode for comparing in the case of JVM {@code double}, and - * 3) the conditional jump on the result of that comparison. First, the comparison opcode is - * emitted. It should result in and int <0, ==0, or >0 on the stack, depending on whether - * L<R, L==R, or L>R, respectively. Then the conditional jump is emitted. We place labels - * in an if-else pattern to place either a 1 (true) or 0 (false) value of the appropriate p-code - * type on the stack. - * - * @implNote This template is consistently generated by the Java compiler (Adoptium OpenJDK 21), - * despite there being possible branchless implementations. That could indicate one of - * a few things: 1) the HotSpot JIT knows how to optimize this pattern, perhaps using - * branchless native instructions, 2) branchless optimizations don't yield the speedup - * here we might expect, or 3) they didn't care to optimize. TODO: Investigate - * in case it's thing 3. We might like to see if branchless JVM bytecodes can improve - * performance. - */ - @Override - default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, - JitType rType, MethodVisitor rv) { - assert rType == lType; - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - Label lblTrue = new Label(); - Label lblDone = new Label(); - switch (rType) { - case FloatJitType t -> rv.visitInsn(fcmpOpcode()); - case DoubleJitType t -> rv.visitInsn(dcmpOpcode()); - case MpFloatJitType t -> Unfinished.TODO("MpFloat"); - default -> throw new AssertionError(); - } - rv.visitJumpInsn(condOpcode(), lblTrue); - TypeConversions.generateLdcFalse(outType, rv); - rv.visitJumpInsn(GOTO, lblDone); - rv.visitLabel(lblTrue); - TypeConversions.generateLdcTrue(outType, rv); - rv.visitLabel(lblDone); - - return outType; - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java deleted file mode 100644 index 43b8bd3cdc..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CompareIntBinOpGen.java +++ /dev/null @@ -1,173 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.op; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.op.JitIntTestOp; - -/** - * An extension for integer comparison operators - * - * @param the class of p-code op node in the use-def graph - */ -public interface CompareIntBinOpGen extends IntBinOpGen { - - /** - * {@inheritDoc} - *

    - * If the comparison is unsigned, we will emit invocations of - * {@link Integer#compareUnsigned(int, int)} or {@link Long#compareUnsigned(long, long)}, - * followed by a conditional jump corresponding to this p-code comparison op. If the comparison - * is signed, and the type fits in a JVM int, we emit the conditional jump of ints directly - * implementing this p-code comparison op. If the type requires a JVM long, we first emit an - * {@link #LCMP lcmp}, followed by the same opcode that would be used in the unsigned case. - * - * @return true if signed, false if not - */ - @Override - boolean isSigned(); - - /** - * The JVM opcode to perform the conditional jump for signed integers. - * - * @return the opcode - */ - int icmpOpcode(); - - default void generateIntCmp(String methodName, MethodVisitor rv) { - rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, methodName, MDESC_INTEGER__COMPARE, - false); - } - - /** - * Emits bytecode for the JVM int case - * - * @param lblTrue the target bytecode label for the true case - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - default void generateIntJump(Label lblTrue, MethodVisitor rv) { - if (isSigned()) { - rv.visitJumpInsn(icmpOpcode(), lblTrue); - } - else { - generateIntCmp("compareUnsigned", rv); - rv.visitJumpInsn(ifOpcode(), lblTrue); - } - } - - /** - * Emits bytecode for the JVM long case - * - * @param lblTrue the target bytecode label for the true case - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - */ - default void generateLongJump(Label lblTrue, MethodVisitor rv) { - if (isSigned()) { - rv.visitInsn(LCMP); - } - else { - rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "compareUnsigned", - MDESC_LONG__COMPARE_UNSIGNED, false); - } - rv.visitJumpInsn(ifOpcode(), lblTrue); - } - - /** - * The JVM opcode to perform the conditional jump for unsigned or long integers. - *

    - * This is emitted after the application of {@link #LCMP} or the comparator method. - * - * @return the opcode - */ - int ifOpcode(); - - @Override - default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); - } - - default JitType generateMpIntCmp(JitCodeGenerator gen, MpIntJitType type, Label lblTrue, - MethodVisitor mv) { - int legCount = type.legsAlloc(); - Label lblDone = new Label(); - // Need two temps, because comparison is from *most* to least-significant - try ( - JvmTempAlloc tmpL = gen.getAllocationModel().allocateTemp(mv, "tmpL", legCount); - JvmTempAlloc tmpR = gen.getAllocationModel().allocateTemp(mv, "tmpR", legCount)) { - OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); - OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, tmpL.idx(legCount - i - 1)); - mv.visitVarInsn(ILOAD, tmpR.idx(legCount - i - 1)); - //OpGen.generateSyserrInts(gen, 2, mv); - generateIntCmp(i == 0 ? "compare" : "compareUnsigned", mv); - if (i != legCount - 1) { - mv.visitInsn(DUP); - mv.visitJumpInsn(IFNE, lblDone); - mv.visitInsn(POP); - } - } - } - mv.visitLabel(lblDone); - mv.visitJumpInsn(ifOpcode(), lblTrue); - return IntJitType.I4; - } - - /** - * {@inheritDoc} - * - *

    - * This reduces the implementation to a flag for signedness, the opcode for the conditional jump - * on integer operands, and the opcode for a conditional jump after the comparison of longs. The - * JVM, does not provide conditional jumps on long operands, so we must first compare the longs, - * pushing an int onto the stack, and then conditionally jumping on that. This pattern is - * similar for unsigned comparison of integers. - */ - @Override - default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, - JitType rType, MethodVisitor rv) { - Label lblTrue = new Label(); - Label lblDone = new Label(); - - rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); - switch (rType) { - case IntJitType t -> generateIntJump(lblTrue, rv); - case LongJitType t -> generateLongJump(lblTrue, rv); - case MpIntJitType t -> generateMpIntCmp(gen, t, lblTrue, rv); - default -> throw new AssertionError(); - } - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - TypeConversions.generateLdcFalse(outType, rv); - rv.visitJumpInsn(GOTO, lblDone); - rv.visitLabel(lblTrue); - TypeConversions.generateLdcTrue(outType, rv); - rv.visitLabel(lblDone); - - return outType; - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java index 52190562ba..47f075acfb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/CopyOpGen.java @@ -15,22 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.op.JitCopyOp; /** * The generator for a {@link JitCopyOp copy}. - * *

    - * This uses the unary operator generator and emits nothing extra. The unary generator template will - * emit code to load the input operand, this emits nothing, and then the template emits code to - * write the output operand, effecting a simple copy. + * This is identical to {@link IntZExtOpGen}, except that we expect (require?) the output and input + * operand to agree in size, and so we don't actually expect any extension. In the event that is not + * the case, it seems agreeable that zero extension is applied. */ -public enum CopyOpGen implements UnOpGen { +public enum CopyOpGen implements IntExtUnOpGen { /** The generator singleton */ GEN; @@ -38,10 +32,4 @@ public enum CopyOpGen implements UnOpGen { public boolean isSigned() { return false; } - - @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitCopyOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - return uType; - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java index b179fd80a9..0ffad4f00b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAbsOpGen.java @@ -15,15 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatAbsOp; /** @@ -33,21 +33,25 @@ import ghidra.pcode.emu.jit.op.JitFloatAbsOp; * This uses the unary operator generator and emits an invocation of {@link Math#abs(float)} or * {@link Math#abs(double)}, depending on the type. */ -public enum FloatAbsOpGen implements FloatUnOpGen { +public enum FloatAbsOpGen implements FloatOpUnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatAbsOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case FloatJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "abs", - MDESC_$FLOAT_UNOP, false); - case DoubleJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "abs", - MDESC_$DOUBLE_UNOP, false); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForFloat(Emitter em) { + return em + .emit(Op::invokestatic, T_MATH, "abs", MDESC_$FLOAT_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret); + } + + @Override + public > Emitter> + opForDouble(Emitter em) { + return em + .emit(Op::invokestatic, T_MATH, "abs", MDESC_$DOUBLE_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java index d903f4c550..31ddb9ceac 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatAddOpGen.java @@ -15,37 +15,34 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatAddOp; /** * The generator for a {@link JitFloatAddOp float_add}. * *

    - * This uses the binary operator generator and simply emits {@link #FADD} or {@link #DADD} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#fadd(Emitter) fadd} or + * {@link Op#dadd(Emitter) dadd} depending on the type. */ -public enum FloatAddOpGen implements FloatBinOpGen { +public enum FloatAddOpGen implements FloatOpBinOpGen { /** The generator singleton */ GEN; @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitFloatAddOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - assert rType == lType; - switch (lType) { - case FloatJitType t -> rv.visitInsn(FADD); - case DoubleJitType t -> rv.visitInsn(DADD); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return lType; + public , N0 extends Ent> // + Emitter> opForFloat(Emitter em) { + return Op.fadd(em); + } + + @Override + public , N0 extends Ent> // + Emitter> opForDouble(Emitter em) { + return Op.dadd(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java index 526d3f9f56..8a5e706deb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCeilOpGen.java @@ -15,16 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_$DOUBLE_UNOP; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_MATH; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_MATH; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatCeilOp; /** @@ -34,25 +34,25 @@ import ghidra.pcode.emu.jit.op.JitFloatCeilOp; * This uses the unary operator generator and emits an invocation of {@link Math#ceil(double)}, * possibly surrounding it with conversions from and to float. */ -public enum FloatCeilOpGen implements FloatUnOpGen { +public enum FloatCeilOpGen implements FloatOpUnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatCeilOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case FloatJitType t -> { - // There apparently is no Math.ceil(float)??? - rv.visitInsn(F2D); - rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "ceil", MDESC_$DOUBLE_UNOP, false); - rv.visitInsn(D2F); - } - case DoubleJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "ceil", - MDESC_$DOUBLE_UNOP, false); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForFloat(Emitter em) { + return em + .emit(Op::f2d) + .emit(this::opForDouble) + .emit(Op::d2f); + } + + @Override + public > Emitter> + opForDouble(Emitter em) { + return em + .emit(Op::invokestatic, T_MATH, "ceil", MDESC_$DOUBLE_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCompareBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCompareBinOpGen.java new file mode 100644 index 0000000000..5e01feb8f5 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatCompareBinOpGen.java @@ -0,0 +1,140 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import static ghidra.lifecycle.Unfinished.TODO; + +import java.util.Comparator; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitFloatTestOp; + +/** + * An extension for float comparison operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatCompareBinOpGen extends BinOpGen { + + @Override + default boolean isSigned() { + return false; + } + + /** + * Emit the JVM bytecode to perform the comparison with float operands on the stack. + *

    + * The result should be as defined by {@link Comparator#compare(Object, Object)}. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> + Emitter> opForFloatCmp(Emitter em); + + /** + * Emit the JVM bytecode to perform the comparison with double operands on the stack. + *

    + * The result should be as defined by {@link Comparator#compare(Object, Object)}. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> + Emitter> opForDoubleCmp(Emitter em); + + /** + * Emit the JVM opcode to perform the conditional jump. + *

    + * The condition should correspond to the true case of the p-code operator. + * + * @param the tail of the incoming stack + * @param the incoming stack including the comparison result + * @param em the emitter typed with the incoming stack + * @return the target label and emitter typed with the resulting stack, i.e., with the + * comparison result popped + */ + > LblEm opForCondJump(Emitter em); + + /** + * {@inheritDoc} + * + *

    + * This implementation reduces the need to just a few opcodes: 1) the opcode for comparing in + * case of JVM {@code float}, 2) the opcode for comparing in the case of JVM {@code double}, and + * 3) the conditional jump on the result of that comparison. First, the comparison opcode is + * emitted. It should result in and int <0, ==0, or >0 on the stack, depending on whether + * L<R, L==R, or L>R, respectively. Then the conditional jump is emitted. We place labels + * in an if-else pattern to place either a 1 (true) or 0 (false) value of the appropriate p-code + * type on the stack. + * + * @implNote This template is consistently generated by the Java compiler (Adoptium OpenJDK 21), + * despite there being possible branchless implementations. That could indicate one of + * a few things: 1) the HotSpot JIT knows how to optimize this pattern, perhaps using + * branchless native instructions, 2) branchless optimizations don't yield the speedup + * here we might expect, or 3) they didn't care to optimize. TODO: Investigate + * in case it's thing 3. We might like to see if branchless JVM bytecodes can improve + * performance. + */ + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType lType = gen.resolveType(op.l(), op.lType()); + JitType rType = gen.resolveType(op.r(), op.rType()); + assert rType == lType; + Emitter> emCmp = switch (lType) { + case FloatJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, ext()) + .emit(gen::genReadToStack, localThis, op.r(), t, rExt()) + .emit(this::opForFloatCmp); + case DoubleJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, Ext.ZERO) + .emit(gen::genReadToStack, localThis, op.r(), t, Ext.ZERO) + .emit(this::opForDoubleCmp); + case MpFloatJitType t -> TODO("MpFloat"); + default -> throw new AssertionError(); + }; + // LATER: Given the type conversion during write, we may not need a jump at all. + var lblTrue = emCmp + .emit(this::opForCondJump); + var lblDone = lblTrue.em() + .emit(Op::ldc__i, 0) + .emit(Op::goto_); + return new LiveOpResult(lblDone.em() + .emit(Lbl::placeDead, lblTrue.lbl()) + .emit(Op::ldc__i, 1) + .emit(Lbl::place, lblDone.lbl()) + .emit(gen::genWriteFromStack, localThis, op.out(), IntJitType.I4, Ext.ZERO, scope)); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatConvertUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatConvertUnOpGen.java new file mode 100644 index 0000000000..3c599c1d25 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatConvertUnOpGen.java @@ -0,0 +1,66 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import java.util.function.Function; + +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.pcode.emu.jit.op.JitUnOp; + +/** + * An extension for float conversion operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatConvertUnOpGen extends UnOpGen { + + /** + * An implementation based on a given bytecode op + * + * @param the type of the generated passage + * @param the JVM type of the input operand + * @param the p-code type of the input operand + * @param the JVM type of the output operand + * @param the p-code type of the output operand + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param op the p-code op + * @param ut the p-code type of the input operand + * @param ot the p-code type of the output operand + * @param opcode a method reference, e.g., to {@link Op#f2d(Emitter)} for the conversion + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack + */ + default , + UJT extends SimpleJitType, OT extends BPrim, + OJT extends SimpleJitType> Emitter gen(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, UJT ut, OJT ot, + Function>, Emitter>> opcode, + Scope scope) { + return em + .emit(gen::genReadToStack, localThis, op.u(), ut, ext()) + .emit(opcode) + .emit(gen::genWriteFromStack, localThis, op.out(), ot, ext(), scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java index 8af81ed2e2..8c9f56abf8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatDivOpGen.java @@ -15,37 +15,34 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatDivOp; /** * The generator for a {@link JitFloatDivOp float_div}. * *

    - * This uses the binary operator generator and simply emits {@link #FDIV} or {@link #DDIV} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#fdiv(Emitter) fdiv} or + * {@link Op#ddiv(Emitter) ddiv} depending on the type. */ -public enum FloatDivOpGen implements FloatBinOpGen { +public enum FloatDivOpGen implements FloatOpBinOpGen { /** The generator singleton */ GEN; @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitFloatDivOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - assert rType == lType; - switch (lType) { - case FloatJitType t -> rv.visitInsn(FDIV); - case DoubleJitType t -> rv.visitInsn(DDIV); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return lType; + public , N0 extends Ent> // + Emitter> opForFloat(Emitter em) { + return Op.fdiv(em); + } + + @Override + public , N0 extends Ent> // + Emitter> opForDouble(Emitter em) { + return Op.ddiv(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatEqualOpGen.java index fa332998c8..a02482e213 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatEqualOpGen.java @@ -15,31 +15,40 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitFloatEqualOp; /** * The generator for a {@link JitFloatEqualOp float_equal}. * *

    - * This uses the float comparison operator generator and simply emits {@link #FCMPL} or - * {@link #DCMPL} depending on the type and then {@link #IFEQ}. + * This uses the float comparison operator generator and simply emits {@link Op#fcmpl(Emitter) + * fcmpl} or {@link Op#dcmpl(Emitter) dcmpl} depending on the type and then {@link Op#ifeq(Emitter) + * ifeq}. */ -public enum FloatEqualOpGen implements CompareFloatOpGen { +public enum FloatEqualOpGen implements FloatCompareBinOpGen { /** The generator singleton */ GEN; @Override - public int fcmpOpcode() { - return FCMPL; + public , N0 extends Ent> // + Emitter> opForFloatCmp(Emitter em) { + return Op.fcmpl(em); } @Override - public int dcmpOpcode() { - return DCMPL; + public , N0 extends Ent> // + Emitter> opForDoubleCmp(Emitter em) { + return Op.dcmpl(em); } @Override - public int condOpcode() { - return IFEQ; + public > LblEm opForCondJump(Emitter em) { + return Op.ifeq(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java index 330a56bc4f..ef3f6983ed 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloat2FloatOpGen.java @@ -17,48 +17,56 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitFloatFloat2FloatOp; /** * The generator for a {@link JitFloatFloat2FloatOp float_float2float}. * *

    - * This uses the unary operator generator and emits {@link #F2D} or {@link #D2F}. + * This uses the unary operator generator and emits {@link Op#f2d(Emitter) f2d} or + * {@link Op#d2f(Emitter) d2f}. */ -public enum FloatFloat2FloatOpGen implements FloatUnOpGen { +public enum FloatFloat2FloatOpGen implements FloatConvertUnOpGen { /** The generator singleton */ GEN; - private JitType gen(MethodVisitor rv, int opcode, JitType type) { - rv.visitInsn(opcode); - return type; + @Override + public boolean isSigned() { + return false; } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatFloat2FloatOp op, - JitBlock block, JitType uType, MethodVisitor rv) { - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - return switch (uType) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitFloatFloat2FloatOp op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + JitType outType = gen.resolveType(op.out(), op.type()); + return new LiveOpResult(switch (uType) { case FloatJitType ut -> switch (outType) { - case FloatJitType ot -> ot; - case DoubleJitType ot -> gen(rv, F2D, ot); + case FloatJitType ot -> em; + case DoubleJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::f2d, scope); case MpFloatJitType ot -> TODO("MpFloat"); default -> throw new AssertionError(); }; case DoubleJitType ut -> switch (outType) { - case FloatJitType ot -> gen(rv, D2F, ot); - case DoubleJitType ot -> ot; + case FloatJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::d2f, scope); + case DoubleJitType ot -> em; case MpFloatJitType ot -> TODO("MpFloat"); default -> throw new AssertionError(); }; case MpFloatJitType ot -> TODO("MpFloat"); default -> throw new AssertionError(); - }; + }); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java index 3a90bef445..45059bb549 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatFloorOpGen.java @@ -15,16 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_$DOUBLE_UNOP; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_MATH; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_MATH; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatFloorOp; /** @@ -34,25 +34,25 @@ import ghidra.pcode.emu.jit.op.JitFloatFloorOp; * This uses the unary operator generator and emits an invocation of {@link Math#floor(double)}, * possibly surrounding it with conversions from and to float. */ -public enum FloatFloorOpGen implements FloatUnOpGen { +public enum FloatFloorOpGen implements FloatOpUnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatFloorOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case FloatJitType t -> { - // There apparently is no Math.floor(float)??? - rv.visitInsn(F2D); - rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "floor", MDESC_$DOUBLE_UNOP, false); - rv.visitInsn(D2F); - } - case DoubleJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "floor", - MDESC_$DOUBLE_UNOP, false); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForFloat(Emitter em) { + return em + .emit(Op::f2d) + .emit(this::opForDouble) + .emit(Op::d2f); + } + + @Override + public > Emitter> + opForDouble(Emitter em) { + return em + .emit(Op::invokestatic, T_MATH, "floor", MDESC_$DOUBLE_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java index 5de1e6215b..0c43059d88 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatInt2FloatOpGen.java @@ -17,22 +17,27 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitFloatInt2FloatOp; /** * The generator for a {@link JitFloatInt2FloatOp float_int2float}. * *

    - * This uses the unary operator generator and emits {@link #I2F}, {@link #I2D}, {@link #L2F}, or - * {@link #L2D}. + * This uses the unary operator generator and emits {@link Op#i2f(Emitter) i2f}, + * {@link Op#i2d(Emitter) i2d}, {@link Op#l2f(Emitter) l2f}, or {@link Op#l2d(Emitter) l2d}. */ -public enum FloatInt2FloatOpGen implements UnOpGen { +public enum FloatInt2FloatOpGen implements FloatConvertUnOpGen { /** The generator singleton */ GEN; @@ -41,30 +46,27 @@ public enum FloatInt2FloatOpGen implements UnOpGen { return false; // TODO: Is it signed? Test to figure it out. } - private JitType gen(MethodVisitor rv, int opcode, JitType type) { - rv.visitInsn(opcode); - return type; - } - @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatInt2FloatOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - return switch (uType) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitFloatInt2FloatOp op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + JitType outType = gen.resolveType(op.out(), op.type()); + return new LiveOpResult(switch (uType) { case IntJitType ut -> switch (outType) { - case FloatJitType ot -> gen(rv, I2F, ot); - case DoubleJitType ot -> gen(rv, I2D, ot); - case MpFloatJitType ot -> TODO("MpInt/Float"); + case FloatJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::i2f, scope); + case DoubleJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::i2d, scope); + case MpFloatJitType ot -> TODO("MpFloat"); default -> throw new AssertionError(); }; case LongJitType ut -> switch (outType) { - case FloatJitType ot -> gen(rv, L2F, ot); - case DoubleJitType ot -> gen(rv, L2D, ot); - case MpFloatJitType ot -> TODO("MpInt/Float"); + case FloatJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::l2f, scope); + case DoubleJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::l2d, scope); + case MpFloatJitType ot -> TODO("MpFloat"); default -> throw new AssertionError(); }; - case MpIntJitType ut -> TODO("MpInt/Float"); + case MpIntJitType ut -> TODO("MpInt->Float"); default -> throw new AssertionError(); - }; + }); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessEqualOpGen.java index 3a4e344406..ffbb5eccfc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessEqualOpGen.java @@ -15,31 +15,40 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitFloatLessEqualOp; /** * The generator for a {@link JitFloatLessEqualOp float_lessequal}. * *

    - * This uses the float comparison operator generator and simply emits {@link #FCMPG} or - * {@link #DCMPG} depending on the type and then {@link #IFLE}. + * This uses the float comparison operator generator and simply emits {@link Op#fcmpg(Emitter) + * fcmpg} or {@link Op#dcmpg(Emitter) dcmpg} depending on the type and then {@link Op#ifle(Emitter) + * ifle}. */ -public enum FloatLessEqualOpGen implements CompareFloatOpGen { +public enum FloatLessEqualOpGen implements FloatCompareBinOpGen { /** The generator singleton */ GEN; @Override - public int fcmpOpcode() { - return FCMPG; + public , N0 extends Ent> // + Emitter> opForFloatCmp(Emitter em) { + return Op.fcmpg(em); } @Override - public int dcmpOpcode() { - return DCMPG; + public , N0 extends Ent> // + Emitter> opForDoubleCmp(Emitter em) { + return Op.dcmpg(em); } @Override - public int condOpcode() { - return IFLE; + public > LblEm opForCondJump(Emitter em) { + return Op.ifle(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessOpGen.java index 4600dd0c64..f08e25b700 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatLessOpGen.java @@ -15,31 +15,40 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitFloatLessOp; /** * The generator for a {@link JitFloatLessOp float_less}. * *

    - * This uses the float comparison operator generator and simply emits {@link #FCMPG} or - * {@link #DCMPG} depending on the type and then {@link #IFLT}. + * This uses the float comparison operator generator and simply emits {@link Op#fcmpg(Emitter) + * fcmpg} or {@link Op#dcmpg(Emitter) dcmpg} depending on the type and then {@link Op#iflt(Emitter) + * iflt}. */ -public enum FloatLessOpGen implements CompareFloatOpGen { +public enum FloatLessOpGen implements FloatCompareBinOpGen { /** The generator singleton */ GEN; @Override - public int fcmpOpcode() { - return FCMPG; + public , N0 extends Ent> // + Emitter> opForFloatCmp(Emitter em) { + return Op.fcmpg(em); } @Override - public int dcmpOpcode() { - return DCMPG; + public , N0 extends Ent> // + Emitter> opForDoubleCmp(Emitter em) { + return Op.dcmpg(em); } @Override - public int condOpcode() { - return IFLT; + public > LblEm opForCondJump(Emitter em) { + return Op.iflt(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java index e202284646..c63f3593b5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatMultOpGen.java @@ -15,37 +15,34 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatMultOp; /** * The generator for a {@link JitFloatMultOp float_mult}. * *

    - * This uses the binary operator generator and simply emits {@link #FMUL} or {@link #DMUL} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#fmul(Emitter) fmul} or + * {@link Op#dmul(Emitter) dmul} depending on the type. */ -public enum FloatMultOpGen implements FloatBinOpGen { +public enum FloatMultOpGen implements FloatOpBinOpGen { /** The generator singleton */ GEN; @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitFloatMultOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - assert rType == lType; - switch (lType) { - case FloatJitType t -> rv.visitInsn(FMUL); - case DoubleJitType t -> rv.visitInsn(DMUL); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return lType; + public , N0 extends Ent> // + Emitter> opForFloat(Emitter em) { + return Op.fmul(em); + } + + @Override + public , N0 extends Ent> // + Emitter> opForDouble(Emitter em) { + return Op.dmul(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java index 2c73c8b4b6..7edc733e00 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNaNOpGen.java @@ -18,12 +18,18 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitFloatNaNOp; /** @@ -33,21 +39,35 @@ import ghidra.pcode.emu.jit.op.JitFloatNaNOp; * This uses the unary operator generator and emits an invocation of {@link Float#isNaN(float)} or * {@link Double#isNaN(double)}, depending on the type. */ -public enum FloatNaNOpGen implements FloatUnOpGen { +public enum FloatNaNOpGen implements UnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatNaNOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case FloatJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_FLOAT, "isNaN", - MDESC_FLOAT__IS_NAN, false); - case DoubleJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_DOUBLE, "isNaN", - MDESC_DOUBLE__IS_NAN, false); + public boolean isSigned() { + return false; + } + + @Override + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitFloatNaNOp op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + return new LiveOpResult(switch (uType) { + case FloatJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(Op::invokestatic, TR_FLOAT, "isNaN", MDESC_FLOAT__IS_NAN, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(gen::genWriteFromStack, localThis, op.out(), IntJitType.I4, ext(), scope); + case DoubleJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(Op::invokestatic, TR_DOUBLE, "isNaN", MDESC_DOUBLE__IS_NAN, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(gen::genWriteFromStack, localThis, op.out(), IntJitType.I4, ext(), scope); case MpFloatJitType t -> TODO("MpFloat"); default -> throw new AssertionError(); - } - return IntJitType.I4; + }); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java index e2777d0734..8669023630 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNegOpGen.java @@ -15,35 +15,34 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatNegOp; /** * The generator for a {@link JitFloatNegOp float_neg}. * *

    - * This uses the unary operator generator and emits {@link #FNEG} or {@link #DNEG}. + * This uses the unary operator generator and emits {@link Op#fneg(Emitter) fneg} or + * {@link Op#dneg(Emitter) dneg}. */ -public enum FloatNegOpGen implements FloatUnOpGen { +public enum FloatNegOpGen implements FloatOpUnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatNegOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case FloatJitType t -> rv.visitInsn(FNEG); - case DoubleJitType t -> rv.visitInsn(DNEG); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForFloat(Emitter em) { + return Op.fneg(em); + } + + @Override + public > Emitter> + opForDouble(Emitter em) { + return Op.dneg(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNotEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNotEqualOpGen.java index bb807a2ff9..74572f513a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNotEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatNotEqualOpGen.java @@ -15,31 +15,40 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitFloatNotEqualOp; /** * The generator for a {@link JitFloatNotEqualOp float_notequal}. * *

    - * This uses the float comparison operator generator and simply emits {@link #FCMPL} or - * {@link #DCMPL} depending on the type and then {@link #IFNE}. + * This uses the float comparison operator generator and simply emits {@link Op#fcmpl(Emitter) + * fcmpl} or {@link Op#dcmpl(Emitter) dcmpl} depending on the type and then {@link Op#ifne(Emitter) + * ifne}. */ -public enum FloatNotEqualOpGen implements CompareFloatOpGen { +public enum FloatNotEqualOpGen implements FloatCompareBinOpGen { /** The generator singleton */ GEN; @Override - public int fcmpOpcode() { - return FCMPL; + public , N0 extends Ent> // + Emitter> opForFloatCmp(Emitter em) { + return Op.fcmpl(em); } @Override - public int dcmpOpcode() { - return DCMPL; + public , N0 extends Ent> // + Emitter> opForDoubleCmp(Emitter em) { + return Op.dcmpl(em); } @Override - public int condOpcode() { - return IFNE; + public > LblEm opForCondJump(Emitter em) { + return Op.ifne(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatOpBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatOpBinOpGen.java new file mode 100644 index 0000000000..add902db5a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatOpBinOpGen.java @@ -0,0 +1,91 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import static ghidra.lifecycle.Unfinished.TODO; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitFloatBinOp; + +/** + * An extension for floating-point binary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatOpBinOpGen extends BinOpGen { + + @Override + default boolean isSigned() { + return false; + } + + /** + * Emit the JVM bytecode to perform the operator with float operands on the stack. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> + Emitter> opForFloat(Emitter em); + + /** + * Emit the JVM bytecode to perform the operator with double operands on the stack. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> + Emitter> opForDouble(Emitter em); + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType lType = gen.resolveType(op.l(), op.lType()); + JitType rType = gen.resolveType(op.r(), op.rType()); + assert rType == lType; + return new LiveOpResult(switch (lType) { + case FloatJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, ext()) + .emit(gen::genReadToStack, localThis, op.r(), t, rExt()) + .emit(this::opForFloat) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case DoubleJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, Ext.ZERO) + .emit(gen::genReadToStack, localThis, op.r(), t, Ext.ZERO) + .emit(this::opForDouble) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case MpFloatJitType t -> TODO("MpFloat"); + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatOpUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatOpUnOpGen.java new file mode 100644 index 0000000000..3c851b04f2 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatOpUnOpGen.java @@ -0,0 +1,83 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitFloatUnOp; + +/** + * An extension for floating-point unary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatOpUnOpGen extends UnOpGen { + @Override + default boolean isSigned() { + return false; + } + + /** + * Emit the JVM bytecode to perform the operator with float operands on the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack with the input operand on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + > Emitter> + opForFloat(Emitter em); + + /** + * Emit the JVM bytecode to perform the operator with double operands on the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack with the input operand on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + > Emitter> + opForDouble(Emitter em); + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + return new LiveOpResult(switch (uType) { + case FloatJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(this::opForFloat) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case DoubleJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(this::opForDouble) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case MpFloatJitType t -> Unfinished.TODO("MpFloat"); + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java index b28cb5d4ba..aea3c9ada7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatRoundOpGen.java @@ -15,16 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_$DOUBLE_UNOP; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_MATH; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_MATH; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatRoundOp; /** @@ -37,30 +37,31 @@ import ghidra.pcode.emu.jit.op.JitFloatRoundOp; * {@code round(x) = floor(x + 0.5)}. This uses the unary operator generator and emits the bytecode * to implement that definition, applying type conversions as needed. */ -public enum FloatRoundOpGen implements FloatUnOpGen { +public enum FloatRoundOpGen implements FloatOpUnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatRoundOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - // Math.round also converts to int/long - case FloatJitType t -> { - rv.visitLdcInsn(0.5f); - rv.visitInsn(FADD); - rv.visitInsn(F2D); - rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "floor", MDESC_$DOUBLE_UNOP, false); - rv.visitInsn(D2F); - } - case DoubleJitType t -> { - rv.visitLdcInsn(0.5d); - rv.visitInsn(DADD); - rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "floor", MDESC_$DOUBLE_UNOP, false); - } - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForFloat(Emitter em) { + return em + .emit(Op::ldc__f, 0.5f) + .emit(Op::fadd) + .emit(Op::f2d) + .emit(Op::invokestatic, T_MATH, "floor", MDESC_$DOUBLE_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::d2f); + } + + @Override + public > Emitter> + opForDouble(Emitter em) { + return em + .emit(Op::ldc__d, 0.5) + .emit(Op::dadd) + .emit(Op::invokestatic, T_MATH, "floor", MDESC_$DOUBLE_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java index 5bb86b34fe..5a86712337 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSqrtOpGen.java @@ -15,16 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_$DOUBLE_UNOP; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_MATH; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_MATH; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatSqrtOp; /** @@ -34,24 +34,25 @@ import ghidra.pcode.emu.jit.op.JitFloatSqrtOp; * This uses the unary operator generator and emits an invocation of {@link Math#sqrt(double)}, * possibly surrounding it with conversions from and to float. */ -public enum FloatSqrtOpGen implements FloatUnOpGen { +public enum FloatSqrtOpGen implements FloatOpUnOpGen { /** The generator singleton */ GEN; @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatSqrtOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case FloatJitType t -> { - rv.visitInsn(F2D); - rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "sqrt", MDESC_$DOUBLE_UNOP, false); - rv.visitInsn(D2F); - } - case DoubleJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_MATH, "sqrt", - MDESC_$DOUBLE_UNOP, false); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return uType; + public > Emitter> + opForFloat(Emitter em) { + return em + .emit(Op::f2d) + .emit(this::opForDouble) + .emit(Op::d2f); + } + + @Override + public > Emitter> + opForDouble(Emitter em) { + return em + .emit(Op::invokestatic, T_MATH, "sqrt", MDESC_$DOUBLE_UNOP, false) + .step(Inv::takeArg) + .step(Inv::ret); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java index 9bfc9a683a..23dbe2bb4c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatSubOpGen.java @@ -15,37 +15,34 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; import ghidra.pcode.emu.jit.op.JitFloatSubOp; /** * The generator for a {@link JitFloatSubOp float_sub}. * *

    - * This uses the binary operator generator and simply emits {@link #FSUB} or {@link #DSUB} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#fsub(Emitter) fsub} or + * {@link Op#dsub(Emitter) dsub} depending on the type. */ -public enum FloatSubOpGen implements FloatBinOpGen { +public enum FloatSubOpGen implements FloatOpBinOpGen { /** The generator singleton */ GEN; @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitFloatSubOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - assert rType == lType; - switch (lType) { - case FloatJitType t -> rv.visitInsn(FSUB); - case DoubleJitType t -> rv.visitInsn(DSUB); - case MpFloatJitType t -> TODO("MpFloat"); - default -> throw new AssertionError(); - } - return lType; + public , N0 extends Ent> // + Emitter> opForFloat(Emitter em) { + return Op.fsub(em); + } + + @Override + public , N0 extends Ent> // + Emitter> opForDouble(Emitter em) { + return Op.dsub(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java index e7a2f41898..05efe797bb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatTruncOpGen.java @@ -17,49 +17,56 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitFloatTruncOp; /** * The generator for a {@link JitFloatTruncOp float_trunc}. * *

    - * This uses the unary operator generator and emits {@link #F2I}, {@link #F2L}, {@link #D2I}, or - * {@link #D2L}. + * This uses the unary operator generator and emits {@link Op#f2i(Emitter) f2i}, + * {@link Op#f2l(Emitter) f2l}, {@link Op#d2i(Emitter) d2i}, or {@link Op#d2l(Emitter) d2l}. */ -public enum FloatTruncOpGen implements FloatUnOpGen { +public enum FloatTruncOpGen implements FloatConvertUnOpGen { /** The generator singleton */ GEN; - private JitType gen(MethodVisitor rv, int opcode, JitType type) { - rv.visitInsn(opcode); - return type; + @Override + public boolean isSigned() { + return false; // TODO: Is it signed? Test to figure it out. } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitFloatTruncOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - return switch (uType) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitFloatTruncOp op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + JitType outType = gen.resolveType(op.out(), op.type()); + return new LiveOpResult(switch (uType) { case FloatJitType ut -> switch (outType) { - case IntJitType ot -> gen(rv, F2I, ot); - case LongJitType ot -> gen(rv, F2L, ot); - case MpIntJitType ot -> TODO("MpFloat/Int"); + case IntJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::f2i, scope); + case LongJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::f2l, scope); + case MpIntJitType ot -> TODO("Float->MpInt"); default -> throw new AssertionError(); }; case DoubleJitType ut -> switch (outType) { - case IntJitType ot -> gen(rv, D2I, ot); - case LongJitType ot -> gen(rv, D2L, ot); - case MpIntJitType ot -> TODO("MpFloat/Int"); + case IntJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::d2i, scope); + case LongJitType ot -> gen(em, localThis, gen, op, ut, ot, Op::d2l, scope); + case MpIntJitType ot -> TODO("Float->MpInt"); default -> throw new AssertionError(); }; - case MpIntJitType ot -> TODO("MpFloat/Int"); + case MpFloatJitType ut -> TODO("MpInt->Float"); default -> throw new AssertionError(); - }; + }); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java index 2e9b107b5b..0c9443b097 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/Int2CompOpGen.java @@ -15,27 +15,35 @@ */ package ghidra.pcode.emu.jit.gen.op; +import java.util.ArrayList; import java.util.List; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.MpIntLocalOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd.SimpleOpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitInt2CompOp; /** * The generator for a {@link JitInt2CompOp int_2comp}. - * *

    - * This uses the unary operator generator and emits {@link #INEG} or {@link #LNEG}, depending on - * type. + * This uses the unary operator generator and emits {@link Op#ineg(Emitter) ineg} or + * {@link Op#lneg(Emitter) lneg}, depending on type. + *

    + * The multi-precision logic is similar to {@link IntAddOpGen}. We follow the process "flip the bits + * and add 1", so for each leg, we consider that it may have a carry in. We then invert all the bits + * using ^-1 and then add that carry in. The least significant leg is assumed to have a carry in, + * effecting the +1. */ -public enum Int2CompOpGen implements IntUnOpGen { +public enum Int2CompOpGen implements IntOpUnOpGen { /** The generator singleton */ GEN; @@ -44,62 +52,139 @@ public enum Int2CompOpGen implements IntUnOpGen { return false; // TODO: Is it? Test with 3-byte operands to figure it out. } - private void generateMpIntLeg2Cmp(int idx, IntJitType type, boolean givesCarry, - MethodVisitor mv) { - // [carryN-1:LONG] - mv.visitVarInsn(ILOAD, idx); - // [legN:INT,carry:LONG] - mv.visitLdcInsn(-1 >>> (Integer.SIZE - type.size() * Byte.SIZE)); - // [ff:INT,legN:INT,carry:LONG] - mv.visitInsn(IXOR); - // [invN:INT,carry:LONG] - TypeConversions.generateIntToLong(type, LongJitType.I8, Ext.ZERO, mv); - // [invN:LONG,carry:LONG] - mv.visitInsn(LADD); - // [carry|2cmpN:LONG] - if (givesCarry) { - mv.visitInsn(DUP2); - // [carry|2cmpN:LONG,carry|2cmpN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, type, Ext.ZERO, mv); - // [2cmpN:INT,carry|2cmpN:LONG] - mv.visitVarInsn(ISTORE, idx); - // [carry|2cmpN:LONG] - mv.visitLdcInsn(Integer.SIZE); - // [32:INT, carry:LONG] - mv.visitInsn(LUSHR); - // [carryN:LONG] - } - else { - TypeConversions.generateLongToInt(LongJitType.I8, type, Ext.ZERO, mv); - // [2cmpN:INT] - mv.visitVarInsn(ISTORE, idx); - // [] - } - } - - private void generateMpInt2Comp(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - int legCount = type.legsAlloc(); - try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { - OpGen.generateMpLegsIntoTemp(result, legCount, mv); - List types = type.legTypes().reversed(); - mv.visitLdcInsn(1L); // Seed the "carry in" with the 1 to add - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - generateMpIntLeg2Cmp(result.idx(i), types.get(i), !isLast, mv); - } - OpGen.generateMpLegsFromTemp(result, legCount, mv); - } + @Override + public > Emitter> + opForInt(Emitter em) { + return Op.ineg(em); } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitInt2CompOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case IntJitType t -> rv.visitInsn(INEG); - case LongJitType t -> rv.visitInsn(LNEG); - case MpIntJitType t -> generateMpInt2Comp(gen, t, rv); - default -> throw new AssertionError(); + public > Emitter> + opForLong(Emitter em) { + return Op.lneg(em); + } + + /** + * Emit bytecode to process a leg of the input operand with the carry in + * + * @param em the emitter typed with a stack of one long, the carry in + * @param opnd the operand containing the leg from the input multi-precision operand + * @param shiftCarry indicates whether or not the long needs to be shifted to position the carry + * bit + * @return the emitter typed with a stack of one long, the inverted leg and carry out + */ + static Emitter> prepOpndAndCarry(Emitter> em, + SimpleOpnd opnd, boolean shiftCarry) { + if (shiftCarry) { + em = em + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr); } - return uType; + return em + .emit(opnd::read) + .emit(Op::ldc__i, -1 >>> (Integer.SIZE - opnd.type().size() * Byte.SIZE)) + .emit(Op::ixor) + .emit(Op::invokestatic, GenConsts.TR_INTEGER, "toUnsignedLong", + GenConsts.MDESC_INTEGER__TO_UNSIGNED_LONG, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ladd); + } + + /** + * Emit bytecode to invert the given leg of the input operand, leaving the carry out on the + * stack. + *

    + * This assumes the stack has a carry from the previous leg's complement. It is either in bit + * position 32, if {@code shiftCarry} is set, or bit position 0, otherwise. It shifts, if + * needed, the carry bit into position 0 and then adds the inverted leg. It writes the lower 32 + * bits of that, i.e., the resulting complement, into an output operand, and then leaves the + * carry out in bit position 32 of the long on the stack. + *

    + * If {@code opnd} is writable, the result is written there and that operand returned. + * Otherwise, a new operand is generated. The caller must em>always use the returned + * operand to construct the legs of the final multi-precision output operand. It must + * never use {@code opnd}, nor the multi-precision operand containing it, as the final + * output. + * + * @param em the emitter typed with a stack of one long, the carry out of the previous leg's + * complement, i.e., the carry in for this leg's complement. + * @param opnd the leg for the input multi-precision operand + * @param shiftCarry true to indicate the carry bit must be shifted from position 32 to 0. + * @param scope a scope for generating temporary local storage + * @return the output operand and the emitter typed with a stack of one long whose value is the + * carry out for this leg's complement. + */ + static SimpleOpndEm> genMpIntLeg2CmpTakesAndGivesCarry( + Emitter> em, SimpleOpnd opnd, boolean shiftCarry, + Scope scope) { + return em + .emit(Int2CompOpGen::prepOpndAndCarry, opnd, shiftCarry) + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(opnd::write, scope); + } + + /** + * Emit bytecode as in + * {@link #genMpIntLeg2CmpTakesAndGivesCarry(Emitter, SimpleOpnd, boolean, Scope)} except that + * we do not leave a carry out on the stack. + * + * @param em the emitter typed with a stack of one long, the carry out of the previous leg's + * complement, i.e., the carry in for this leg's complement. + * @param opnd the leg for the input multi-precision operand + * @param shiftCarry true to indicate the carry bit must be shifted from position 32 to 0. + * @param scope a scope for generating temporary local storage + * @return the output operand and the emitter typed with the empty stack. + */ + static SimpleOpndEm genMpIntLeg2CmpTakesCarry( + Emitter> em, SimpleOpnd opnd, boolean shiftCarry, + Scope scope) { + return em + .emit(Int2CompOpGen::prepOpndAndCarry, opnd, shiftCarry) + .emit(Op::l2i) + .emit(opnd::write, scope); + } + + /** + * {@inheritDoc} + *

    + * The strategy here follows after + * {@link IntAddOpGen#genRunMpInt(Emitter, Local, JitCodeGenerator, ghidra.pcode.emu.jit.op.JitIntAddOp, MpIntJitType, Scope)}. + * We no longer need to assert a minimum length, since we provide a "carry in" of 1 for the + * initial leg. We initialize the complement by loading that 1 onto the stack and invoking + * {@link #genMpIntLeg2CmpTakesAndGivesCarry(Emitter, SimpleOpnd, boolean, Scope)} with + * {@code shiftCarry} cleared. We terminate the operation with + * {@link #genMpIntLeg2CmpTakesCarry(Emitter, SimpleOpnd, boolean, Scope)}. We use + * {@link #genMpIntLeg2CmpTakesAndGivesCarry(Emitter, SimpleOpnd, boolean, Scope)} with + * {@code shiftCarry} set for each middle leg. The resulting legs are all appended to form the + * final multi-precision output. + */ + @Override + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitInt2CompOp op, + MpIntJitType type, Scope scope) { + var opnd = gen.genReadToOpnd(em, localThis, op.u(), type, ext(), scope); + em = opnd.em(); + var legs = opnd.opnd().type().castLegsLE(opnd.opnd()); + + int legCount = type.legsAlloc(); + List> outLegs = new ArrayList<>(); + var emCarry = em + .emit(Op::ldc__l, 1); + for (int i = 0; i < legCount - 1; i++) { + var result = emCarry + .emit(Int2CompOpGen::genMpIntLeg2CmpTakesAndGivesCarry, legs.get(i), i != 0, + scope); + emCarry = result.em(); + outLegs.add(result.opnd()); + } + var result = emCarry + .emit(Int2CompOpGen::genMpIntLeg2CmpTakesCarry, legs.getLast(), true, scope); + em = result.em(); + outLegs.add(result.opnd()); + + var out = MpIntLocalOpnd.of(type, "out", outLegs); + return gen.genWriteFromOpnd(em, localThis, op.out(), out, ext(), scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java index d19eaf951c..183ace9618 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAddOpGen.java @@ -15,120 +15,261 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; +import java.util.ArrayList; +import java.util.List; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.MpIntLocalOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd.SimpleOpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntAddOp; /** * The generator for a {@link JitIntAddOp int_add}. - * *

    - * This uses the binary operator generator and simply emits {@link #IADD} or {@link #LADD} depending - * on the type. - * + * This uses the binary operator generator and simply emits {@link Op#iadd(Emitter) iadd} or + * {@link Op#ladd(Emitter) ladd} depending on the type. *

    - * NOTE: The multi-precision integer parts of this are a work in progress. + * The multi-precision integer logic is not such a simple matter. */ -public enum IntAddOpGen implements IntBinOpGen { +public enum IntAddOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; - static void generateMpIntLegAdd(JitCodeGenerator gen, int idx, boolean takesCarry, - boolean givesCarry, boolean storesResult, MethodVisitor mv) { - if (takesCarry) { - // [...,llegN:INT,olegN+1:LONG] - mv.visitLdcInsn(32); - mv.visitInsn(LUSHR); - // [...,lleg1...,carryinN:LONG] - mv.visitInsn(DUP2_X1); - mv.visitInsn(POP2); - // [...,carryinN:LONG,llegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); - // [...,carryinN:LONG,llegN:LONG] - mv.visitInsn(LADD); - // [...,sumpartN:LONG] - } - else { - // [...,llegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); - // [...,sumpartN:LONG] (legN + 0) - } - mv.visitVarInsn(ILOAD, idx); - // [...,sumpartN:LONG,rlegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); - // [...,sumpartN:LONG,rlegN:LONG] - mv.visitInsn(LADD); - // [...,olegN:LONG] - if (storesResult) { - if (givesCarry) { - mv.visitInsn(DUP2); - } - // [...,(olegN:LONG),olegN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); - // [...,(olegN:LONG),olegN:INT] - /** NB. The store will perform the masking */ - mv.visitVarInsn(ISTORE, idx); - // [...,(olegN:LONG)] - } - else { - if (!givesCarry) { - mv.visitInsn(POP2); - } - } - } - - private void generateMpIntAdd(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - /** - * The strategy is to allocate a temp local for each leg of the result. First, we'll pop the - * right operand into the temp. Then, as we work with each leg of the left operand, we'll - * execute the algorithm. Convert both right and left legs to a long and add them (along - * with a possible carry in). Store the result back into the temp locals. Shift the leg - * right 32 to get the carry out, then continue to the next leg up. The final carry out can - * be dropped (overflow). The result legs are then pushed back to the stack. - */ - // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) - int legCount = type.legsAlloc(); - try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { - OpGen.generateMpLegsIntoTemp(result, legCount, mv); - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - boolean takesCarry = i != 0; // not first - generateMpIntLegAdd(gen, result.idx(i), takesCarry, !isLast, true, mv); - } - OpGen.generateMpLegsFromTemp(result, legCount, mv); - } - } - @Override public boolean isSigned() { return false; } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntAddOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.iadd(em); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntAddOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); - switch (rType) { - case IntJitType t -> rv.visitInsn(IADD); - case LongJitType t -> rv.visitInsn(LADD); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntAdd(gen, t, rv); - case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); - default -> throw new AssertionError(); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.ladd(em); + } + + /** + * Emit bytecode to load a leg of the left operand and combine it with the carry in + *

    + * The carry in is a long where bit 32 has the value 0 or 1. The lower 32 bits, which are + * actually the result of the previous legs' addition, are ignored. We use a long because it can + * hold the full result of addition, where the carry out winds up at bit 32 of the long. This + * routine emits bytecode to shift the previous sum out, so that the carry bit is now at bit 0, + * i.e., the long how has the value 0 or 1. It then loads and adds the left leg into that long. + * + * @param em the emitter typed with a stack of one long, the carry in + * @param left the operand containing the leg from the left multi-precision operand + * @return the emitter typed with a stack of one long, the summed left and carry in + */ + static Emitter> prepLeftAndCarry(Emitter> em, + SimpleOpnd left) { + return em + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr) + .emit(left::read) + .emit(Op::i2l) + .emit(Op::ladd); + } + + /** + * Emit bytecode to load a leg of the right operand and add it to the summed left and carry in + *

    + * This completes the addition of the left and right legs along with the carry in. The long on + * the stack is now the result, with the carry out in bit position 32. + * + * @param em the emitter typed with a stack of one long, the summed left and carry in + * @param right the operand containing the leg from the right multi-precision operand + * @return the emitter typed with a stack of one long, the result and carry out + */ + static Emitter> addRight(Emitter> em, + SimpleOpnd right) { + return em + .emit(right::read) + .emit(Op::i2l) + .emit(Op::ladd); + } + + /** + * Conditionally emit bytecode to store the result into a leg of the output operand + *

    + * Other operators may borrow some components of this mp-int addition routine, e.g., to compute + * only the output carry bit. In those cases, storing the actual result of the addition is not + * necessary, but we still need to keep the intermediate carry bit. Thus, we provide this + * routine to conditionally tee the sum (lower 32-bits of the long on the stack) for a leg into + * a result leg. Whether or not we emit such bytecode, we ensure the value on the stack remains + * unchanged. + *

    + * See + * {@link #genMpIntLegAddTakesAndGivesCarry(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} + * regarding the returned record. + * + * @param em the emitter typed with a stack of one long, the result and carry out for a given + * leg + * @param into the output leg operand for the multi-precision output operand + * @param store true to emit the bytecode, false to emit nothing + * @param scope a scope for generating temporary local storage + * @return the output operand or null, and the emitter typed with a stack of one long whose + * value is unchanged. + */ + static SimpleOpndEm> maybeStore(Emitter> em, + SimpleOpnd into, boolean store, Scope scope) { + if (!store) { + return new SimpleOpndEm<>(null, em); } - return rType; + return em + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(into::write, scope); + } + + /** + * Emit bytecode to add two corresponding legs of the operands, leaving the carry out on the + * stack. + *

    + * This assumes the stack has a carry from the previous legs' sum in bit position 32. It shifts + * it into position and then adds in the two given legs. It conditionally writes the lower 32 + * bits of that, i.e., the resulting sum, into an output operand, and then leaves the carry out + * in bit position 32 of the long on the stack. + *

    + * The returned value is always a non-null record, but the value of the operand may vary. If + * {@code store} is false, the operand is always null. This will be the case, e.g., for + * computing the carry out of multi-precision addition, because the actual result is not needed. + * If {@code store} is true, the the returned operand may or may not be identical to the given + * {@code left} parameter, depending on whether or not that operand can be written. The caller + * must always use the returned operand to construct the legs of the final + * multi-precision output operand. It must never use {@code left}, nor the + * multi-precision operand containing it, as the final output. + * + * @param em the emitter typed with a stack of one long, the carry out of the previous legs' + * sum, i.e., the carry in for these legs' sum. + * @param left the leg for the left multi-precision operand + * @param right the leg for the right multi-precision operand + * @param storesResult true to receive the leg for the output multi-precision operand + * @param scope a scope for generating temporary local storage + * @return the output operand or null, and the emitter typed with a stack of one long whose + * value is the carry out for these legs' sum + */ + static SimpleOpndEm> genMpIntLegAddTakesAndGivesCarry( + Emitter> em, SimpleOpnd left, + SimpleOpnd right, boolean storesResult, Scope scope) { + return em + .emit(IntAddOpGen::prepLeftAndCarry, left) + .emit(IntAddOpGen::addRight, right) + .emit(IntAddOpGen::maybeStore, left, storesResult, scope); + } + + /** + * Emit bytecode as in + * {@link #genMpIntLegAddTakesAndGivesCarry(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} + * except that we do not expect a carry in on the stack. + *

    + * This should be used to initiate the addition, taking the least-significant legs of the input + * multi-precision operands. + * + * @param em the emitter typed with the empty stack + * @param left the leg for the left multi-precision operand + * @param right the leg for the right multi-precision operand + * @param storesResult true to receive the leg for the output multi-precision operand + * @param scope a scope for generating temporary local storage + * @return the output operand or null, and the emitter typed with a stack of one long whose + * value is the carry out for these legs' sum + */ + static SimpleOpndEm> genMpIntLegAddGivesCarry(Emitter em, + SimpleOpnd left, SimpleOpnd right, + boolean storesResult, Scope scope) { + return em + .emit(left::read) + .emit(Op::i2l) + .emit(IntAddOpGen::addRight, right) + .emit(IntAddOpGen::maybeStore, left, storesResult, scope); + } + + /** + * Emit bytecode as in + * {@link #genMpIntLegAddTakesAndGivesCarry(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} + * except that we do not leave a carry out on the stack. + *

    + * This should be used to finalize the addition, taking the most-significant legs of the input + * multi-precision operands. Note that this always stores the result and returns an output + * operand. Otherwise, this would give no output at all, since it does not leave a carry out on + * the stack. + * + * @param em the emitter typed with a stack of one long, the carry out of the previous legs' + * sum, i.e., the carry in for these legs' sum. + * @param left the leg for the left multi-precision operand + * @param right the leg for the right multi-precision operand + * @param scope a scope for generating temporary local storage + * @return the output operand and the emitter typed with the empty stack + */ + static SimpleOpndEm genMpIntLegAddTakesCarry(Emitter> em, + SimpleOpnd left, SimpleOpnd right, Scope scope) { + return em + .emit(IntAddOpGen::prepLeftAndCarry, left) + .emit(IntAddOpGen::addRight, right) + .emit(Op::l2i) + .emit(left::write, scope); + } + + /** + * {@inheritDoc} + *

    + * The strategy here follows from grade-school long addition. We assert that there are at least + * two legs, otherwise we would have just emitted a single add bytecode. This allows us to + * unconditionally initialize the addition with + * {@link #genMpIntLegAddGivesCarry(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} and + * terminate it with {@link #genMpIntLegAddTakesCarry(Emitter, SimpleOpnd, SimpleOpnd, Scope)}. + * When there are more than 2 legs, we use + * {@link #genMpIntLegAddTakesAndGivesCarry(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} as + * many times as necessary in the middle. For all legs, we store the result and append it as a + * leg to the final output. + */ + @Override + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntAddOp op, + MpIntJitType type, Scope scope) { + /** + * The strategy here is fairly simple. Ensure both operands are stored in locals. One of the + * operands must be in temporaries to ensure we don't modify an input in place. + *

    + * For each leg position, convert both right and left legs to a long and add them (along + * with a possible carry in). Store the result back into the temp locals. Shift the long sum + * right 32 to get the carry out, then continue to the next leg up. The final carry out can + * be dropped (overflow). + */ + var left = gen.genReadToOpnd(em, localThis, op.l(), type, ext(), scope); + var right = gen.genReadToOpnd(left.em(), localThis, op.r(), type, rExt(), scope); + em = right.em(); + var lLegs = left.opnd().type().castLegsLE(left.opnd()); + assert lLegs.size() >= 2; + var rLegs = right.opnd().type().castLegsLE(right.opnd()); + + List> outLegs = new ArrayList<>(); + int legCount = type.legsAlloc(); + + var first = genMpIntLegAddGivesCarry(em, lLegs.getFirst(), rLegs.getFirst(), true, scope); + var emCarry = first.em(); + outLegs.add(first.opnd()); + for (int i = 1; i < legCount - 1; i++) { + var result = + genMpIntLegAddTakesAndGivesCarry(emCarry, lLegs.get(i), rLegs.get(i), true, scope); + emCarry = result.em(); + outLegs.add(result.opnd()); + } + var last = genMpIntLegAddTakesCarry(emCarry, lLegs.getLast(), rLegs.getLast(), scope); + em = last.em(); + outLegs.add(last.opnd()); + + var out = MpIntLocalOpnd.of(type, "out", outLegs); + return gen.genWriteFromOpnd(em, localThis, op.out(), out, ext(), scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAndOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAndOpGen.java index a55f8ee240..19bc5d1320 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAndOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntAndOpGen.java @@ -15,26 +15,36 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntAndOp; /** * The generator for a {@link JitIntAndOp int_and}. * *

    - * This uses the bitwise binary operator and emits {@link #IAND} or {@link #LAND} depending on the - * type. + * This uses the bitwise binary operator and emits {@link Op#iand(Emitter) iand} or + * {@link Op#land(Emitter) land} depending on the type. */ -public enum IntAndOpGen implements BitwiseBinOpGen { +public enum IntAndOpGen implements IntBitwiseBinOpGen { /** The generator singleton */ GEN; @Override - public int intOpcode() { - return IAND; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.iand(em); } @Override - public int longOpcode() { - return LAND; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.land(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBitwiseBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBitwiseBinOpGen.java new file mode 100644 index 0000000000..366f47cf93 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBitwiseBinOpGen.java @@ -0,0 +1,73 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.MpIntLocalOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.pcode.emu.jit.op.JitBinOp; + +/** + * An extension for bitwise binary operators + *

    + * This provides a simple strategy for multi-precision integer implementation. Since all bit + * positions are considered independently, we just apply the same + * {@link #opForInt(Emitter, IntJitType)} operator to each pair of corresponding legs independently + * to compute each corresponding output leg. + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntBitwiseBinOpGen extends IntOpBinOpGen { + @Override + default boolean isSigned() { + return false; + } + + @Override + default Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, MpIntJitType type, + Scope scope) { + var left = gen.genReadToOpnd(em, localThis, op.l(), type, ext(), scope); + var right = gen.genReadToOpnd(left.em(), localThis, op.r(), type, rExt(), scope); + em = right.em(); + var lLegs = left.opnd().type().castLegsLE(left.opnd()); + var rLegs = right.opnd().type().castLegsLE(right.opnd()); + + List> outLegs = new ArrayList<>(); + int legCount = type.legsAlloc(); + for (int i = 0; i < legCount; i++) { + var result = em + .emit(lLegs.get(i)::read) + .emit(rLegs.get(i)::read) + .emit(this::opForInt, IntJitType.I4) + .emit(lLegs.get(i)::write, scope); + em = result.em(); + outLegs.add(result.opnd()); + } + var out = MpIntLocalOpnd.of(type, "out", outLegs); + return gen.genWriteFromOpnd(em, localThis, op.out(), out, ext(), scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java index 8c23201f47..15fa28a0e6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCarryOpGen.java @@ -15,171 +15,142 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntCarryOp; /** * The generator for a {@link JitIntCarryOp int_carry}. - * *

    - * This uses the binary operator generator. First we have to consider which strategy we are going to - * use. If the p-code type is strictly smaller than its host JVM type, we can simply add the two - * operands and examine the next bit up. This is accomplished by emitting {@link #IADD} or - * {@link #LADD}, depending on the type, followed by a shift right and a mask. - * + * This uses the integer predicate operator generator. First we have to consider which strategy we + * are going to use. If the p-code type is strictly smaller than its host JVM type, we can simply + * add the two operands and examine the next bit up. This is accomplished by emitting + * {@link Op#iadd(Emitter) iadd} or {@link Op#ladd(Emitter) ladd}, depending on the type, followed + * by a shift right and a mask. *

    * If the p-code type exactly fits its host JVM type, we still add, but we will need to compare the - * result to one of the operands. Thus, we override - * {@link #afterLeft(JitCodeGenerator, JitIntCarryOp, JitType, JitType, MethodVisitor) afterLeft} - * and emit code to duplicate the left operand. We can then add and invoke - * {@link Integer#compareUnsigned(int, int)} to determine whether there was overflow. If there was, - * then we know the carry bit would have been set. We can spare the conditional flow by just - * shifting the sign bit into the 1's place. - * + * result to one of the operands. Thus, we emit code to duplicate the left operand. We can then add + * and invoke {@link Integer#compareUnsigned(int, int)} (or similar for longs) to determine whether + * there was overflow. If there was, then we know the carry bit would have been set. We can spare + * the conditional flow by just shifting the sign bit into the 1's place. *

    - * NOTE: The multi-precision integer parts of this are a work in progress. + * For multi-precision integers, we invoke the subroutines in {@link IntAddOpGen}, but do not store + * the results, because we only need the carry. When we reach the end, we take advantage of the fact + * that the final stack result is actually the full 33-bit result for the last leg. We can just + * shift it the required number of bytes (depending on the type of the input operands) and mask for + * the desired carry bit. */ -public enum IntCarryOpGen implements IntBinOpGen { +public enum IntCarryOpGen implements IntPredBinOpGen { /** The generator singleton */ GEN; - private void generateMpIntCarry(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - /** - * Similar strategy as for INT_ADD. In fact, we call its per-leg logic. - */ - // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) - int legCount = type.legsAlloc(); - int remSize = type.partialSize(); - - try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", legCount)) { - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ISTORE, temp.idx(i)); - // NOTE: More significant legs have higher indices (reverse of stack) - } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean takesCarry = i != 0; // not first - IntAddOpGen.generateMpIntLegAdd(gen, temp.idx(i), takesCarry, true, false, mv); - } - // [olegN:LONG] - if (remSize == 0) { - // The last leg was full, so extract bit 32 - mv.visitLdcInsn(32); - } - else { - // The last leg was partial, so get the next more significant bit - mv.visitLdcInsn(remSize * Byte.SIZE); - } - mv.visitInsn(LUSHR); - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); - mv.visitLdcInsn(1); - mv.visitInsn(IAND); - } - } - @Override public boolean isSigned() { return false; } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntCarryOp op, JitType lType, JitType rType, - MethodVisitor rv) { - /** - * There are two strategies to use here depending on whether or not there's room to capture - * the carry bit. If there's not room, we have to compare the sum to one of the input - * operands. If the sum is less, then we can conclude there was a carry. For that strategy, - * we will need to keep a copy of the left operand, so duplicate it. - * - * On the other hand, if there is room to capture the carry, we can just add the two - * operands and extract the carry bit. There is no need to duplicate the left operand. - */ - lType = TypeConversions.forceUniform(gen, lType, rType, ext(), rv); - switch (lType) { - case IntJitType(int size) when size == Integer.BYTES -> rv.visitInsn(DUP); - case IntJitType lt -> { - } - case LongJitType(int size) when size == Long.BYTES -> rv.visitInsn(DUP2); - case LongJitType lt -> { - } - case MpIntJitType lt -> { - } - default -> throw new AssertionError(); + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + if (type == IntJitType.I4) { + return em + .emit(Op::dup_x1) // r l r + .emit(Op::iadd) + .emit(Op::swap) + .emit(Op::invokestatic, TR_INTEGER, "compareUnsigned", MDESC_INTEGER__COMPARE, + false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + // sum < l iff sign bit is 1 + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::iushr); } - return lType; + // Just add and extract the carry bit + return em + .emit(Op::iadd) + .emit(Op::ldc__i, type.size() * Byte.SIZE) + .emit(Op::ishr) + // LATER: This mask may not be necessary.... + .emit(Op::ldc__i, 1) + .emit(Op::iand); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntCarryOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); - switch (rType) { - case IntJitType(int size) when size == Integer.BYTES -> { - // [l,l,r] - rv.visitInsn(IADD); - // [l,sum] - rv.visitInsn(SWAP); // spare an LDC,XOR - // [sum,l] - rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", - MDESC_INTEGER__COMPARE, false); - // [cmpU(sum,l)] sum < l iff sign bit is 1 - rv.visitLdcInsn(31); - rv.visitInsn(IUSHR); - return IntJitType.I1; - } - case IntJitType(int size) -> { - // Just add and extract the carry bit - rv.visitInsn(IADD); - rv.visitLdcInsn(size * Byte.SIZE); - rv.visitInsn(ISHR); - rv.visitLdcInsn(1); - rv.visitInsn(IAND); - return IntJitType.I1; - } - case LongJitType(int size) when size == Long.BYTES -> { - // [l:LONG,l:LONG,r:LONG] - rv.visitInsn(LADD); - // [l:LONG,sum:LONG] - rv.visitInsn(DUP2_X2); - rv.visitInsn(POP2); - // [sum:LONG,l:LONG] - rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "compareUnsigned", - MDESC_LONG__COMPARE_UNSIGNED, false); - // [cmpU(sum,l):INT] sum < l iff sign bit is 1 - rv.visitLdcInsn(31); - rv.visitInsn(IUSHR); - return IntJitType.I1; - } - case LongJitType(int size) -> { - // Just add and extract the carry bit - rv.visitInsn(LADD); - rv.visitLdcInsn(size * Byte.SIZE); - rv.visitInsn(LSHR); - rv.visitInsn(L2I); - // TODO: This mask may not be necessary - rv.visitLdcInsn(1); - rv.visitInsn(IAND); - return IntJitType.I1; - } - case MpIntJitType t when t.size() == lType.size() -> { - generateMpIntCarry(gen, t, rv); - return IntJitType.I1; - } - case MpIntJitType t -> { - return TODO("MpInt of differing sizes"); - } - default -> throw new AssertionError(); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + if (type == LongJitType.I8) { + return em + .emit(Op::dup2_x2_22) // r l r + .emit(Op::ladd) + .emit(Op::dup2_x2_22) + .emit(Op::pop2__2) + .emit(Op::invokestatic, TR_LONG, "compareUnsigned", MDESC_LONG__COMPARE, + false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + // sum < l iff sign bit is 1 + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::iushr); } + // Just add and extract the carry bit + return em + .emit(Op::ladd) + .emit(Op::ldc__i, type.size() * Byte.SIZE) + .emit(Op::lshr) + .emit(Op::l2i) + // LATER: This mask may not be necessary.... + .emit(Op::ldc__i, 1) + .emit(Op::iand); + } + + @Override + public Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntCarryOp op, + MpIntJitType type, Scope scope) { + /** + * Similar strategy as for INT_ADD. In fact, we call its per-leg logic. + */ + var left = gen.genReadToOpnd(em, localThis, op.l(), type, ext(), scope); + var right = gen.genReadToOpnd(left.em(), localThis, op.r(), type, rExt(), scope); + em = right.em(); + var lLegs = left.opnd().type().castLegsLE(left.opnd()); + assert lLegs.size() >= 2; + var rLegs = right.opnd().type().castLegsLE(right.opnd()); + + int legCount = type.legsAlloc(); + + var first = IntAddOpGen.genMpIntLegAddGivesCarry(em, lLegs.getFirst(), rLegs.getFirst(), + false, scope); + var emCarry = first.em(); + for (int i = 1; i < legCount; i++) { + var result = IntAddOpGen.genMpIntLegAddTakesAndGivesCarry(emCarry, lLegs.get(i), + rLegs.get(i), false, scope); + emCarry = result.em(); + } + // carry on top of stack is really [carry][sum] in long + if (type.partialSize() == 0) { + // The last leg was full, so extract the carry bit + return emCarry + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lushr) + .emit(Op::l2i); + } + // The last leg was partial, so just get the bit one to the right + return emCarry + .emit(Op::ldc__i, type.partialSize() * Byte.SIZE) + .emit(Op::lushr) + .emit(Op::l2i) + // LATER: This mask probably not needed + .emit(Op::ldc__i, 1) + .emit(Op::iand); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCompareBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCompareBinOpGen.java new file mode 100644 index 0000000000..51e53c9f96 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCompareBinOpGen.java @@ -0,0 +1,222 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import java.util.function.Function; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.GenConsts; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitIntTestOp; + +/** + * An extension for integer comparison operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntCompareBinOpGen extends IntPredBinOpGen { + + /** + * Invert the boolean on top of the stack + * + * @param the tail of the incoming stack + * @param the incoming stack with the boolean on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + static > Emitter> not(Emitter em) { + return em + // Where this is used, the previous subroutines have already emitted &1 + //.emit(Op::ldc__i, 1) + //.emit(Op::iand) + .emit(Op::ldc__i, 1) + .emit(Op::ixor); + } + + /** + * Assuming a conditional jump bytecode was just emitted, emit bytecode to push 0 (false) onto + * the stack for the fall-through case, or 1 (true) onto the stack for the taken case. + * + * @param the incoming stack, i.e., that after emitting the conditional jump + * @param lblEmTrue the target label of the conditional jump just emitted, and the emitter typed + * with the incoming stack + * @return the emitter with the resulting stack, i.e., having pushed the boolean result + */ + default Emitter> genBool(LblEm lblEmTrue) { + var lblEmDone = lblEmTrue.em() + .emit(Op::ldc__i, 0) + .emit(Op::goto_); + return lblEmDone.em() + .emit(Lbl::placeDead, lblEmTrue.lbl()) + .emit(Op::ldc__i, 1) + .emit(Lbl::place, lblEmDone.lbl()); + } + + /** + * An implementation for (unsigned) int operands that invokes + * {@link Integer#compareUnsigned(int, int)} and then emits the given {@code if} jump. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param opIf a method reference, e.g., to {@link Op#ifge(Emitter, Lbl)} for the conditional + * jump + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default , N0 extends Ent> + Emitter> genIntViaUcmpThenIf(Emitter em, + Function>, LblEm> opIf) { + return em + .emit(Op::invokestatic, GenConsts.TR_INTEGER, "compareUnsigned", + GenConsts.MDESC_INTEGER__COMPARE, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(this::genIntViaIf, opIf); + } + + /** + * An implementation for (signed) int operands that simply emits the given {@code if_icmp} + * jump. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param opIfIcmp a method reference, e.g., to {@link Op#if_icmpge(Emitter, Lbl)} for the + * conditional jump + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default , N0 extends Ent> + Emitter> + genIntViaIfIcmp(Emitter em, Function, LblEm> opIfIcmp) { + return genBool(opIfIcmp.apply(em)); + } + + /** + * A utility that emits the given {@code if} along with the logic that pushes the correct + * result depending on whether or not the jump is taken. + * + * @param the tail of the incoming stack + * @param the incoming stack including the predicate, which is compared with 0 + * @param em the emitter typed with the incoming stack + * @param opIf a method reference, e.g., to {@link Op#ifge(Emitter, Lbl)} for the conditional + * jump + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default > Emitter> + genIntViaIf(Emitter em, Function, LblEm> opIf) { + return genBool(opIf.apply(em)); + } + + /** + * An implementation for (signed) long operands that emits {@link Op#lcmp(Emitter) lcmp} and + * then emits the given {@code if} jump. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param opIf a method reference, e.g., to {@link Op#ifge(Emitter, Lbl)} for the conditional + * jump + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default , N0 extends Ent> + Emitter> genLongViaLcmpThenIf(Emitter em, + Function>, LblEm> opIf) { + return em + .emit(Op::lcmp) + .emit(this::genIntViaIf, opIf); + } + + /** + * An implementation for (unsigned) long operands that invokes + * {@link Long#compareUnsigned(long, long)} and then emits the given {@code if} jump. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param opIf a method reference, e.g., to {@link Op#ifge(Emitter, Lbl)} for the conditional + * jump + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default , N0 extends Ent> + Emitter> genLongViaUcmpThenIf(Emitter em, + Function>, LblEm> opIf) { + return em + .emit(Op::invokestatic, GenConsts.TR_LONG, "compareUnsigned", + GenConsts.MDESC_LONG__COMPARE, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(this::genIntViaIf, opIf); + } + + /** + * {@inheritDoc} + *

    + * The strategy for multi-precision comparison can be applied to all comparisons: Start with the + * most-significant legs and compare for equality until we find the first not-equal + * pair. Then, apply {@link #opForInt(Emitter, IntJitType)} to determine the overall result. + * There is no need to load or compare any legs beyond the most-significant not-equal pair. If + * we reach the final (least-significant) pair, we need not check them for equality. Just + * delegate to {@link #opForInt(Emitter, IntJitType)}. + */ + @Override + default Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, MpIntJitType type, + Scope scope) { + /** + * Need to examine from most-significant to least-significant. Stop at the first pair of + * legs which are not equal, or on the least-significant leg. + */ + int legCount = type.legsAlloc(); + Local localLLeg = scope.decl(Types.T_INT, "lLeg"); + Local localRLeg = scope.decl(Types.T_INT, "rLeg"); + Lbl lblDone = Lbl.create(); + for (int i = legCount - 1; i > 0; i--) { // Yes, stop one before 0, so use >, not >= + em = em + .emit(gen::genReadLegToStack, localThis, op.l(), type, i, ext()) + .emit(Op::dup) + .emit(Op::istore, localLLeg) + .emit(gen::genReadLegToStack, localThis, op.r(), type, i, ext()) + .emit(Op::dup) + .emit(Op::istore, localRLeg) + .emit(IntNotEqualOpGen.GEN::opForInt, IntJitType.I4) + .emit(Op::ifne, lblDone); + } + // We've reached the last leg. Just load them onto the stack + var lblEmStaged = em + .emit(gen::genReadLegToStack, localThis, op.l(), type, 0, ext()) + .emit(gen::genReadLegToStack, localThis, op.r(), type, 0, ext()) + .emit(Op::goto_); + return lblEmStaged.em() + .emit(Lbl::placeDead, lblDone) + .emit(Op::iload, localLLeg) + .emit(Op::iload, localRLeg) + .emit(Lbl::place, lblEmStaged.lbl()) + .emit(this::opForInt, IntJitType.I4); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCountUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCountUnOpGen.java new file mode 100644 index 0000000000..246748f7e2 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntCountUnOpGen.java @@ -0,0 +1,95 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitUnOp; + +/** + * An extension for unary integer operators that count bits + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntCountUnOpGen extends UnOpGen { + + /** + * Emit the JVM bytecode to perform the operator with int operands on the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack with the input operand on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the input operand + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + > Emitter> opForInt(Emitter em, + IntJitType type); + + /** + * Emit the JVM bytecode to perform the operator with long operands on the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack with the input operand on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the input operand + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + > Emitter> opForLong(Emitter em, + LongJitType type); + + /** + * Emit the JVM bytecode to perform the operator with mp-int operands. + * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param op the p-code op + * @param type the p-code type of the input operand + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., with only the result pushed + */ + Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, MpIntJitType type, + Scope scope); + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + var emResult = switch (uType) { + case IntJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(this::opForInt, t); + case LongJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(this::opForLong, t); + case MpIntJitType t -> genRunMpInt(em, localThis, gen, op, t, scope); + default -> throw new AssertionError(); + }; + return new LiveOpResult( + gen.genWriteFromStack(emResult, localThis, op.out(), IntJitType.I4, ext(), scope)); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java index 8d8506dae1..1d15c26b03 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntDivOpGen.java @@ -15,27 +15,29 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntAddOp; import ghidra.pcode.emu.jit.op.JitIntDivOp; /** * The generator for a {@link JitIntAddOp int_add}. - * *

    - * This uses the binary operator generator and simply emits {@link #INVOKESTATIC} on - * {@link Integer#divideUnsigned(int, int)} or {@link Long#divideUnsigned(long, long)} depending on - * the type. + * This uses the binary operator generator and simply emits + * {@link Op#invokestatic(Emitter, TRef, String, ghidra.pcode.emu.jit.gen.util.Methods.MthDesc, boolean) + * invokestatic} on {@link Integer#divideUnsigned(int, int)} or * + * {@link Long#divideUnsigned(long, long)} depending on the type. + *

    + * For multi-precision division, this emits code to invoke + * {@link JitCompiledPassage#mpIntDivide(int[], int[], int[])}. */ -public enum IntDivOpGen implements IntBinOpGen { +public enum IntDivOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; @@ -44,31 +46,33 @@ public enum IntDivOpGen implements IntBinOpGen { return false; } - private void generateMpIntDiv(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntDivide", mv, 1, TakeOut.OUT); + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return em + .emit(Op::invokestatic, GenConsts.TR_INTEGER, "divideUnsigned", + GenConsts.MDESC_$INT_BINOP, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntDivOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return em + .emit(Op::invokestatic, GenConsts.TR_LONG, "divideUnsigned", + GenConsts.MDESC_$LONG_BINOP, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntDivOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); - switch (rType) { - case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "divideUnsigned", - MDESC_$INT_BINOP, false); - case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "divideUnsigned", - MDESC_$LONG_BINOP, false); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntDiv(gen, t, rv); - // FIXME: forceUniform shouldn't have to enforce the same size.... - case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); - default -> throw new AssertionError(); - } - // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations. - return rType; + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntDivOp op, + MpIntJitType type, Scope scope) { + return genMpDelegationToStaticMethod(em, gen, localThis, type, "mpIntDivide", op, 1, + TakeOut.OUT, scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntEqualOpGen.java index dc84f6a3ca..8be9e3405d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntEqualOpGen.java @@ -15,16 +15,23 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntEqualOp; /** * The generator for a {@link JitIntEqualOp int_equal}. * *

    - * This uses the integer comparison operator generator and simply emits {@link #IF_ICMPEQ} or - * {@link #IFEQ} depending on the type. + * To avoid jumps, this delegates to {@link Integer#compare(int, int)}, which is signed, and then + * inverts the result. */ -public enum IntEqualOpGen implements CompareIntBinOpGen { +public enum IntEqualOpGen implements IntCompareBinOpGen { /** The generator singleton */ GEN; @@ -34,12 +41,18 @@ public enum IntEqualOpGen implements CompareIntBinOpGen { } @Override - public int icmpOpcode() { - return IF_ICMPEQ; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return em + .emit(IntNotEqualOpGen.GEN::opForInt, type) + .emit(IntCompareBinOpGen::not); } @Override - public int ifOpcode() { - return IFEQ; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return em + .emit(IntNotEqualOpGen.GEN::opForLong, type) + .emit(IntCompareBinOpGen::not); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntExtUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntExtUnOpGen.java new file mode 100644 index 0000000000..e8d041c847 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntExtUnOpGen.java @@ -0,0 +1,74 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.pcode.emu.jit.op.JitUnOp; + +/** + * An extension for unary integer extension operators + *

    + * The strategy here is to do nothing more than invoke the readers and writers. Because those are + * responsible for converting between the types, with the appropriate signedness, the work of + * extension is already done. We need only to know whether or not the operators should be treated as + * signed or unsigned. Thankfully, that method is already required by a super interface. + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntExtUnOpGen extends UnOpGen { + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + JitType oType = gen.resolveType(op.out(), op.type()); + JitType minType = JitType.unifyLeast(uType, oType); + return new LiveOpResult(switch (minType) { + case IntJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(gen::genWriteFromStack, localThis, op.out(), t, ext(), scope); + case LongJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(gen::genWriteFromStack, localThis, op.out(), t, ext(), scope); + // Need floats for COPY + case FloatJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(gen::genWriteFromStack, localThis, op.out(), t, ext(), scope); + case DoubleJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(gen::genWriteFromStack, localThis, op.out(), t, ext(), scope); + case MpIntJitType t -> { + var result = em + .emit(gen::genReadToOpnd, localThis, op.u(), t, ext(), scope); + yield result.em() + .emit(gen::genWriteFromOpnd, localThis, op.out(), result.opnd(), ext(), + scope); + } + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java index a2618f65fc..88d24d730e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLeftOpGen.java @@ -25,7 +25,7 @@ import ghidra.pcode.emu.jit.op.JitIntLeftOp; * This uses the integer shift operator generator and simply invokes * {@link JitCompiledPassage#intLeft(int, int)}, etc. depending on the types. */ -public enum IntLeftOpGen implements ShiftIntBinOpGen { +public enum IntLeftOpGen implements IntShiftBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessEqualOpGen.java index 2f41d1f935..a4667788c8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessEqualOpGen.java @@ -15,15 +15,24 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntLessEqualOp; /** * The generator for a {@link JitIntLessEqualOp int_lessequal}. * *

    - * This uses the integer comparison operator generator and simply emits {@link #IFLE}. + * This uses the (unsigned) integer comparison operator generator and simply emits + * {@link Op#ifle(Emitter) ifle}. */ -public enum IntLessEqualOpGen implements CompareIntBinOpGen { +public enum IntLessEqualOpGen implements IntCompareBinOpGen { /** The generator singleton */ GEN; @@ -33,12 +42,14 @@ public enum IntLessEqualOpGen implements CompareIntBinOpGen { } @Override - public int icmpOpcode() { - throw new AssertionError(); + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return genIntViaUcmpThenIf(em, Op::ifle); } @Override - public int ifOpcode() { - return IFLE; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return genLongViaUcmpThenIf(em, Op::ifle); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessOpGen.java index 279d7b4fd1..0f3c110ac1 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntLessOpGen.java @@ -15,15 +15,24 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntLessOp; /** * The generator for a {@link JitIntLessOp int_less}. * *

    - * This uses the integer comparison operator generator and simply emits {@link #IFLT}. + * This uses the (unsigned) integer comparison operator generator and simply emits + * {@link Op#iflt(Emitter) iflt}. */ -public enum IntLessOpGen implements CompareIntBinOpGen { +public enum IntLessOpGen implements IntCompareBinOpGen { /** The generator singleton */ GEN; @@ -33,12 +42,14 @@ public enum IntLessOpGen implements CompareIntBinOpGen { } @Override - public int icmpOpcode() { - throw new AssertionError(); + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return genIntViaUcmpThenIf(em, Op::iflt); } @Override - public int ifOpcode() { - return IFLT; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return genLongViaUcmpThenIf(em, Op::iflt); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java index 82b9c65ec8..a1ae916025 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntMultOpGen.java @@ -15,24 +15,25 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntMultOp; /** * The generator for a {@link JitIntMultOp int_mult}. * *

    - * This uses the binary operator generator and simply emits {@link #IMUL} or {@link #LMUL} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#imul(Emitter) imul} or + * {@link Op#lmul(Emitter) lmul} depending on the type. + *

    + * For multi-precision multiplication, this emits code to invoke + * {@link JitCompiledPassage#mpIntMultiply(int[], int[], int[])} */ -public enum IntMultOpGen implements IntBinOpGen { +public enum IntMultOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; @@ -41,6 +42,18 @@ public enum IntMultOpGen implements IntBinOpGen { return false; } + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.imul(em); + } + + @Override + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lmul(em); + } + /** * Generate the mp-int multiply code. *

    @@ -55,7 +68,8 @@ public enum IntMultOpGen implements IntBinOpGen { * That ensures all the operand sizes match, which is often (at least conventionally) required * by the Sleigh compiler. However, if r1 and r2 are each only 64 bits, and I can keep track of * that fact, then I could perform about half as many multiplies and adds. It also be nice if I - * can look ahead and see that only 64 bits of temp is actually used. + * can look ahead and see that only 64 bits of temp is actually used. The same is true of + * {@link IntDivOpGen}, {@link IntRemOpGen}, {@link IntSDivOpGen}, and {@link IntSRemOpGen}. *

    * IDEA: It would be quite a change, but perhaps generating a temporary JVM-level DFG * would be useful for culling. The difficulty here is knowing whether or not a temp (unique) is @@ -63,32 +77,18 @@ public enum IntMultOpGen implements IntBinOpGen { * additional Sleigh compiler support. If used, I should not cull any computations, so that the * retired value is the full value. * + * @param em the code emitter with an empty stack + * @param localThis a handle to the owning compiled passage * @param gen the code generator + * @param op the p-code op * @param type the (uniform) type of the inputs and output operands - * @param mv the method visitor + * @param scope a scope for op-temporary variables */ - private void generateMpIntMult(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntMultiply", mv, 0, TakeOut.OUT); - } - @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntMultOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); - } - - @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntMultOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); - switch (rType) { - case IntJitType t -> rv.visitInsn(IMUL); - case LongJitType t -> rv.visitInsn(LMUL); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntMult(gen, t, rv); - case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); - default -> throw new AssertionError(); - } - // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations. - return rType; + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntMultOp op, + MpIntJitType type, Scope scope) { + return genMpDelegationToStaticMethod(em, gen, localThis, type, "mpIntMultiply", op, 0, + TakeOut.OUT, scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java index 690462b051..94e96c9955 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNegateOpGen.java @@ -15,24 +15,28 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; +import java.util.ArrayList; +import java.util.List; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.MpIntLocalOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntNegateOp; /** * The generator for a {@link JitIntNegateOp int_negate}. - * *

    * There is no bitwise "not" operator in the JVM. We borrow the pattern we see output by the Java * compiler for int negate(n) {return ~n;}. It XORs the input with a register of 1s. * This uses the unary operator generator and emits the equivalent code. */ -public enum IntNegateOpGen implements IntUnOpGen { +public enum IntNegateOpGen implements IntOpUnOpGen { /** The generator singleton */ GEN; @@ -41,38 +45,41 @@ public enum IntNegateOpGen implements IntUnOpGen { return false; // TODO: Is it? Test with 3-byte operands to figure it out. } - private void generateMpIntNegate(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - int legCount = type.legsAlloc(); - try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", legCount)) { - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ISTORE, temp.idx(i)); - // NOTE: More significant legs have higher indices (reverse of stack) - } - // Compute and push back in reverse order - int i = legCount; - for (SimpleJitType t : type.legTypes()) { - mv.visitVarInsn(ILOAD, temp.idx(--i)); - mv.visitLdcInsn(-1 >>> (Integer.SIZE - t.size() * Byte.SIZE)); - mv.visitInsn(IXOR); - } - } + @Override + public > Emitter> + opForInt(Emitter em) { + return em + .emit(Op::ldc__i, -1) + .emit(Op::ixor); } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntNegateOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case IntJitType t -> { - rv.visitLdcInsn(-1 >>> (Integer.SIZE - t.size() * Byte.SIZE)); - rv.visitInsn(IXOR); - } - case LongJitType t -> { - rv.visitLdcInsn(-1L >>> (Long.SIZE - t.size() * Byte.SIZE)); - rv.visitInsn(LXOR); - } - case MpIntJitType t -> generateMpIntNegate(gen, t, rv); - default -> throw new AssertionError(); + public > Emitter> + opForLong(Emitter em) { + return em + .emit(Op::ldc__l, -1L) + .emit(Op::lxor); + } + + @Override + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntNegateOp op, + MpIntJitType type, Scope scope) { + var opnd = gen.genReadToOpnd(em, localThis, op.u(), type, ext(), scope); + em = opnd.em(); + var legs = opnd.opnd().type().castLegsLE(opnd.opnd()); + + List> outLegs = new ArrayList<>(); + int legCount = type.legsAlloc(); + for (int i = 0; i < legCount; i++) { + var result = em + .emit(legs.get(i)::read) + .emit(this::opForInt) + .emit(legs.get(i)::write, scope); + em = result.em(); + outLegs.add(result.opnd()); } - return uType; + var out = MpIntLocalOpnd.of(type, "out", outLegs); + return gen.genWriteFromOpnd(em, localThis, op.out(), out, ext(), scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNotEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNotEqualOpGen.java index c5d809ec83..79c8fea9ac 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNotEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntNotEqualOpGen.java @@ -15,16 +15,26 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.GenConsts; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntNotEqualOp; /** * The generator for a {@link JitIntNotEqualOp int_notequal}. * *

    - * This uses the integer comparison operator generator and simply emits {@link #IF_ICMPNE} or - * {@link #IFNE} depending on the type. + * To avoid jumps, this delegates to {@link Integer#compare(int, int)}, which is signed, and then + * masks the result. */ -public enum IntNotEqualOpGen implements CompareIntBinOpGen { +public enum IntNotEqualOpGen implements IntCompareBinOpGen { /** The generator singleton */ GEN; @@ -34,12 +44,26 @@ public enum IntNotEqualOpGen implements CompareIntBinOpGen { } @Override - public int icmpOpcode() { - return IF_ICMPNE; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return em + .emit(Op::invokestatic, GenConsts.TR_INTEGER, "compare", + GenConsts.MDESC_INTEGER__COMPARE, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ldc__i, 1) + .emit(Op::iand); + // LATER: Can probably remove this mask 1. Tests check it, but still. } @Override - public int ifOpcode() { - return IFNE; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return em + .emit(Op::lcmp) + .emit(Op::ldc__i, 1) + .emit(Op::iand); + // LATER: Can probably remove this mask 1. Tests check it, but still. } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOpBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOpBinOpGen.java new file mode 100644 index 0000000000..15bb4fa081 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOpBinOpGen.java @@ -0,0 +1,102 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitBinOp; + +/** + * An extension for integer binary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntOpBinOpGen extends BinOpGen { + + /** + * Emit the JVM bytecode to perform the operator with intF operands on the stack. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the operands + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> Emitter> + opForInt(Emitter em, IntJitType type); + + /** + * Emit the JVM bytecode to perform the operator with long operands on the stack. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the operands + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> Emitter> + opForLong(Emitter em, LongJitType type); + + /** + * Emit the JVM bytecode to perform the operator with multi-precision operands. + * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param op the p-code op + * @param type the p-code type of the operands + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the empty stack + */ + Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, MpIntJitType type, + Scope scope); + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType lType = gen.resolveType(op.l(), op.lType()); + JitType rType = gen.resolveType(op.r(), op.rType()); + JitType uType = JitType.unify(lType, rType); + return new LiveOpResult(switch (uType) { + case IntJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, ext()) + .emit(gen::genReadToStack, localThis, op.r(), t, rExt()) + .emit(this::opForInt, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case LongJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, ext()) + .emit(gen::genReadToStack, localThis, op.r(), t, rExt()) + .emit(this::opForLong, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case MpIntJitType t -> genRunMpInt(em, localThis, gen, op, t, scope); + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOpUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOpUnOpGen.java new file mode 100644 index 0000000000..1f479fcbfa --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOpUnOpGen.java @@ -0,0 +1,91 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitUnOp; + +/** + * An extension for integer unary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntOpUnOpGen extends UnOpGen { + + /** + * Emit the JVM bytecode to perform the operator with int operands on the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack with the input operand on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + > Emitter> opForInt(Emitter em); + + /** + * Emit the JVM bytecode to perform the operator with long operands on the stack. + * + * @param the tail of the incoming stack + * @param the incoming stack with the input operand on top + * @param em the emitter typed with the incoming stack + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + > Emitter> opForLong(Emitter em); + + /** + * Emit the JVM bytecode to perform the operator with multi-precision operands. + * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param op the p-code op + * @param type the p-code type of the operands + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the empty stack + */ + Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, MpIntJitType type, + Scope scope); + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + return new LiveOpResult(switch (uType) { + case IntJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(this::opForInt) + .emit(gen::genWriteFromStack, localThis, op.out(), t, ext(), scope); + case LongJitType t -> em + .emit(gen::genReadToStack, localThis, op.u(), t, ext()) + .emit(this::opForLong) + .emit(gen::genWriteFromStack, localThis, op.out(), t, ext(), scope); + case MpIntJitType t -> genRunMpInt(em, localThis, gen, op, t, scope); + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOrOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOrOpGen.java index 2a6010c6b9..70ec18c839 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOrOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntOrOpGen.java @@ -15,26 +15,36 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntOrOp; /** * The generator for a {@link JitIntOrOp int_or}. * *

    - * This uses the bitwise binary operator and emits {@link #IOR} or {@link #LOR} depending on the - * type. + * This uses the bitwise binary operator and emits {@link Op#ior(Emitter) ior} or + * {@link Op#lor(Emitter) lor} depending on the type. */ -public enum IntOrOpGen implements BitwiseBinOpGen { +public enum IntOrOpGen implements IntBitwiseBinOpGen { /** The generator singleton */ GEN; @Override - public int intOpcode() { - return IOR; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.ior(em); } @Override - public int longOpcode() { - return LOR; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lor(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java new file mode 100644 index 0000000000..ab3d792941 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntPredBinOpGen.java @@ -0,0 +1,194 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import static ghidra.pcode.emu.jit.gen.GenConsts.*; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitBinOp; + +/** + * An extension for integer operators whose outputs are boolean + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntPredBinOpGen extends BinOpGen { + + /** + * Emit the JVM bytecode to perform the operator with integer operands on the stack. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the operands + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> Emitter> + opForInt(Emitter em, IntJitType type); + + /** + * An implementation for integer operands that delegates to a method on + * {@link JitCompiledPassage} + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the operands + * @param methodName the name of the method + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default , N0 extends Ent> + Emitter> + delegateIntFlagbit(Emitter em, IntJitType type, String methodName) { + return em + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName, + MDESC_JIT_COMPILED_PASSAGE__$FLAGBIT_INT_RAW, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ldc__i, type.size() * Byte.SIZE - 1) + .emit(Op::ishr) + // LATER: This mask may not be needed + .emit(Op::ldc__i, 1) + .emit(Op::iand); + } + + /** + * Emit the JVM bytecode to perform the operator with long operands on the stack. + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the operands + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + , N0 extends Ent> Emitter> + opForLong(Emitter em, LongJitType type); + + /** + * An implementation for long operands that delegates to a method on {@link JitCompiledPassage} + * + * @param the tail of the incoming stack + * @param the tail of the incoming stack including the right operand + * @param the incoming stack with the right and left operands on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the operands + * @param methodName the name of the method + * @return the emitter typed with the resulting stack, i.e., the tail with the result pushed + */ + default , N0 extends Ent> + Emitter> + delegateLongFlagbit(Emitter em, LongJitType type, String methodName) { + return em + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName, + MDESC_JIT_COMPILED_PASSAGE__$FLAGBIT_LONG_RAW, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::ldc__i, type.size() * Byte.SIZE - 1) + .emit(Op::lshr) + .emit(Op::l2i) + // LATER: This mask may not be needed + .emit(Op::ldc__i, 1) + .emit(Op::iand); + } + + /** + * Emit the JVM bytecode to perform the operator with multi-precision operands. + * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param op the p-code op + * @param type the p-code type of the operands + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the resulting stack, i.e., containing only the result + */ + Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, T op, MpIntJitType type, + Scope scope); + + /** + * An implementation for multi-precision integer operands that delegates to a method on + * {@link JitCompiledPassage} + * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param op the p-code op + * @param type the p-code type of the operands + * @param scope a scope for generating temporary local storage + * @param methodName the name of the method + * @return the emitter typed with the resulting stack, i.e., containing only the result + */ + default Emitter> delegateMpIntFlagbit( + Emitter em, Local> localThis, JitCodeGenerator gen, T op, + MpIntJitType type, Scope scope, String methodName) { + return em + .emit(gen::genReadToArray, localThis, op.l(), type, ext(), scope, 0) + .emit(gen::genReadToArray, localThis, op.r(), type, rExt(), scope, 0) + .emit(Op::ldc__i, type.partialSize() * Byte.SIZE - 1) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName, + MDESC_JIT_COMPILED_PASSAGE__$FLAGBIT_MP_INT, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); + } + + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType lType = gen.resolveType(op.l(), op.lType()); + JitType rType = gen.resolveType(op.r(), op.rType()); + assert rType == lType; + return new LiveOpResult(switch (lType) { + case IntJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, ext()) + .emit(gen::genReadToStack, localThis, op.r(), t, rExt()) + .emit(this::opForInt, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case LongJitType t -> em + .emit(gen::genReadToStack, localThis, op.l(), t, Ext.ZERO) + .emit(gen::genReadToStack, localThis, op.r(), t, Ext.ZERO) + .emit(this::opForLong, t) + .emit(gen::genWriteFromStack, localThis, op.out(), IntJitType.I4, Ext.ZERO, + scope); + case MpIntJitType t -> em + .emit(this::genRunMpInt, localThis, gen, op, t, scope) + .emit(gen::genWriteFromStack, localThis, op.out(), IntJitType.I4, Ext.ZERO, + scope); + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java index bb85065519..2a381390d5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRemOpGen.java @@ -15,26 +15,30 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.GenConsts; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntRemOp; /** * The generator for a {@link JitIntRemOp int_rem}. * *

    - * This uses the binary operator generator and simply emits {@link #INVOKESTATIC} on - * {@link Integer#remainderUnsigned(int, int)} or {@link Long#remainderUnsigned(long, long)} + * This uses the binary operator generator and simply emits + * {@link Op#invokestatic(Emitter, TRef, String, ghidra.pcode.emu.jit.gen.util.Methods.MthDesc, boolean)} + * on {@link Integer#remainderUnsigned(int, int)} or {@link Long#remainderUnsigned(long, long)} * depending on the type. + *

    + * For multi-precision remainder, this emits code to invoke + * {@link JitCompiledPassage#mpIntDivide(int[], int[], int[])}, but selects what remains in the left + * operand as the result. */ -public enum IntRemOpGen implements IntBinOpGen { +public enum IntRemOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; @@ -43,31 +47,33 @@ public enum IntRemOpGen implements IntBinOpGen { return false; } - private void generateMpIntRem(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntDivide", mv, 1, TakeOut.LEFT); + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return em + .emit(Op::invokestatic, GenConsts.TR_INTEGER, "remainderUnsigned", + GenConsts.MDESC_$INT_BINOP, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntRemOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return em + .emit(Op::invokestatic, GenConsts.TR_LONG, "remainderUnsigned", + GenConsts.MDESC_$LONG_BINOP, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntRemOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); - switch (rType) { - case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "remainderUnsigned", - MDESC_$INT_BINOP, false); - case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "remainderUnsigned", - MDESC_$LONG_BINOP, false); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntRem(gen, t, rv); - // FIXME: forceUniform shouldn't have to enforce the same size.... - case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); - default -> throw new AssertionError(); - } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return rType; + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntRemOp op, + MpIntJitType type, Scope scope) { + return genMpDelegationToStaticMethod(em, gen, localThis, type, "mpIntDivide", op, 1, + TakeOut.LEFT, scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java index 8905fa84ef..8a4ebb8141 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntRightOpGen.java @@ -25,7 +25,7 @@ import ghidra.pcode.emu.jit.op.JitIntRightOp; * This uses the integer shift operator generator and simply invokes * {@link JitCompiledPassage#intRight(int, int)}, etc. depending on the types. */ -public enum IntRightOpGen implements ShiftIntBinOpGen { +public enum IntRightOpGen implements IntShiftBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java index 408d5d1c40..19280070d8 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSBorrowOpGen.java @@ -15,29 +15,28 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntSBorrowOp; /** * The generator for a {@link JitIntSBorrowOp int_sborrow}. - * *

    - * This uses the binary operator generator and emits {@link #INVOKESTATIC} on - * {@link JitCompiledPassage#sBorrowIntRaw(int, int)} or + * This uses the binary operator generator and emits + * {@link Op#invokestatic(Emitter, TRef, String, ghidra.pcode.emu.jit.gen.util.Methods.MthDesc, boolean) + * invokestatic} on {@link JitCompiledPassage#sBorrowIntRaw(int, int)} or * {@link JitCompiledPassage#sBorrowLongRaw(long, long)} depending on the type. We must then emit a * shift and mask to extract the correct bit. + *

    + * For multi-precision signed borrow, we delegate to + * {@link JitCompiledPassage#sBorrowMpInt(int[], int[], int)}, which requires no follow-on bit + * extraction. */ -public enum IntSBorrowOpGen implements IntBinOpGen { +public enum IntSBorrowOpGen implements IntPredBinOpGen { /** The generator singleton */ GEN; @@ -47,41 +46,21 @@ public enum IntSBorrowOpGen implements IntBinOpGen { } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSBorrowOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, Ext.SIGN, rv); + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return delegateIntFlagbit(em, type, "sBorrowIntRaw"); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSBorrowOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, Ext.SIGN, rv); - switch (rType) { - case IntJitType(int size) -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sBorrowIntRaw", - MDESC_JIT_COMPILED_PASSAGE__S_CARRY_INT_RAW, true); - rv.visitLdcInsn(size * Byte.SIZE - 1); - rv.visitInsn(ISHR); - // TODO: This mask may not be necessary - rv.visitLdcInsn(1); - rv.visitInsn(IAND); - return IntJitType.I1; - } - case LongJitType(int size) -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sBorrowLongRaw", - MDESC_JIT_COMPILED_PASSAGE__S_CARRY_LONG_RAW, true); - rv.visitLdcInsn(size * Byte.SIZE - 1); - rv.visitInsn(LSHR); - rv.visitInsn(L2I); - // TODO: This mask may not be necessary - rv.visitLdcInsn(1); - rv.visitInsn(IAND); - return IntJitType.I1; - } - case MpIntJitType t -> { - return IntSCarryOpGen.generateMpIntSCarry(gen, t, "sBorrowMpInt", rv); - } - default -> throw new AssertionError(); - } + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return delegateLongFlagbit(em, type, "sBorrowLongRaw"); + } + + @Override + public Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntSBorrowOp op, + MpIntJitType type, Scope scope) { + return delegateMpIntFlagbit(em, localThis, gen, op, type, scope, "sBorrowMpInt"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java index 99dd1b166f..a047e49195 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSCarryOpGen.java @@ -15,30 +15,28 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntSCarryOp; /** * The generator for a {@link JitIntSCarryOp int_scarry}. - * *

    - * This uses the binary operator generator and emits {@link #INVOKESTATIC} on - * {@link JitCompiledPassage#sCarryIntRaw(int, int)} or + * This uses the binary operator generator and emits + * {@link Op#invokestatic(Emitter, TRef, String, ghidra.pcode.emu.jit.gen.util.Methods.MthDesc, boolean) + * invokestatic} on {@link JitCompiledPassage#sCarryIntRaw(int, int)} or * {@link JitCompiledPassage#sCarryLongRaw(long, long)} depending on the type. We must then emit a * shift and mask to extract the correct bit. + *

    + * For multi-precision signed borrow, we delegate to + * {@link JitCompiledPassage#sCarryMpInt(int[], int[], int)}, which requires no follow-on bit + * extraction. */ -public enum IntSCarryOpGen implements IntBinOpGen { +public enum IntSCarryOpGen implements IntPredBinOpGen { /** The generator singleton */ GEN; @@ -47,56 +45,22 @@ public enum IntSCarryOpGen implements IntBinOpGen { return true; } - static IntJitType generateMpIntSCarry(JitCodeGenerator gen, MpIntJitType type, - String methodName, MethodVisitor mv) { - JitAllocationModel am = gen.getAllocationModel(); - int legCount = type.legsAlloc(); - try ( - JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", legCount); - JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", legCount)) { - OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); - OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); - - OpGen.generateMpLegsIntoArray(tmpL, legCount, legCount, mv); - OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); - mv.visitLdcInsn((type.size() % Integer.BYTES) * Byte.SIZE - 1); - - mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName, - MDESC_JIT_COMPILED_PASSAGE__S_CARRY_MP_INT, true); - } - return IntJitType.I4; + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return delegateIntFlagbit(em, type, "sCarryIntRaw"); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSCarryOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); - switch (rType) { - case IntJitType(int size) -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sCarryIntRaw", - MDESC_JIT_COMPILED_PASSAGE__S_CARRY_INT_RAW, true); - rv.visitLdcInsn(size * Byte.SIZE - 1); - rv.visitInsn(ISHR); - // TODO: This mask may not be necessary - rv.visitLdcInsn(1); - rv.visitInsn(IAND); - return IntJitType.I1; - } - case LongJitType(int size) -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sCarryLongRaw", - MDESC_JIT_COMPILED_PASSAGE__S_CARRY_LONG_RAW, true); - rv.visitLdcInsn(size * Byte.SIZE - 1); - rv.visitInsn(LSHR); - rv.visitInsn(L2I); - // TODO: This mask may not be necessary - rv.visitLdcInsn(1); - rv.visitInsn(IAND); - return IntJitType.I1; - } - case MpIntJitType t -> { - return generateMpIntSCarry(gen, t, "sCarryMpInt", rv); - } - default -> throw new AssertionError(); - } + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return delegateLongFlagbit(em, type, "sCarryLongRaw"); + } + + @Override + public Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntSCarryOp op, + MpIntJitType type, Scope scope) { + return delegateMpIntFlagbit(em, localThis, gen, op, type, scope, "sCarryMpInt"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java index c720f98d4a..d982e1b5f2 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSDivOpGen.java @@ -15,23 +15,25 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntSDivOp; /** * The generator for a {@link JitIntSDivOp int_sdiv}. * *

    - * This uses the binary operator generator and simply emits {@link #IDIV} or {@link #LDIV} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#idiv(Emitter) idiv} or + * {@link Op#ldiv(Emitter) ldiv} depending on the type. + *

    + * For multi-precision division, this emits code to invoke + * {@link JitCompiledPassage#mpIntSignedDivide(int[], int[], int[])}. */ -public enum IntSDivOpGen implements IntBinOpGen { +public enum IntSDivOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; @@ -40,30 +42,23 @@ public enum IntSDivOpGen implements IntBinOpGen { return true; } - private void generateMpIntSDiv(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntSignedDivide", mv, 1, - TakeOut.OUT); + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.idiv(em); } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSDivOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.ldiv(em); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSDivOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); - switch (rType) { - case IntJitType t -> rv.visitInsn(IDIV); - case LongJitType t -> rv.visitInsn(LDIV); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntSDiv(gen, t, rv); - // FIXME: forceUniform shouldn't have to enforce the same size.... - case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); - default -> throw new AssertionError(); - } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return rType; + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntSDivOp op, + MpIntJitType type, Scope scope) { + return genMpDelegationToStaticMethod(em, gen, localThis, type, "mpIntSignedDivide", op, 1, + TakeOut.OUT, scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java index ee4437291a..50f81b4dc0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSExtOpGen.java @@ -15,26 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; -import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.op.JitIntSExtOp; /** * The generator for a {@link JitIntSExtOp int_sext}. - * *

    - * We implement this using a left then signed-right shift. This uses the unary operator generator - * and emits {@link #ISHL} and {@link #ISHR} or {@link #LSHL} and {@link #LSHR}, depending on type. - * Additional type conversions may be emitted first. As a special case, sign extension from - * {@link IntJitType#I4 int4} to {@link LongJitType#I8 int8} is implemented with by emitting only - * {@link #I2L}. + * This works exactly the same as {@link IntZExtOpGen} except that the conversions use sign + * extension. */ -public enum IntSExtOpGen implements IntUnOpGen { +public enum IntSExtOpGen implements IntExtUnOpGen { /** The generator singleton */ GEN; @@ -42,10 +31,4 @@ public enum IntSExtOpGen implements IntUnOpGen { public boolean isSigned() { return true; } - - @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntSExtOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - return uType; - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessEqualOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessEqualOpGen.java index d2873ac9b0..441a950322 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessEqualOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessEqualOpGen.java @@ -15,16 +15,24 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntSLessEqualOp; /** * The generator for a {@link JitIntSLessEqualOp int_slessequal}. * *

    - * This uses the integer comparison operator generator and simply emits {@link #IF_ICMPLE} or - * {@link #IFLE} depending on the type. + * This uses the (signed) integer comparison operator generator and simply emits + * {@link Op#if_icmple(Emitter) if_icmple} or {@link Op#ifle(Emitter) ifle} depending on the type. */ -public enum IntSLessEqualOpGen implements CompareIntBinOpGen { +public enum IntSLessEqualOpGen implements IntCompareBinOpGen { /** The generator singleton */ GEN; @@ -34,12 +42,14 @@ public enum IntSLessEqualOpGen implements CompareIntBinOpGen } @Override - public int icmpOpcode() { - return IF_ICMPLE; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return genIntViaIfIcmp(em, Op::if_icmple); } @Override - public int ifOpcode() { - return IFLE; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return genLongViaLcmpThenIf(em, Op::ifle); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessOpGen.java index 49bdc9002d..b765cdaa5d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSLessOpGen.java @@ -15,16 +15,24 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntSLessOp; /** * The generator for a {@link JitIntSLessOp int_sless}. * *

    - * This uses the integer comparison operator generator and simply emits {@link #IF_ICMPLT} or - * {@link #IFLT} depending on the type. + * This uses the (signed) integer comparison operator generator and simply emits + * {@link Op#if_icmplt(Emitter) if_icmplt} or {@link Op#iflt(Emitter) iflt} depending on the type. */ -public enum IntSLessOpGen implements CompareIntBinOpGen { +public enum IntSLessOpGen implements IntCompareBinOpGen { /** The generator singleton */ GEN; @@ -34,12 +42,14 @@ public enum IntSLessOpGen implements CompareIntBinOpGen { } @Override - public int icmpOpcode() { - return IF_ICMPLT; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return genIntViaIfIcmp(em, Op::if_icmplt); } @Override - public int ifOpcode() { - return IFLT; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return genLongViaLcmpThenIf(em, Op::iflt); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java index ac9a6daa8c..d4e3e884ed 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRemOpGen.java @@ -15,23 +15,26 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntSRemOp; /** * The generator for a {@link JitIntSRemOp int_srem}. * *

    - * This uses the binary operator generator and simply emits {@link #IREM} or {@link #LREM} depending - * on the type. + * This uses the binary operator generator and simply emits {@link Op#irem(Emitter) irem} or + * {@link Op#lrem(Emitter) lrem} depending on the type. + *

    + * For multi-precision remainder, this emits code to invoke + * {@link JitCompiledPassage#mpIntSignedDivide(int[], int[], int[])}, but selects what remains in + * the left operand as the result. */ -public enum IntSRemOpGen implements IntBinOpGen { +public enum IntSRemOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; @@ -40,30 +43,23 @@ public enum IntSRemOpGen implements IntBinOpGen { return true; } - private void generateMpIntSRem(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntSignedDivide", mv, 1, - TakeOut.LEFT); + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.irem(em); } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSRemOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lrem(em); } @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSRemOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv); - switch (rType) { - case IntJitType t -> rv.visitInsn(IREM); - case LongJitType t -> rv.visitInsn(LREM); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntSRem(gen, t, rv); - // FIXME: forceUniform shouldn't have to enforce the same size.... - case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); - default -> throw new AssertionError(); - } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return rType; + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntSRemOp op, + MpIntJitType type, Scope scope) { + return genMpDelegationToStaticMethod(em, gen, localThis, type, "mpIntSignedDivide", op, 1, + TakeOut.LEFT, scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java index c8c10dc824..77f8adb7dc 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSRightOpGen.java @@ -25,7 +25,7 @@ import ghidra.pcode.emu.jit.op.JitIntSRightOp; * This uses the integer shift operator generator and simply invokes * {@link JitCompiledPassage#intSRight(int, int)}, etc. depending on the types. */ -public enum IntSRightOpGen implements ShiftIntBinOpGen { +public enum IntSRightOpGen implements IntShiftBinOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntShiftBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntShiftBinOpGen.java new file mode 100644 index 0000000000..0738578033 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntShiftBinOpGen.java @@ -0,0 +1,278 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.op; + +import static ghidra.pcode.emu.jit.gen.GenConsts.*; + +import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; +import ghidra.pcode.emu.jit.op.JitIntBinOp; +import ghidra.pcode.emu.jit.var.JitVal; +import ghidra.pcode.emu.jit.var.JitVar; + +/** + * An extension for integer shift operators + * + *

    + * This is just going to invoke one of the {@link JitCompiledPassage#intLeft(int, int)}, + * {@link JitCompiledPassage#intRight(int, int)}, {@link JitCompiledPassage#intSRight(int, int)}, or + * one of their overloaded methods, depending on the operand types. + * + * @param the class of p-code op node in the use-def graph + */ +public interface IntShiftBinOpGen extends BinOpGen { + /** + * {@inheritDoc} + *

    + * The shift amount is always treated unsigned. + */ + @Override + default Ext rExt() { + return Ext.ZERO; + } + + /** + * The name of the static method in {@link JitCompiledPassage} to invoke + * + * @return the name + */ + String methodName(); + + /** + * The implementation when both operands are simple primitives + * + * @param the type of the generated passage + * @param the JVM type of the left operand + * @param the p-code type of the left operand + * @param the JVM type of the right operand + * @param the p-code type of the right operand + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param outVar the output operand + * @param outType the p-code type of the output value + * @param lVal the left operand + * @param lType the p-code type of the left operand + * @param rVal the right operand + * @param rType the p-code type of the right operand + * @param scope a scope for generating temporary local storage + * @param mdesc the descriptor of the (overloaded) method + * @return the emitter typed with the incoming stack + */ + default , + LJT extends SimpleJitType, RT extends BPrim, RJT extends SimpleJitType, + N extends Next> Emitter genShiftPrimPrim(Emitter em, Local> localThis, + JitCodeGenerator gen, JitVar outVar, LJT outType, JitVal lVal, LJT lType, + JitVal rVal, RJT rType, Scope scope, MthDesc, RT>> mdesc) { + return em + .emit(gen::genReadToStack, localThis, lVal, lType, ext()) + .emit(gen::genReadToStack, localThis, rVal, rType, rExt()) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName(), mdesc, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(gen::genWriteFromStack, localThis, outVar, outType, ext(), scope); + } + + /** + * The implementation when the left operand is an mp-int and the right is a primitive + * + * @param the type of the generated passage + * @param the JVM type of the right operand + * @param the p-code type of the right operand + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param outVar the output operand + * @param outType the p-code type of the output value + * @param lVal the left operand + * @param lType the p-code type of the left operand + * @param rVal the right operand + * @param rType the p-code type of the right operand + * @param scope a scope for generating temporary local storage + * @param mdesc the descriptor of the (overloaded) method + * @return the emitter typed with the incoming stack + */ + default , + RJT extends SimpleJitType, N extends Next> Emitter genShiftMpPrim(Emitter em, + Local> localThis, JitCodeGenerator gen, JitVar outVar, + MpIntJitType outType, JitVal lVal, MpIntJitType lType, JitVal rVal, RJT rType, + Scope scope, + MthDesc>, TInt>, TRef>, RT>> mdesc) { + /** + * FIXME: We could avoid this array allocation by shifting in place, but then we'd still + * need to communicate the actual out size. Things are easy if the out size is smaller than + * the left-in size, but not so easy if larger. Or, maybe over-provision if larger.... + */ + return em + .emit(Op::ldc__i, outType.legsAlloc()) + .emit(Op::newarray, Types.T_INT) + .emit(Op::dup) + .emit(Op::ldc__i, outType.size()) + .emit(gen::genReadToArray, localThis, lVal, lType, ext(), scope, 0) + .emit(gen::genReadToStack, localThis, rVal, rType, rExt()) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName(), mdesc, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(gen::genWriteFromArray, localThis, outVar, outType, ext(), scope); + } + + /** + * The implementation when the left operand is a primitive and the right operand is an mp-int + * + * @param the type of the generated passage + * @param the JVM type of the left operand + * @param the p-code type of the left operand + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param outVar the output operand + * @param outType the p-code type of the output value + * @param lVal the left operand + * @param lType the p-code type of the left operand + * @param rVal the right operand + * @param rType the p-code type of the right operand + * @param scope a scope for generating temporary local storage + * @param mdesc the descriptor of the (overloaded) method + * @return the emitter typed with the incoming stack + */ + default , + LJT extends SimpleJitType, N extends Next> Emitter genShiftPrimMp(Emitter em, + Local> localThis, JitCodeGenerator gen, JitVar outVar, LJT outType, + JitVal lVal, LJT lType, JitVal rVal, MpIntJitType rType, Scope scope, + MthDesc, TRef>> mdesc) { + return em + .emit(gen::genReadToStack, localThis, lVal, lType, ext()) + /** + * TODO: Generate code to detect shifts > lType size, then just invoke the signature + * with int shift amount? + */ + .emit(gen::genReadToArray, localThis, rVal, rType, rExt(), scope, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName(), mdesc, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(gen::genWriteFromStack, localThis, outVar, outType, ext(), scope); + } + + /** + * The implementation when both operands are mp-ints + * + * @param the type of the generated passage + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localThis a handle to the local holding the {@code this} reference + * @param gen the code generator + * @param outVar the output operand + * @param outType the p-code type of the output value + * @param lVal the left operand + * @param lType the p-code type of the left operand + * @param rVal the right operand + * @param rType the p-code type of the right operand + * @param scope a scope for generating temporary local storage + * @param mdesc the descriptor of the (overloaded) method + * @return the emitter typed with the incoming stack + */ + default Emitter genShiftMpMp(Emitter em, + Local> localThis, JitCodeGenerator gen, JitVar outVar, + MpIntJitType outType, JitVal lVal, MpIntJitType lType, JitVal rVal, MpIntJitType rType, + Scope scope, MthDesc>, TInt>, TRef>, TRef>> mdesc) { + /** + * FIXME: We could avoid this array allocation by shifting in place, but then we'd still + * need to communicate the actual out size. Things are easy if the out size is smaller than + * the left-in size, but not so easy if larger. Or, maybe over-provision if larger.... + */ + return em + .emit(Op::ldc__i, outType.legsAlloc()) + .emit(Op::newarray, Types.T_INT) + .emit(Op::dup) + .emit(Op::ldc__i, outType.size()) + .emit(gen::genReadToArray, localThis, lVal, lType, ext(), scope, 0) + /** + * TODO: Generate code to detect shifts > lType size, then just invoke the signature + * with int shift amount? + */ + .emit(gen::genReadToArray, localThis, rVal, rType, rExt(), scope, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, methodName(), mdesc, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid) + .emit(gen::genWriteFromArray, localThis, outVar, outType, ext(), scope); + } + + /** + * {@inheritDoc} + * + *

    + * This reduces the implementation to just the name of the method to invoke. This will select + * the JVM signature of the method based on the p-code operand types. + */ + @Override + default OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope) { + JitType lType = gen.resolveType(op.l(), op.lType()); + JitType rType = gen.resolveType(op.r(), op.rType()); + return new LiveOpResult(switch (lType) { + case IntJitType lt -> switch (rType) { + case IntJitType rt -> genShiftPrimPrim(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_II); + case LongJitType rt -> genShiftPrimPrim(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_IJ); + case MpIntJitType rt -> genShiftPrimMp(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_IA); + default -> throw new AssertionError(); + }; + case LongJitType lt -> switch (rType) { + case IntJitType rt -> genShiftPrimPrim(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_JI); + case LongJitType rt -> genShiftPrimPrim(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_JJ); + case MpIntJitType rt -> genShiftPrimMp(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_JA); + default -> throw new AssertionError(); + }; + case MpIntJitType lt -> switch (rType) { + case IntJitType rt -> genShiftMpPrim(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_AI); + case LongJitType rt -> genShiftMpPrim(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_AJ); + case MpIntJitType rt -> genShiftMpMp(em, localThis, gen, op.out(), lt, op.l(), + lt, op.r(), rt, scope, MDESC_$SHIFT_AA); + default -> throw new AssertionError(); + }; + default -> throw new AssertionError(); + }); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java index a19730af1b..a2545fff32 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntSubOpGen.java @@ -15,30 +15,29 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; +import java.util.ArrayList; +import java.util.List; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.MpIntLocalOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd.SimpleOpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitIntSubOp; /** * The generator for a {@link JitIntSubOp int_sub}. - * *

    - * This uses the binary operator generator and simply emits {@link #ISUB} or {@link #LSUB} depending - * on the type. - * + * This uses the binary operator generator and simply emits {@link Op#isub(Emitter) isub} or + * {@link Op#lsub(Emitter) lsub} depending on the type. *

    - * NOTE: The multi-precision integer parts of this are a work in progress. + * This uses the same multi-precision integer strategy and pattern as {@link IntAddOpGen}. */ -public enum IntSubOpGen implements IntBinOpGen { +public enum IntSubOpGen implements IntOpBinOpGen { /** The generator singleton */ GEN; @@ -47,83 +46,192 @@ public enum IntSubOpGen implements IntBinOpGen { return false; } - private void generateMpIntLegSub(JitCodeGenerator gen, int idx, boolean takesBorrow, - boolean givesBorrow, MethodVisitor mv) { - if (takesBorrow) { - // [...,llegN:INT,olegN+1:LONG] - mv.visitLdcInsn(32); - mv.visitInsn(LSHR); // signed so that ADD effects subtraction - // [...,lleg1...,borrowinN:LONG] - mv.visitInsn(DUP2_X1); - mv.visitInsn(POP2); - // [...,borrowinN:LONG,llegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); - // [...,borrowinN:LONG,llegN:LONG] - mv.visitInsn(LADD); // Yes, add, because borrow is 0 or -1 - // [...,diffpartN:LONG] - } - else { - // [...,legN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); - // [...,diffpartN:LONG] (legN + 0) - } - mv.visitVarInsn(ILOAD, idx); - // [...,diffpartN:LONG,rlegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); - // [...,diffpartN:LONG,rlegN:LONG] - mv.visitInsn(LSUB); - // [...,olegN:LONG] - if (givesBorrow) { - mv.visitInsn(DUP2); - } - // [...,(olegN:LONG),olegN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); - // [...,(olegN:LONG),olegN:INT] - /** NB. The store will perform the masking */ - mv.visitVarInsn(ISTORE, idx); - // [...,(olegN:LONG)] - } - - private void generateMpIntSub(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - /** - * The strategy is to allocate a temp local for each leg of the result. First, we'll pop the - * right operand into the temp. Then, as we work with each leg of the left operand, we'll - * execute the algorithm. Convert both right and left legs to a long and add them (along - * with a possible borrow in). Store the result back into the temp locals. Shift the leg - * right 32 to get the carry out, then continue to the next leg up. The final carry out can - * be dropped (overflow). The result legs are then pushed back to the stack. - */ - // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) - int legCount = type.legsAlloc(); // include partial - try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) { - OpGen.generateMpLegsIntoTemp(result, legCount, mv); - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - boolean takesCarry = i != 0; // not first - generateMpIntLegSub(gen, result.idx(i), takesCarry, !isLast, mv); - } - OpGen.generateMpLegsFromTemp(result, legCount, mv); - } + @Override + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.isub(em); } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSubOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lsub(em); } + /** + * Emit bytecode to load a leg of the left operand and combine it with the borrow in + *

    + * The borrow in is a long where the upper 32 bits all have the value 0 or 1. The lower 32 bits, + * which are actually the result of the previous legs' subtraction, are ignored. We use a long + * because it can hold the full result of subtraction, where the borrow out winds up in the + * upper 32 bits of the long. This routine emits bytecode to shift the previous difference out, + * so that the borrow bit now fills the full 64 bits, i.e., the long how has the value 0 or -1. + * It then loads and adds the left leg into that long. + * + * @param em the emitter typed with a stack of one long, the borrow in + * @param left the operand containing the leg from the left multi-precision operand + * @return the emitter typed with a stack of one long, the summed left and borrow in + */ + static Emitter> prepLeftAndBorrow(Emitter> em, + SimpleOpnd left) { + return em + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::lshr) // Signed so that ladd below effects subtraction + .emit(left::read) + .emit(Op::i2l) + .emit(Op::ladd); + } + + /** + * Emit bytecode to load a leg of the right operand and subtract it from the summed left and + * borrow in + *

    + * This completes the subtraction of the left and right legs along with the borrow in. The long + * on the stack is now the result, with the borrow out in the upper 32 bits. + * + * @param em the emitter typed with a stack of one long, the summed left and borrow in + * @param right the operand containing the leg from the right multi-precision operand + * @return the emitter typed with a stack of one long, the result and borrow out + */ + static Emitter> subRight(Emitter> em, + SimpleOpnd right) { + return em + .emit(right::read) + .emit(Op::i2l) + .emit(Op::lsub); + } + + /** + * Emit bytecode to subtract two corresponding legs of the operands, leaving the borrow out on + * the stack. + *

    + * This assumes the stack has a borrow from the previous legs' difference in the upper 32 bits. + * It signed shifts the borrow such that it adds 0 or -1 into the left leg, and then subtracts + * the right leg. It conditionally writes the lower 32 bits of that, i.e., the resulting + * difference, into an output operand, and then leaves the borrow out in the upper 32 bits of + * the long on the stack. + *

    + * The returned value is always a non-null record, but the value of the operand may vary. If + * {@code store} is false, the operand is always null. This will be the case, e.g., for + * computing the borrow out of multi-precision subtraction, because the actual result is not + * needed. If {@code store} is true, the the returned operand may or may not be identical to the + * given {@code left} parameter, depending on whether or not that operand can be written. The + * caller must always use the returned operand to construct the legs of the final + * multi-precision output operand. It must never use {@code left}, nor the + * multi-precision operand containing it, as the final output. + * + * @param em the emitter typed with a stack of one long, the borrow out of the previous legs' + * difference, i.e., the borrow in for these legs' difference. + * @param left the leg for the left multi-precision operand + * @param right the leg for the right multi-precision operand + * @param storesResult true to receive the leg for the output multi-precision operand + * @param scope a scope for generating temporary local storage + * @return the output operand or null, and the emitter typed with a stack of one long whose + * value is the borrow out for these legs' difference + */ + static SimpleOpndEm> genMpIntLegSubTakesAndGivesBorrow( + Emitter> em, SimpleOpnd left, + SimpleOpnd right, boolean storesResult, Scope scope) { + return em + .emit(IntSubOpGen::prepLeftAndBorrow, left) + .emit(IntSubOpGen::subRight, right) + .emit(IntAddOpGen::maybeStore, left, storesResult, scope); + } + + /** + * Emit bytecode as in + * {@link #genMpIntLegSubTakesAndGivesBorrow(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} + * except that we do not expect a borrow in on the stack. + *

    + * This should be used to initiate the subtraction, taking the least-significant legs of the + * input multi-precision operands. + * + * @param em the emitter typed with the empty stack + * @param left the leg for the left multi-precision operand + * @param right the leg for the right multi-precision operand + * @param storesResult true to receive the leg for the output multi-precision operand + * @param scope a scope for generating temporary local storage + * @return the output operand or null, and the emitter typed with a stack of one long whose + * value is the borrow out for these legs' difference + */ + static SimpleOpndEm> genMpIntLegSubGivesBorrow( + Emitter em, SimpleOpnd left, SimpleOpnd right, + boolean storesResult, Scope scope) { + return em + .emit(left::read) + .emit(Op::i2l) + .emit(IntSubOpGen::subRight, right) + .emit(IntAddOpGen::maybeStore, left, storesResult, scope); + } + + /** + * Emit bytecode as in + * {@link #genMpIntLegSubTakesAndGivesBorrow(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} + * except that we do not leave a borrow out on the stack. + *

    + * This should be used to finalize the subtraction, taking the most-significant legs of the + * input multi-precision operands. Note that this always stores the result and returns an output + * operand. Otherwise, this would give no output at all, since it does not leave a borrow out on + * the stack. + * + * @param em the emitter typed with a stack of one long, the borrow out of the previous legs' + * difference, i.e., the borrow in for these legs' difference. + * @param left the leg for the left multi-precision operand + * @param right the leg for the right multi-precision operand + * @param scope a scope for generating temporary local storage + * @return the output operand and the emitter typed with the empty stack + */ + static SimpleOpndEm genMpIntLegSubTakesBorrow( + Emitter> em, SimpleOpnd left, + SimpleOpnd right, Scope scope) { + return em + .emit(IntSubOpGen::prepLeftAndBorrow, left) + .emit(IntSubOpGen::subRight, right) + .emit(Op::l2i) + .emit(left::write, scope); + } + + /** + * {@inheritDoc} + *

    + * The strategy here follows from grade-school long subtraction. We assert that there are at + * least two legs, otherwise we would have just emitted a single sub bytecode. This allows us to + * unconditionally initialize the subtraction with + * {@link #genMpIntLegSubGivesBorrow(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} and + * terminate it with {@link #genMpIntLegSubTakesBorrow(Emitter, SimpleOpnd, SimpleOpnd, Scope)}. + * When there are more than 2 legs, we use + * {@link #genMpIntLegSubTakesAndGivesBorrow(Emitter, SimpleOpnd, SimpleOpnd, boolean, Scope)} + * as many times as necessary in the middle. For all legs, we store the result and append it as + * a leg to the final output. + */ @Override - public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSubOp op, JitBlock block, - JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); - switch (rType) { - case IntJitType t -> rv.visitInsn(ISUB); - case LongJitType t -> rv.visitInsn(LSUB); - case MpIntJitType t when t.size() == lType.size() -> generateMpIntSub(gen, t, rv); - case MpIntJitType t -> TODO("MpInt of differing sizes"); - default -> throw new AssertionError(); + public Emitter genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitIntSubOp op, + MpIntJitType type, Scope scope) { + var left = gen.genReadToOpnd(em, localThis, op.l(), type, ext(), scope); + var right = gen.genReadToOpnd(left.em(), localThis, op.r(), type, rExt(), scope); + em = right.em(); + var lLegs = left.opnd().type().castLegsLE(left.opnd()); + assert lLegs.size() >= 2; + var rLegs = right.opnd().type().castLegsLE(right.opnd()); + + List> outLegs = new ArrayList<>(); + int legCount = type.legsAlloc(); + + var first = genMpIntLegSubGivesBorrow(em, lLegs.getFirst(), rLegs.getFirst(), true, scope); + var emCarry = first.em(); + outLegs.add(first.opnd()); + for (int i = 1; i < legCount - 1; i++) { + var result = + genMpIntLegSubTakesAndGivesBorrow(emCarry, lLegs.get(i), rLegs.get(i), true, scope); + emCarry = result.em(); + outLegs.add(result.opnd()); } - return rType; + var last = genMpIntLegSubTakesBorrow(emCarry, lLegs.getLast(), rLegs.getLast(), scope); + em = last.em(); + outLegs.add(last.opnd()); + + var out = MpIntLocalOpnd.of(type, "out", outLegs); + return gen.genWriteFromOpnd(em, localThis, op.out(), out, ext(), scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntXorOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntXorOpGen.java index ea3abecc77..b9ac776f25 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntXorOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntXorOpGen.java @@ -15,26 +15,36 @@ */ package ghidra.pcode.emu.jit.gen.op; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Op; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; import ghidra.pcode.emu.jit.op.JitIntXorOp; /** * The generator for a {@link JitIntXorOp int_xor}. * *

    - * This uses the bitwise binary operator and emits {@link #IXOR} or {@link #LXOR} depending on the - * type. + * This uses the bitwise binary operator and emits {@link Op#ixor(Emitter) ixor} or + * {@link Op#lxor(Emitter) lxor} depending on the type. */ -public enum IntXorOpGen implements BitwiseBinOpGen { +public enum IntXorOpGen implements IntBitwiseBinOpGen { /** The generator singleton */ GEN; @Override - public int intOpcode() { - return IXOR; + public , N0 extends Ent> + Emitter> opForInt(Emitter em, IntJitType type) { + return Op.ixor(em); } @Override - public int longOpcode() { - return LXOR; + public , N0 extends Ent> + Emitter> opForLong(Emitter em, LongJitType type) { + return Op.lxor(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java index 7e4c2e1eeb..302d74beff 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntZExtOpGen.java @@ -15,27 +15,20 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.op.JitIntZExtOp; /** * The generator for a {@link JitIntZExtOp int_zext}. - * *

    * This uses the unary operator generator and emits nothing extra. The unary generator template will * emit code to load the input operand, this emits nothing, and then the template emits code to * write the output operand, including the necessary type conversion. That type conversion performs * the zero extension. - * *

    * Note that this implementation is equivalent to {@link CopyOpGen}, except that differences in * operand sizes are expected. */ -public enum IntZExtOpGen implements IntUnOpGen { +public enum IntZExtOpGen implements IntExtUnOpGen { /** The generator singleton */ GEN; @@ -43,17 +36,4 @@ public enum IntZExtOpGen implements IntUnOpGen { public boolean isSigned() { return false; } - - /** - * {@inheritDoc} - * - * @implNote No need for explicit zero-extended type conversion (vice {@link IntSExtOpGen}), - * because conversion will happen as a manner of writing the output. Thus, this is - * identical in operation to {@link CopyOpGen}. - */ - @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntZExtOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - return uType; - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java index b9f6d2562c..aff629a9db 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LoadOpGen.java @@ -17,16 +17,25 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; +import java.util.ArrayList; +import java.util.List; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.FieldForSpaceIndirect; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.*; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.access.IntAccessGen; +import ghidra.pcode.emu.jit.gen.access.LongAccessGen; +import ghidra.pcode.emu.jit.gen.opnd.*; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitLoadOp; import ghidra.program.model.lang.Endian; @@ -40,172 +49,255 @@ import ghidra.program.model.lang.Endian; *

    * We request a field to pre-fetch the {@link JitBytesPcodeExecutorStateSpace space} and emit code * to load it onto the stack. We then emit code to load the offset onto the stack and convert it to - * a JVM long, if necessary. The varnode size is loaded by emitting an {@link Opcodes#LDC ldc}, and - * finally we emit an invocation of {@link JitBytesPcodeExecutorStateSpace#read(long, int)}. The - * result is a byte array, so we finish by emitting the appropriate conversion and write the result - * to the output operand. + * a JVM long, if necessary. The varnode size is loaded by emitting an + * {@link Op#ldc__i(Emitter, int) ldc}, and finally we emit an invocation of + * {@link JitBytesPcodeExecutorStateSpace#read(long, int)}. The result is a byte array, so we finish + * by emitting the appropriate conversion and write the result to the output operand. */ public enum LoadOpGen implements OpGen { /** The generator singleton */ GEN; - @Override - public void generateInitCode(JitCodeGenerator gen, JitLoadOp op, MethodVisitor iv) { - gen.requestFieldForSpaceIndirect(op.space()); + /** + * Read an integer (often a leg) from a given byte array + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param access the access generator for integers (determines the byte order) + * @param off the offset of the integer in the byte array + * @param type the p-code type of the value to read + * @return the emitter typed with the resulting stack, i.e., having popped the array ref and + * pushed the result. + */ + private >> Emitter> + genRunConvMpIntLeg(Emitter em, IntAccessGen access, int off, IntJitType type) { + return em + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, access.chooseReadName(type.size()), + MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); } - private void generateConvMpIntRunCodeLegBE(int off, int size, MethodVisitor rv, - boolean keepByteArr) { - // [...,bytearr] - if (keepByteArr) { - rv.visitInsn(DUP); - // [...,(bytearr),bytearr] - } - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - IntReadGen.BE.chooseName(size), MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - // [...,(bytearr),legN] - if (keepByteArr) { - rv.visitInsn(SWAP); - // [...,legN,(bytearr)] - } + /** + * Read an integer from a given byte array + *

    + * The byte array ought to exactly fit the type of the value being read. + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param endian the byte order + * @param type the p-code type of the value to read + * @return the emitter typed with the resulting stack, i.e., having popped the array ref and + * pushed the result. + */ + private >> Emitter> + genRunConvInt(Emitter em, Endian endian, IntJitType type) { + return genRunConvMpIntLeg(em, IntAccessGen.forEndian(endian), 0, type); } - private void generateConvMpIntRunCodeLegLE(int off, int size, MethodVisitor rv, - boolean keepByteArr) { - // [...,bytearr] - if (keepByteArr) { - rv.visitInsn(DUP); - // [...,(bytearr),bytearr] - } - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - IntReadGen.LE.chooseName(size), MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - // [...,(bytearr),legN] - if (keepByteArr) { - rv.visitInsn(SWAP); - // [...,legN,(bytearr)] - } + /** + * Read a long from a given byte array + *

    + * The byte array ought to exactly fit the type of the value being read. + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param endian the byte order + * @param type the p-code type of the value to read + * @return the emitter typed with the resulting stack, i.e., having popped the array ref and + * pushed the result. + */ + private >> Emitter> + genRunConvLong(Emitter em, Endian endian, LongJitType type) { + LongAccessGen access = LongAccessGen.forEndian(endian); + return em + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, access.chooseReadName(type.size()), + MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret); } - private void generateConvIntRunCode(Endian endian, IntJitType type, MethodVisitor rv) { - switch (endian) { - case BIG -> generateConvMpIntRunCodeLegBE(0, type.size(), rv, false); - case LITTLE -> generateConvMpIntRunCodeLegLE(0, type.size(), rv, false); - } + /** + * Read a float from a given byte array + *

    + * The byte array ought to exactly fit the type of the value being read. + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param endian the byte order + * @param type the p-code type of the value to read + * @return the emitter typed with the resulting stack, i.e., having popped the array ref and + * pushed the result. + */ + private >> Emitter> + genRunConvFloat(Emitter em, Endian endian, FloatJitType type) { + return em + .emit(this::genRunConvInt, endian, IntJitType.I4) + .emit(IntToFloat.INSTANCE::convertStackToStack, IntJitType.I4, type, Ext.ZERO); } - static String chooseReadLongName(Endian endian, int size) { + /** + * Read a double from a given byte array + *

    + * The byte array ought to exactly fit the type of the value being read. + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param endian the byte order + * @param type the p-code type of the value to read + * @return the emitter typed with the resulting stack, i.e., having popped the array ref and + * pushed the result. + */ + private >> Emitter> + genRunConvDouble(Emitter em, Endian endian, DoubleJitType type) { + return em + .emit(this::genRunConvLong, endian, LongJitType.I8) + .emit(LongToDouble.INSTANCE::convertStackToStack, LongJitType.I4, type, Ext.ZERO); + } + + /** + * The implementation of {@link #genRunConvMpInt(Emitter, Endian, MpIntJitType, String, Scope)} + * for big-endian order + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the value to read + * @param name the name prefix for the generated locals + * @param scope a scope for generating temporary local storage + * @return the operand containing the locals, and the emitter typed with the resulting stack, + * i.e., having popped the array ref + */ + private >> OpndEm + genRunConvMpIntBE(Emitter em, MpIntJitType type, String name, Scope scope) { + Local> arr = scope.decl(Types.T_BYTE_ARR, "arr"); + var emStored = em + .emit(Op::astore, arr); + + List> legs = new ArrayList<>(); + List legTypes = type.legTypesLE(); + int off = 0; + for (IntJitType lt : legTypes) { + var leg = emStored + .emit(Op::aload, arr) + .emit(this::genRunConvMpIntLeg, IntAccessGen.BE, off, lt) + .emit(Opnd::createInt, lt, "%s_off%d".formatted(name, off), scope); + emStored = leg.em(); + legs.add(leg.opnd()); + off += lt.size(); + } + return new OpndEm<>(MpIntLocalOpnd.of(type, name, legs), emStored); + } + + /** + * The implementation of {@link #genRunConvMpInt(Emitter, Endian, MpIntJitType, String, Scope)} + * for little-endian order + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param type the p-code type of the value to read + * @param name the name prefix for the generated locals + * @param scope a scope for generating temporary local storage + * @return the operand containing the locals, and the emitter typed with the resulting stack, + * i.e., having popped the array ref + */ + private >> OpndEm + genRunConvMpIntLE(Emitter em, MpIntJitType type, String name, Scope scope) { + Local> arr = scope.decl(Types.T_BYTE_ARR, "arr"); + var emStored = em + .emit(Op::astore, arr); + + List> legs = new ArrayList<>(); + List legTypes = type.legTypesLE(); + int off = type.size(); + for (IntJitType lt : legTypes) { + off -= lt.size(); + var leg = emStored + .emit(Op::aload, arr) + .emit(this::genRunConvMpIntLeg, IntAccessGen.LE, off, lt) + .emit(Opnd::createInt, lt, "%s_off%d".formatted(name, off), scope); + emStored = leg.em(); + legs.add(leg.opnd()); + } + return new OpndEm<>(MpIntLocalOpnd.of(type, name, legs), emStored); + } + + /** + * Read a multi-precision integer from a given byte array + *

    + * The byte array ought to exactly fit the type of the value being read. The {@code endian} + * parameter indicates the byte order in the source byte array. The resulting operand's legs are + * always in little-endian order. + * + * @param the tail of the incoming stack + * @param the incoming stack with a ref to the byte array on top + * @param em the emitter typed with the incoming stack + * @param endian the byte order + * @param type the p-code type of the value to read + * @param name the name prefix for the generated locals + * @param scope a scope for generating temporary local storage + * @return the operand containing the locals, and the emitter typed with the resulting stack, + * i.e., having popped the array ref + */ + private >> OpndEm + genRunConvMpInt(Emitter em, Endian endian, MpIntJitType type, String name, + Scope scope) { return switch (endian) { - case BIG -> LongReadGen.BE.chooseName(size); - case LITTLE -> LongReadGen.LE.chooseName(size); + case BIG -> genRunConvMpIntBE(em, type, name, scope); + case LITTLE -> genRunConvMpIntLE(em, type, name, scope); }; } - private void generateConvLongRunCode(Endian endian, LongJitType type, MethodVisitor rv) { - // [...,bytearr] - rv.visitLdcInsn(0); - // [...,bytearr, offset=0] - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseReadLongName(endian, type.size()), MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - // [...,value] - } - - private void generateConvFloatRunCode(Endian endian, FloatJitType type, MethodVisitor rv) { - // [...,bytearr] - generateConvIntRunCode(endian, IntJitType.I4, rv); - // [...,value:INT] - TypeConversions.generateIntToFloat(IntJitType.I4, type, rv); - // [...,value:FLOAT] - } - - private void generateConvDoubleRunCode(Endian endian, DoubleJitType type, MethodVisitor rv) { - // [...,bytearr] - generateConvLongRunCode(endian, LongJitType.I8, rv); - // [...,value:LONG] - TypeConversions.generateLongToDouble(LongJitType.I8, type, rv); - // [...,value:DOUBLE] - } - - private void generateConvMpIntRunCodeBE(MpIntJitType type, MethodVisitor rv) { - int countFull = type.legsWhole(); - int remSize = type.partialSize(); - - int off = 0; - if (remSize > 0) { - // [...,bytearr] - generateConvMpIntRunCodeLegBE(off, remSize, rv, true); - // [...,legN,bytearr] - off += remSize; - } - for (int i = 0; i < countFull; i++) { - // [...,legN-1,bytearr] - generateConvMpIntRunCodeLegBE(off, Integer.BYTES, rv, true); - // [...,legN-1,legN,bytearr] - off += Integer.BYTES; - } - // [...,leg1,...,legN,bytearr] - rv.visitInsn(POP); - // [...,leg1,...,legN] - } - - private void generateConvMpIntRunCodeLE(MpIntJitType type, MethodVisitor rv) { - int countFull = type.legsWhole(); - int remSize = type.partialSize(); - - int off = type.size(); - if (remSize > 0) { - off -= remSize; - // [...,bytearr] - generateConvMpIntRunCodeLegLE(off, remSize, rv, true); - // [...,legN,bytearr] - } - for (int i = 0; i < countFull; i++) { - off -= Integer.BYTES; - // [...,legN-1,bytearr] - generateConvMpIntRunCodeLegLE(off, Integer.BYTES, rv, true); - // [...,legN-1,legN,bytearr] - } - // [...,leg1,...,legN,bytearr] - rv.visitInsn(POP); - // [...,leg1,...,legN] - } - - private void generateConvMpIntRunCode(Endian endian, MpIntJitType type, - MethodVisitor rv) { - switch (endian) { - case BIG -> generateConvMpIntRunCodeBE(type, rv); - case LITTLE -> generateConvMpIntRunCodeLE(type, rv); - } - } - @Override - public void generateRunCode(JitCodeGenerator gen, JitLoadOp op, JitBlock block, - MethodVisitor rv) { - // [...] - gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); - // [...,space] - JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO); - // [...,space,offset:?INT/LONG] - TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv); - // [...,space,offset:LONG] - rv.visitLdcInsn(op.out().size()); - // [...,space,offset,size] - rv.visitMethodInsn(INVOKEVIRTUAL, NAME_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, "read", - MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__READ, false); - // [...,bytearr] + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitLoadOp op, JitBlock block, Scope scope) { + FieldForSpaceIndirect field = gen.requestFieldForSpaceIndirect(op.space()); + + var emArr = em + .emit(field::genLoad, localThis, gen) + .emit(gen::genReadToStack, localThis, op.offset(), LongJitType.I8, Ext.ZERO) + .emit(Op::ldc__i, op.out().size()) + .emit(Op::invokevirtual, T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, "read", + MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__READ, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret); + Endian endian = gen.getAnalysisContext().getEndian(); - JitType outType = gen.getTypeModel().typeOf(op.out()); - switch (outType) { - case IntJitType iType -> generateConvIntRunCode(endian, iType, rv); - case LongJitType lType -> generateConvLongRunCode(endian, lType, rv); - case FloatJitType fType -> generateConvFloatRunCode(endian, fType, rv); - case DoubleJitType dType -> generateConvDoubleRunCode(endian, dType, rv); - case MpIntJitType mpType -> generateConvMpIntRunCode(endian, mpType, rv); + em = switch (gen.getTypeModel().typeOf(op.out())) { + case IntJitType t -> emArr + .emit(this::genRunConvInt, endian, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case LongJitType t -> emArr + .emit(this::genRunConvLong, endian, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case MpIntJitType t -> { + var result = emArr + .emit(this::genRunConvMpInt, endian, t, "load", scope); + yield result.em() + .emit(gen::genWriteFromOpnd, localThis, op.out(), result.opnd(), Ext.ZERO, + scope); + } + case FloatJitType t -> emArr + .emit(this::genRunConvFloat, endian, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case DoubleJitType t -> emArr + .emit(this::genRunConvDouble, endian, t) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); default -> throw new AssertionError(); - } - // [...,value] - gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); - // [...] + }; + return new LiveOpResult(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java index 2f7e076a0d..0c3b33d5cb 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/LzCountOpGen.java @@ -17,14 +17,13 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.bouncycastle.util.Bytes; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitLzCountOp; /** @@ -35,7 +34,7 @@ import ghidra.pcode.emu.jit.op.JitLzCountOp; * {@link Integer#numberOfLeadingZeros(int)} or {@link Long#numberOfLeadingZeros(long)}, depending * on the type. */ -public enum LzCountOpGen implements IntUnOpGen { +public enum LzCountOpGen implements IntCountUnOpGen { /** The generator singleton */ GEN; @@ -48,66 +47,99 @@ public enum LzCountOpGen implements IntUnOpGen { return false; } - private void generateMpIntLzCount(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - // [leg1:INT,...,legN:INT] - mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros", - MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); - // [lzc1:INT,leg2:INT,...,legN:INT] - for (int i = 1; i < type.legsAlloc(); i++) { - mv.visitInsn(SWAP); - // [leg2:INT,lzc1:INT,...,legN:INT] - mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros", - MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); - // [lzc2:INT,lzc1:INT,...,legN:INT] - - Label lblAdd = new Label(); - Label lblNext = new Label(); - mv.visitInsn(DUP); - mv.visitLdcInsn(Integer.SIZE); - mv.visitJumpInsn(IF_ICMPEQ, lblAdd); - // [lzc2:INT,lzc1:INT,...,legN:INT] - mv.visitInsn(SWAP); - mv.visitInsn(POP); - // [lzc2:INT,...,legN:INT] - mv.visitJumpInsn(GOTO, lblNext); - mv.visitLabel(lblAdd); - // [lzc2:INT,lzc1:INT,...,legN:INT] - mv.visitInsn(IADD); - // [lzc2+lzc1:INT,...,legN:INT] - mv.visitLabel(lblNext); - // [lzcT:INT,...,legN:INT] - } - - SimpleJitType mslType = type.legTypes().get(0); - if (mslType.size() < Integer.BYTES) { - mv.visitLdcInsn(Integer.SIZE - mslType.size() * Byte.SIZE); - mv.visitInsn(ISUB); + @Override + public > Emitter> + opForInt(Emitter em, IntJitType type) { + var temp = em + .emit(Op::invokestatic, TR_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false) + .step(Inv::takeArg) + .step(Inv::ret); + if (type != IntJitType.I4) { + return temp + .emit(Op::ldc__i, Integer.SIZE - type.size() * Byte.SIZE) + .emit(Op::isub); } + return temp; } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitLzCountOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case IntJitType t -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros", - MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); - if (t.size() < Integer.BYTES) { - rv.visitLdcInsn(Integer.SIZE - t.size() * Byte.SIZE); - rv.visitInsn(ISUB); - } - } - case LongJitType t -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "numberOfLeadingZeros", - MDESC_LONG__NUMBER_OF_LEADING_ZEROS, false); - if (t.size() < Long.BYTES) { - rv.visitLdcInsn(Long.SIZE - t.size() * Bytes.SIZE); - rv.visitInsn(ISUB); - } - } - case MpIntJitType t -> generateMpIntLzCount(gen, t, rv); - default -> throw new AssertionError(); + public > Emitter> + opForLong(Emitter em, LongJitType type) { + var temp = em + .emit(Op::invokestatic, TR_LONG, "numberOfLeadingZeros", + MDESC_LONG__NUMBER_OF_LEADING_ZEROS, false) + .step(Inv::takeArg) + .step(Inv::ret); + if (type != LongJitType.I8) { + return temp + .emit(Op::ldc__i, Long.SIZE - type.size() * Byte.SIZE) + .emit(Op::isub); } - return IntJitType.I4; + return temp; + } + + /** + * {@inheritDoc} + *

    + * The strategy here is straightforward: Start with the most-significant leg, totalling up the + * number of leading zeros, until we encounter a situation where the leg did not have 32 zeros. + * We test for this by checking if the running total is equal to 32 times the number of legs + * processed so far. We need not load or compute any legs beyond the point where we come up with + * less. When we reach the end, we made need to subtract some constant number of bits to account + * for types that do not occupy the full most-significant leg. + */ + @Override + public Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitLzCountOp op, + MpIntJitType type, Scope scope) { + /** + * LATER: There could be a more efficient way to do this without having to piece the leg + * parts together, when we're dealing with a shifted mp-int. We could instead just load the + * masked parts and adjust the result based on the shift. + */ + /** + * Start with the most significant and stop when we get anything other than Integer.SIZE, or + * right before the last. + */ + int legCount = type.legsAlloc(); + Lbl> lblDone = Lbl.create(); + + var emCount = em + .emit(gen::genReadLegToStack, localThis, op.u(), type, legCount - 1, ext()) + .emit(Op::invokestatic, TR_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::dup) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Op::if_icmpne, lblDone); + for (int i = legCount - 2; i >= 1; i--) { + emCount = emCount + .emit(gen::genReadLegToStack, localThis, op.u(), type, i, ext()) + .emit(Op::invokestatic, TR_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::iadd) + .emit(Op::dup) + .emit(Op::ldc__i, Integer.SIZE * (legCount - i)) + .emit(Op::if_icmpne, lblDone); + } + emCount = emCount + .emit(gen::genReadLegToStack, localThis, op.u(), type, 0, ext()) + .emit(Op::invokestatic, TR_INTEGER, "numberOfLeadingZeros", + MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::iadd) + .emit(Lbl::place, lblDone); + IntJitType mslType = type.legTypesLE().getLast(); + if (mslType != IntJitType.I4) { + emCount = emCount + .emit(Op::ldc__i, Integer.SIZE - mslType.size() * Byte.SIZE) + .emit(Op::isub); + } + return emCount; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/NopOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/NopOpGen.java index 7786836bf1..74e5212dde 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/NopOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/NopOpGen.java @@ -15,10 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitNopOp; import ghidra.pcode.emu.jit.op.JitOp; @@ -33,7 +38,9 @@ public enum NopOpGen implements OpGen { GEN; @Override - public void generateRunCode(JitCodeGenerator gen, JitOp op, JitBlock block, MethodVisitor rv) { - // NOP + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitOp op, JitBlock block, Scope scope) { + return new LiveOpResult(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java index 654abc790e..d1a6ae761a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/OpGen.java @@ -15,19 +15,29 @@ */ package ghidra.pcode.emu.jit.gen.op; -import java.io.PrintStream; -import java.util.stream.Collectors; -import java.util.stream.IntStream; +import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.*; +import java.util.List; +import java.util.stream.Collectors; + +import org.objectweb.asm.Opcodes; import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.var.*; @@ -169,13 +179,13 @@ import ghidra.program.model.pcode.PcodeOp; * {@link PcodeOp#INT_ZEXT int_zext} * {@link JitIntZExtOp} * {@link IntZExtOpGen} - * none; defers to {@link VarGen} and {@link TypeConversions} + * none; defers to {@link VarGen} and {@link Opnd} * * * {@link PcodeOp#INT_SEXT int_sext} * {@link JitIntSExtOp} * {@link IntSExtOpGen} - * {@link Opcodes#ISHL ishl}, {@link Opcodes#ISHR ishr}, etc. + * none; defers to {@link VarGen} and {@link Opnd} * * * {@link PcodeOp#INT_ADD int_add} @@ -487,7 +497,7 @@ import ghidra.program.model.pcode.PcodeOp; * * @param the class of p-code op node in the use-def graph */ -public interface OpGen extends Opcodes { +public interface OpGen { /** * Lookup the generator for a given p-code op use-def node * @@ -568,156 +578,64 @@ public interface OpGen extends Opcodes { } /** - * Emit bytecode to move all legs from the stack into a temporary allocation - *

    - * This consumes {@code legCount} legs from the stack. Nothing else is pushed to the stack. The - * legs are placed in ascending indices as popped from the stack, i.e., in little-endian order. + * For debugging: emit code to print the values of the given operand to stderr. * - * @param temp the allocation of temporary legs - * @param legCount the number of legs to move - * @param mv the method visitor + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param opnd the operand whose values to print + * @return the emitter typed with the incoming stack */ - static void generateMpLegsIntoTemp(JvmTempAlloc temp, int legCount, MethodVisitor mv) { - // [leg1,...,legN] - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ISTORE, temp.idx(i)); + static Emitter generateSyserrInts(Emitter em, Opnd opnd) { + List> legs = opnd.type().castLegsLE(opnd); + String fmt = legs.stream().map(l -> "%08x").collect(Collectors.joining(":")); + var emArr = em + .emit(Op::getstatic, T_SYSTEM, "err", T_PRINT_STREAM) + .emit(Op::ldc__a, fmt) + .emit(Op::ldc__i, legs.size()) + .emit(Op::anewarray, T_OBJECT); + for (int i = 0; i < legs.size(); i++) { + SimpleOpnd leg = legs.get(i); + emArr = emArr + .emit(Op::dup) + .emit(Op::ldc__i, i) + .emit(leg::read) + .emit(Op::invokestatic, TR_INTEGER, "valueOf", MDESC_INTEGER__VALUE_OF, + false) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(Op::aastore); } - // [] + return emArr + .emit(Op::invokevirtual, T_STRING, "formatted", MDESC_STRING__FORMATTED, false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::invokevirtual, T_PRINT_STREAM, "println", MDESC_PRINT_STREAM__PRINTLN, + false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid); } /** - * Emit bytecode to move all legs from a temporary allocation onto the stack - *

    - * This consumes nothing. It places {@code legCount} legs onto the stack, pushed in descending - * order, i.e., such that they would be popped in little-endian order. - * - * @param temp the allocation of temporary legs - * @param legCount the number of lets to move - * @param mv the method visitor + * The result of emitting code for a p-code op */ - static void generateMpLegsFromTemp(JvmTempAlloc temp, int legCount, MethodVisitor mv) { - // [] - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, temp.idx(legCount - i - 1)); - } - // [leg1,...,legN] + sealed interface OpResult { } /** - * Emit bytecode to copy all legs from a temporary allocation into an array - *

    - * This does not consume anything from the stack. Upon return, the new array is pushed onto the - * stack. The legs are positioned in the array in the same order as in the locals. When used - * with {@link #generateMpLegsIntoTemp(JvmTempAlloc, int, MethodVisitor)}, this is little-endian - * order. + * The result when bytecode after that emitted is reachable * - * @param temp the allocation of temporary legs - * @param arrSize the size of the array, possibly over-provisioned - * @param legCount the number of legs to move - * @param mv the method visitor + * @param em the emitter typed with the empty stack */ - static void generateMpLegsIntoArray(JvmTempAlloc temp, int arrSize, int legCount, - MethodVisitor mv) { - assert arrSize >= legCount; - // [] - mv.visitLdcInsn(arrSize); - // [count:INT] - mv.visitIntInsn(NEWARRAY, T_INT); - // [arr:INT[count]] - for (int i = 0; i < legCount; i++) { - mv.visitInsn(DUP); - // [arr,arr:INT[count]] - mv.visitLdcInsn(i); - // [idx:INT,arr,arr:INT[count]] - mv.visitVarInsn(ILOAD, temp.idx(i)); - // [leg:INT,idx:INT,arr,arr:INT[count]] - mv.visitInsn(IASTORE); - // [arr:INT[count]] - } - } + record LiveOpResult(Emitter em) implements OpResult {} /** - * Emit bytecode to push all legs from an array onto the stack - *

    - * This consumes the array at the top of the stack, and pushes its legs onto the stack in the - * reverse order as they are positioned in the array. If the legs are in little-endian order, as - * is convention, this method will push the legs to the stack with the least-significant leg on - * top. + * The result when bytecode after that emitted is not reachable * - * @param legCount the number of legs in the array - * @param mv the method visitor + * @param em the dead emitter */ - static void generateMpLegsFromArray(int legCount, MethodVisitor mv) { - // [out:INT[count]] - for (int i = 0; i < legCount - 1; i++) { - // [out] - mv.visitInsn(DUP); - // [out,out] - mv.visitLdcInsn(legCount - 1 - i); - // [idx,out,out] - mv.visitInsn(IALOAD); - // [legN:INT,out] - mv.visitInsn(SWAP); - // [out,legN:INT] - } - mv.visitLdcInsn(0); - // [idx,out,...] - mv.visitInsn(IALOAD); - // [leg1,...] - } - - static void generateSyserrInts(JitCodeGenerator gen, int count, MethodVisitor mv) { - try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", count)) { - // [leg1,...,legN] - generateMpLegsIntoTemp(temp, count, mv); - // [] - mv.visitFieldInsn(GETSTATIC, Type.getInternalName(System.class), "err", - Type.getDescriptor(PrintStream.class)); - // [System.err] - String fmt = - IntStream.range(0, count).mapToObj(i -> "%08x").collect(Collectors.joining(":")); - mv.visitLdcInsn(fmt); - // [fmt:String,System.err] - - mv.visitLdcInsn(count); - // [count,fmt,System.err] - mv.visitTypeInsn(ANEWARRAY, Type.getInternalName(Object.class)); - // [blegs:Object[count],fmt,System.err] - for (int i = 0; i < count; i++) { - mv.visitInsn(DUP); - // [blegs,blegs,fmt,System.err] - mv.visitLdcInsn(i); - // [idx:INT,blegs,blegs,fmt,System.err] - mv.visitVarInsn(ILOAD, temp.idx(count - i - 1)); - // [val:INT,idx,blegs,blegs,fmt,System.err] - mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(Integer.class), "valueOf", - Type.getMethodDescriptor(Type.getType(Integer.class), Type.INT_TYPE), false); - // [val:Integer,idx,blegs,blegs,fmt,System.err] - mv.visitInsn(AASTORE); - // [blegs,fmt,System.err] - } - - mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(String.class), "formatted", - Type.getMethodDescriptor(Type.getType(String.class), Type.getType(Object[].class)), - false); - // [msg:String,System.err] - mv.visitMethodInsn(INVOKEVIRTUAL, Type.getInternalName(PrintStream.class), "println", - Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)), false); - // [] - generateMpLegsFromTemp(temp, count, mv); - // [leg1,...,legN] - } - } - - /** - * Emit bytecode into the class constructor. - * - * @param gen the code generator - * @param op the p-code op (use-def node) to translate - * @param iv the visitor for the class constructor - */ - default void generateInitCode(JitCodeGenerator gen, T op, MethodVisitor iv) { - } + record DeadOpResult(Emitter em) implements OpResult {} /** * Emit bytecode into the {@link JitCompiledPassage#run(int) run} method. @@ -726,14 +644,23 @@ public interface OpGen extends Opcodes { * This method must emit the code needed to load any input operands, convert them to the * appropriate type, perform the actual operation, and then if applicable, store the output * operand. The implementations should delegate to - * {@link JitCodeGenerator#generateValReadCode(JitVal, JitTypeBehavior, Ext)}, - * {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType, Ext)}, and - * {@link TypeConversions} appropriately. + * {@link JitCodeGenerator#genReadToStack(Emitter, Local, JitVal, ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType, Ext)}, + * {@link JitCodeGenerator#genWriteFromStack(Emitter, Local, JitVar, ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType, Ext, Scope)} + * or similar for mp-int types. * + * @param the type of the generated passage + * @param em the emitter typed with the empty stack + * @param localThis a handle to the local holding the {@code this} reference + * @param localCtxmod a handle to the local holding {@code ctxmod} + * @param retReq an indication of what must be returned by this + * {@link JitCompiledPassage#run(int)} method. * @param gen the code generator * @param op the p-code op (use-def node) to translate * @param block the basic block containing the p-code op - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method. + * @param scope a scope for generating temporary local storage + * @return the result of emitting the p-code op's bytecode */ - void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv); + OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, T op, JitBlock block, Scope scope); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PhiOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PhiOpGen.java index 7c33110506..8cf833d256 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PhiOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PhiOpGen.java @@ -15,11 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitVarScopeModel; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitPhiOp; /** @@ -28,7 +33,7 @@ import ghidra.pcode.emu.jit.op.JitPhiOp; *

    * We emit nothing. This generator ought not to be invoked, anyway, but things may change. In the * meantime, the design is that we allocate a JVM local per varnode. Since phi nodes are meant to - * track possible definitions of the same varnode, there is no need to a phi node to emit + * track possible definitions of the same varnode, there is no need for a phi node to emit * any code. The value, whichever option it happens to be, is already in its local variable. * * @see JitVarScopeModel @@ -38,7 +43,9 @@ public enum PhiOpGen implements OpGen { GEN; @Override - public void generateRunCode(JitCodeGenerator gen, JitPhiOp op, JitBlock block, - MethodVisitor rv) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitPhiOp op, JitBlock block, Scope scope) { + return new LiveOpResult(em); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java index 642b623572..c42e6489bd 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/PopCountOpGen.java @@ -17,12 +17,13 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitPopCountOp; /** @@ -32,7 +33,7 @@ import ghidra.pcode.emu.jit.op.JitPopCountOp; * This uses the unary operator generator and emits an invocation of {@link Integer#bitCount(int)} * or {@link Long#bitCount(long)}, depending on the type. */ -public enum PopCountOpGen implements IntUnOpGen { +public enum PopCountOpGen implements IntCountUnOpGen { /** The generator singleton */ GEN; @@ -41,32 +42,44 @@ public enum PopCountOpGen implements IntUnOpGen { return false; } - private void generateMpIntPopCount(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { - // [leg1:INT,...,legN:INT] - mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "bitCount", MDESC_INTEGER__BIT_COUNT, false); - // [pop1:INT,leg2:INT...,legN:INT] - for (int i = 1; i < type.legsAlloc(); i++) { - mv.visitInsn(SWAP); - // [leg2:INT,pop1:INT,...,legN:INT] - mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "bitCount", MDESC_INTEGER__BIT_COUNT, - false); - // [pop2:INT,pop1:INT,...,legN:INT] - mv.visitInsn(IADD); - // [popT:INT,...,legN:INT] - } + @Override + public > Emitter> + opForInt(Emitter em, IntJitType type) { + return em + .emit(Op::invokestatic, TR_INTEGER, "bitCount", MDESC_INTEGER__BIT_COUNT, false) + .step(Inv::takeArg) + .step(Inv::ret); } @Override - public JitType generateUnOpRunCode(JitCodeGenerator gen, JitPopCountOp op, JitBlock block, - JitType uType, MethodVisitor rv) { - switch (uType) { - case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "bitCount", - MDESC_INTEGER__BIT_COUNT, false); - case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "bitCount", - MDESC_LONG__BIT_COUNT, false); - case MpIntJitType t -> generateMpIntPopCount(gen, t, rv); - default -> throw new AssertionError(); + public > Emitter> + opForLong(Emitter em, LongJitType type) { + return em + .emit(Op::invokestatic, TR_LONG, "bitCount", MDESC_LONG__BIT_COUNT, false) + .step(Inv::takeArg) + .step(Inv::ret); + } + + /** + * {@inheritDoc} + *

    + * The strategy here is to simply total the pop count from every leg. + */ + @Override + public Emitter> genRunMpInt(Emitter em, + Local> localThis, JitCodeGenerator gen, JitPopCountOp op, + MpIntJitType type, Scope scope) { + int legCount = type.legsAlloc(); + + var emCount = em + .emit(gen::genReadLegToStack, localThis, op.u(), type, 0, ext()) + .emit(this::opForInt, IntJitType.I4); + for (int i = 1; i < legCount; i++) { + emCount = emCount + .emit(gen::genReadLegToStack, localThis, op.u(), type, i, ext()) + .emit(this::opForInt, IntJitType.I4) + .emit(Op::iadd); } - return IntJitType.I4; + return emCount; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java deleted file mode 100644 index 65acabca9b..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/ShiftIntBinOpGen.java +++ /dev/null @@ -1,198 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.op; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; -import ghidra.pcode.emu.jit.op.JitIntBinOp; - -/** - * An extension for integer shift operators - * - *

    - * This is just going to invoke one of the {@link JitCompiledPassage#intLeft(int, int)}, - * {@link JitCompiledPassage#intRight(int, int)}, {@link JitCompiledPassage#intSRight(int, int)}, - * etc. methods, depending on the operand types. - * - * @param the class of p-code op node in the use-def graph - */ -public interface ShiftIntBinOpGen extends IntBinOpGen { - /** - * {@inheritDoc} - *

    - * The shift amount is always treated unsigned. - */ - @Override - default Ext rExt() { - return Ext.ZERO; - } - - /** - * The name of the static method in {@link JitCompiledPassage} to invoke - * - * @return the name - */ - String methodName(); - - default MpIntJitType generateShiftMpPrimitive(JitAllocationModel am, int legCount, - SimpleJitType rType, MpIntJitType outType, String mdesc, MethodVisitor mv) { - try ( - JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", legCount); - JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", rType.javaType(), 1)) { - // [amt:INT, lleg1:INT,...,llegN:INT] - mv.visitVarInsn(rType.opcodeStore(), tmpR.idx(0)); - // [lleg1,...,llegN] - OpGen.generateMpLegsIntoTemp(tmpL, legCount, mv); - // [] - /** - * FIXME: We could avoid this array allocation by shifting in place, but then we'd still - * need to communicate the actual out size. Things are easy if the out size is smaller - * than the left-in size, but not so easy if larger. Or, maybe over-provision if - * larger.... - */ - mv.visitLdcInsn(outType.legsAlloc()); - // [outLegCount:INT] - mv.visitIntInsn(NEWARRAY, T_INT); - // [out:ARR] - mv.visitInsn(DUP); - // [out,out] - mv.visitLdcInsn(outType.size()); - // [outBytes:INT,out,out] - OpGen.generateMpLegsIntoArray(tmpL, legCount, legCount, mv); - // [inL:ARR,outBytes:INT,out,out] - mv.visitVarInsn(rType.opcodeLoad(), tmpR.idx(0)); - // [inR:SIMPLE,inL:ARR,outBytes,out,out] - mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true); - // [out] - OpGen.generateMpLegsFromArray(outType.legsAlloc(), mv); - // [oleg1,...,olegN] - } - return outType.ext(); - } - - default SimpleJitType generateShiftPrimitiveMp(JitAllocationModel am, SimpleJitType lType, - int legCount, String mdesc, MethodVisitor mv) { - try (JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", legCount)) { - // [rleg1:INT,...,rlegN:INT,val:INT] - OpGen.generateMpLegsIntoTemp(tmpR, legCount, mv); - // [val:INT] - OpGen.generateMpLegsIntoArray(tmpR, legCount, legCount, mv); - // [inR:ARR,val:INT] - mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true); - // [out:INT] - } - return lType.ext(); - } - - default MpIntJitType generateShiftMpMp(JitAllocationModel am, int leftLegCount, - int rightLegCount, MpIntJitType outType, MethodVisitor mv) { - try ( - JvmTempAlloc tmpL = am.allocateTemp(mv, "tmpL", leftLegCount); - JvmTempAlloc tmpR = am.allocateTemp(mv, "tmpR", rightLegCount)) { - // [rleg1:INT,...,rlegN:INT,lleg1:INT,...,llegN:INT] - OpGen.generateMpLegsIntoTemp(tmpR, rightLegCount, mv); - // [lleg1,...,llegN] - OpGen.generateMpLegsIntoTemp(tmpL, leftLegCount, mv); - // [] - // FIXME: Same as in shiftPrimitiveMp - int outLegCount = outType.legsAlloc(); - mv.visitLdcInsn(outLegCount); - // [outLegCount:INT] - mv.visitIntInsn(NEWARRAY, T_INT); - // [out:ARR] - mv.visitInsn(DUP); - // [out,out] - mv.visitLdcInsn(outType.size()); - // [outBytes:INT,out,out] - OpGen.generateMpLegsIntoArray(tmpL, leftLegCount, leftLegCount, mv); - // [inL:ARR,outBytes,out,out] - OpGen.generateMpLegsIntoArray(tmpR, rightLegCount, rightLegCount, mv); - // [inR,inL,outBytes,out,out] - mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), - MDESC_$SHIFT_AA, true); - // [out] - OpGen.generateMpLegsFromArray(outLegCount, mv); - // [oleg1,...,olegN] - } - return outType; - } - - /** - * {@inheritDoc} - * - *

    - * This reduces the implementation to just the name of the method to invoke. This will select - * the JVM signature of the method based on the p-code operand types. - */ - @Override - default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, - JitType rType, MethodVisitor rv) { - JitAllocationModel am = gen.getAllocationModel(); - return switch (lType) { - case IntJitType lt -> switch (rType) { - case IntJitType rt -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), - MDESC_$SHIFT_II, true); - yield lType.ext(); - } - case LongJitType rt -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), - MDESC_$SHIFT_IJ, true); - yield lType.ext(); - } - case MpIntJitType rt -> generateShiftPrimitiveMp(am, lt, rt.legsAlloc(), - MDESC_$SHIFT_IA, rv); - default -> throw new AssertionError(); - }; - - case LongJitType lt -> switch (rType) { - case IntJitType rt -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), - MDESC_$SHIFT_JI, true); - yield lType.ext(); - } - case LongJitType rt -> { - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), - MDESC_$SHIFT_JJ, true); - yield lType.ext(); - } - case MpIntJitType rt -> generateShiftPrimitiveMp(am, lt, rt.legsAlloc(), - MDESC_$SHIFT_JA, rv); - default -> throw new AssertionError(); - }; - case MpIntJitType lt -> switch (rType) { - case IntJitType rt -> generateShiftMpPrimitive(am, lt.legsAlloc(), rt, - MpIntJitType.forSize(op.out().size()), MDESC_$SHIFT_AI, rv); - case LongJitType rt -> generateShiftMpPrimitive(am, lt.legsAlloc(), rt, - MpIntJitType.forSize(op.out().size()), MDESC_$SHIFT_AJ, rv); - case MpIntJitType rt -> generateShiftMpMp(am, lt.legsAlloc(), rt.legsAlloc(), - MpIntJitType.forSize(op.out().size()), rv); - default -> throw new AssertionError(); - }; - default -> throw new AssertionError(); - }; - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java index 9e5cff18bc..343cc608ee 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/StoreOpGen.java @@ -17,16 +17,25 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; +import java.util.List; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorStatePiece.JitBytesPcodeExecutorStateSpace; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.FieldForSpaceIndirect; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.*; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.access.IntAccessGen; +import ghidra.pcode.emu.jit.gen.access.LongAccessGen; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.*; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitStoreOp; import ghidra.program.model.lang.Endian; @@ -40,178 +49,245 @@ import ghidra.program.model.lang.Endian; *

    * We request a field to pre-fetch the {@link JitBytesPcodeExecutorStateSpace space} and emit code * to load it onto the stack. We then emit code to load the offset onto the stack and convert it to - * a JVM long, if necessary. The varnode size is loaded by emitting an {@link Opcodes#LDC ldc}. We - * must now emit code to load the value and convert it to a byte array. The conversion depends on - * the type of the value. Finally, we emit an invocation of + * a JVM long, if necessary. The varnode size is loaded by emitting an + * {@link Op#ldc__i(Emitter, int) ldc}. We must now emit code to load the value and convert it to a + * byte array. The conversion depends on the type of the value. Finally, we emit an invocation of * {@link JitBytesPcodeExecutorStateSpace#write(long, byte[], int, int)}. */ public enum StoreOpGen implements OpGen { /** The generator singleton */ GEN; - @Override - public void generateInitCode(JitCodeGenerator gen, JitStoreOp op, MethodVisitor iv) { - gen.requestFieldForSpaceIndirect(op.space()); + /** + * Write an integer (often a leg) into a given byte array + * + * @param the tail of the incoming stack + * @param the incoming stack with the source value on top + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param access the access generator for integers (determines the byte order) + * @param off the offset in the byte array to write the integer + * @param type the p-code type of the value to write + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + private > Emitter + genRunConvMpIntLeg(Emitter em, Local> localArr, IntAccessGen access, + int off, IntJitType type) { + return em + .emit(Op::aload, localArr) + .emit(Op::ldc__i, off) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, access.chooseWriteName(type.size()), + MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); } - private void generateConvMpIntRunCodeLegBE(int off, int size, MethodVisitor rv) { - // [...,legN,bytearr] - rv.visitInsn(DUP_X1); - // [...,bytearr,legN,bytearr] - rv.visitLdcInsn(off); - // [...,bytearr,legN,bytearr,off] - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - IntWriteGen.BE.chooseName(size), MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - // [...,bytearr] + /** + * Write an integer into a given byte array + *

    + * The byte array ought to exactly fit the type of the value being written. + * + * @param the tail of the incoming stack + * @param the incoming stack with the source value on top + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param endian the byte order + * @param type the p-code type of the value to write + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + private > Emitter genRunConvInt(Emitter em, + Local> localArr, Endian endian, IntJitType type) { + return em + .emit(this::genRunConvMpIntLeg, localArr, IntAccessGen.forEndian(endian), 0, type); } - private void generateConvMpIntRunCodeLegLE(int off, int size, MethodVisitor rv) { - // [...,legN,bytearr] - rv.visitInsn(DUP_X1); - // [...,bytearr,legN,bytearr] - rv.visitLdcInsn(off); - // [...,bytearr,legN,bytearr,off] - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - IntWriteGen.LE.chooseName(size), MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - // [...,bytearr] + /** + * Write a long into a given byte array + *

    + * The byte array ought to exactly fit the type of the value being written. + * + * @param the tail of the incoming stack + * @param the incoming stack with the source value on top + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param endian the byte order + * @param type the p-code type of the value to write + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + private > Emitter genRunConvLong(Emitter em, + Local> localArr, Endian endian, LongJitType type) { + LongAccessGen access = LongAccessGen.forEndian(endian); + return em + .emit(Op::aload, localArr) + .emit(Op::ldc__i, 0) + .emit(Op::invokestatic, T_JIT_COMPILED_PASSAGE, access.chooseWriteName(type.size()), + MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::retVoid); } - private void generateConvIntRunCode(Endian endian, IntJitType type, MethodVisitor rv) { - switch (endian) { - case BIG -> generateConvMpIntRunCodeLegBE(0, type.size(), rv); - case LITTLE -> generateConvMpIntRunCodeLegLE(0, type.size(), rv); + /** + * Write a float into a given byte array + *

    + * The byte array ought to exactly fit the type of the value being written. + * + * @param the tail of the incoming stack + * @param the incoming stack with the source value on top + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param endian the byte order + * @param type the p-code type of the value to write + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + private > Emitter genRunConvFloat( + Emitter em, Local> localArr, Endian endian, FloatJitType type) { + return em + .emit(FloatToInt.INSTANCE::convertStackToStack, type, IntJitType.I4, Ext.ZERO) + .emit(this::genRunConvInt, localArr, endian, IntJitType.I4); + } + + /** + * Write a double into a given byte array + *

    + * The byte array ought to exactly fit the type of the value being written. + * + * @param the tail of the incoming stack + * @param the incoming stack with the source value on top + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param endian the byte order + * @param type the p-code type of the value to write + * @return the emitter typed with the resulting stack, i.e., having popped the value + */ + private > Emitter genRunConvDouble( + Emitter em, Local> localArr, Endian endian, DoubleJitType type) { + return em + .emit(DoubleToLong.INSTANCE::convertStackToStack, type, LongJitType.I8, Ext.ZERO) + .emit(this::genRunConvLong, localArr, endian, LongJitType.I8); + } + + /** + * The implementation of {@link #genRunConvMpInt(Emitter, Local, Endian, Opnd, Scope)} for + * big-endian order + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param opnd the source operand (list of locals and p-code type) + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack + */ + private Emitter genRunConvMpIntBE(Emitter em, + Local> localArr, Opnd opnd, Scope scope) { + List> legs = opnd.type().castLegsLE(opnd); + int off = opnd.type().size(); + for (SimpleOpnd l : legs) { + off -= l.type().size(); + em = em + .emit(l::read) + .emit(this::genRunConvMpIntLeg, localArr, IntAccessGen.BE, off, l.type()); } + return em; } - private String chooseWriteLongName(Endian endian, int size) { + /** + * The implementation of {@link #genRunConvMpInt(Emitter, Local, Endian, Opnd, Scope)} for + * little-endian order + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param opnd the source operand (list of locals and p-code type) + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack + */ + private Emitter genRunConvMpIntLE(Emitter em, + Local> localArr, Opnd opnd, Scope scope) { + List> legs = opnd.type().castLegsLE(opnd); + int off = 0; + for (SimpleOpnd l : legs) { + em = em + .emit(l::read) + .emit(this::genRunConvMpIntLeg, localArr, IntAccessGen.LE, off, l.type()); + off += l.type().size(); + } + return em; + } + + /** + * Write a double into a given byte array + *

    + * The byte array ought to exactly fit the type of the value being written. The {@code endian} + * parameter indicates the byte order in the destination array. The source operand's legs are + * always in little-endian order. + * + * @param the incoming stack + * @param em the emitter typed with the incoming stack + * @param localArr a handle to the local holding the destination array reference + * @param endian the byte order + * @param opnd the source operand (list of locals and p-code type) + * @param scope a scope for generating temporary local storage + * @return the emitter typed with the incoming stack + */ + private Emitter genRunConvMpInt(Emitter em, Local> localArr, + Endian endian, Opnd opnd, Scope scope) { return switch (endian) { - case BIG -> LongWriteGen.BE.chooseName(size); - case LITTLE -> LongWriteGen.LE.chooseName(size); + case BIG -> genRunConvMpIntBE(em, localArr, opnd, scope); + case LITTLE -> genRunConvMpIntLE(em, localArr, opnd, scope); }; } - private void generateConvLongRunCode(Endian endian, LongJitType type, MethodVisitor rv) { - // [...,value:LONG,bytearr] - rv.visitInsn(DUP_X2); - // [...,bytearr,value:LONG,bytearr] - rv.visitLdcInsn(0); - // [...,bytearr,value:LONG,bytearr,offset=0] - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseWriteLongName(endian, type.size()), - MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - // [...,bytearr]) - } - - private void generateConvFloatRunCode(Endian endian, FloatJitType type, MethodVisitor rv) { - // [...,value:FLOAT,bytearr] - rv.visitInsn(SWAP); - // [...,bytearr,value:FLOAT] - TypeConversions.generateFloatToInt(type, IntJitType.I4, rv); - // [...,bytearr,value:INT] - rv.visitInsn(SWAP); - // [...,value:INT,bytearr] - generateConvIntRunCode(endian, IntJitType.I4, rv); - // [...,bytearr] - } - - private void generateConvDoubleRunCode(Endian endian, DoubleJitType type, MethodVisitor rv) { - // [...,value:DOUBLE,bytearr] - rv.visitInsn(DUP_X2); - // [...,bytearr,value:DOUBLE,bytearr] - rv.visitInsn(POP); - // [...,bytearr,value:DOUBLE] - TypeConversions.generateDoubleToLong(type, LongJitType.I8, rv); - // [...,bytearr,value:LONG] - rv.visitInsn(DUP2_X1); - // [...,value:LONG,bytearr,value:LONG] - rv.visitInsn(POP2); - // [...,value:LONG,bytearr] - generateConvLongRunCode(endian, LongJitType.I8, rv); - // [...,bytearr] - } - - private void generateConvMpIntRunCodeBE(MpIntJitType type, MethodVisitor rv) { - // [...,leg1,...,legN,bytearr] - int countFull = type.legsWhole(); - int remSize = type.partialSize(); - - int off = type.size(); - for (int i = 0; i < countFull; i++) { - off -= Integer.BYTES; - // [...,legN-1,legN,bytearr] - generateConvMpIntRunCodeLegBE(off, Integer.BYTES, rv); - // [...,legN-1,bytearr] - } - if (remSize > 0) { - off -= remSize; - // [...,leg1,bytearr] - generateConvMpIntRunCodeLegBE(off, remSize, rv); - // [...,bytearr] - } - // [...,bytearr] - } - - private void generateConvMpIntRunCodeLE(MpIntJitType type, MethodVisitor rv) { - // [...,leg1,...,legN,bytearr] - int countFull = type.legsWhole(); - int remSize = type.partialSize(); - - int off = 0; - for (int i = 0; i < countFull; i++) { - // [...,legN-1,legN,bytearr] - generateConvMpIntRunCodeLegLE(off, Integer.BYTES, rv); - // [...,legN-1,bytearr] - off += Integer.BYTES; - } - if (remSize > 0) { - // [...,leg1,bytearr] - generateConvMpIntRunCodeLegLE(off, remSize, rv); - // [...,bytearr] - off += remSize; - } - // [...,bytearr] - } - - private void generateConvMpIntRunCode(Endian endian, MpIntJitType type, MethodVisitor rv) { - switch (endian) { - case BIG -> generateConvMpIntRunCodeBE(type, rv); - case LITTLE -> generateConvMpIntRunCodeLE(type, rv); - } - } - @Override - public void generateRunCode(JitCodeGenerator gen, JitStoreOp op, JitBlock block, - MethodVisitor rv) { - // [...] - gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); - // [...,space] - JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO); - // [...,space,offset:?] - TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv); - // [...,space,offset:LONG] - JitType valueType = gen.generateValReadCode(op.value(), op.valueType(), Ext.ZERO); - // [...,space,offset,value] - rv.visitLdcInsn(op.value().size()); - // [...,space,offset,value,size] - rv.visitIntInsn(NEWARRAY, T_BYTE); - // [...,space,offset,value,bytearray] + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitStoreOp op, JitBlock block, Scope scope) { + FieldForSpaceIndirect field = gen.requestFieldForSpaceIndirect(op.space()); + + Local> localArr = scope.decl(Types.T_BYTE_ARR, "arr"); + var emSpaceOffset = em + .emit(field::genLoad, localThis, gen) + .emit(gen::genReadToStack, localThis, op.offset(), LongJitType.I8, Ext.ZERO) + .emit(Op::ldc__i, op.value().size()) + .emit(Op::newarray, Types.T_BYTE) + .emit(Op::astore, localArr); + Endian endian = gen.getAnalysisContext().getEndian(); - switch (valueType) { - case IntJitType iType -> generateConvIntRunCode(endian, iType, rv); - case LongJitType lType -> generateConvLongRunCode(endian, lType, rv); - case FloatJitType fType -> generateConvFloatRunCode(endian, fType, rv); - case DoubleJitType dType -> generateConvDoubleRunCode(endian, dType, rv); - case MpIntJitType mpType -> generateConvMpIntRunCode(endian, mpType, rv); + var emConv = switch (gen.getTypeModel().typeOf(op.value())) { + case IntJitType t -> emSpaceOffset + .emit(gen::genReadToStack, localThis, op.value(), t, Ext.ZERO) + .emit(this::genRunConvInt, localArr, endian, t); + case LongJitType t -> emSpaceOffset + .emit(gen::genReadToStack, localThis, op.value(), t, Ext.ZERO) + .emit(this::genRunConvLong, localArr, endian, t); + case MpIntJitType t -> { + var value = emSpaceOffset + .emit(gen::genReadToOpnd, localThis, op.value(), t, Ext.ZERO, scope); + yield value.em() + .emit(this::genRunConvMpInt, localArr, endian, value.opnd(), scope); + } + case FloatJitType t -> emSpaceOffset + .emit(gen::genReadToStack, localThis, op.value(), t, Ext.ZERO) + .emit(this::genRunConvFloat, localArr, endian, t); + case DoubleJitType t -> emSpaceOffset + .emit(gen::genReadToStack, localThis, op.value(), t, Ext.ZERO) + .emit(this::genRunConvDouble, localArr, endian, t); default -> throw new AssertionError(); - } - // [...,space,offset,bytearray] - rv.visitLdcInsn(0); - // [...,space,offset,bytearray,srcOffset=0] - rv.visitLdcInsn(op.value().size()); - // [...,space,offset,bytearray,srcOffset=0,size] - rv.visitMethodInsn( - INVOKEVIRTUAL, NAME_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, "write", - MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__WRITE, false); - // [...] + }; + return new LiveOpResult(emConv + .emit(Op::aload, localArr) + .emit(Op::ldc__i, 0) + .emit(Op::ldc__i, op.value().size()) + .emit(Op::invokevirtual, T_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE, "write", + MDESC_JIT_BYTES_PCODE_EXECUTOR_STATE_SPACE__WRITE, false) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java index 88000acaee..85fad5484b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SubPieceOpGen.java @@ -15,23 +15,25 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; +import ghidra.pcode.emu.jit.gen.var.ValGen; import ghidra.pcode.emu.jit.op.JitSubPieceOp; +import ghidra.pcode.emu.jit.var.JitVal; /** * The generator for a {@link JitSubPieceOp subpiece}. - * - *

    - * NOTE: The multi-precision int parts of this are a work in progress. - * *

    * This is not quite like a normal binary operator, because the second operand is always a constant. * It behaves more like a class of unary operators, if you ask me. Thus, we do not extend @@ -39,122 +41,42 @@ import ghidra.pcode.emu.jit.op.JitSubPieceOp; * constant, we can deal with it at generation time. We emit code to shift right by that * constant amount, accounting for bits and bytes. The masking, if required, is taken care of by the * variable writing code, given the resulting type. + *

    + * To avoid loading parts of the (left) operand that will just get dropped by this operator, we + * instead provide the subpiecing arguments (namely the offset and destination operand size) to the + * value-loading logic. This is done via the {@link ValGen#subpiece(int, int)} method. We can then + * load only those parts that are actually needed. */ public enum SubPieceOpGen implements OpGen { /** The generator singleton */ GEN; - /** - * Assumes the next-more-significant leg (i.e., the one from the previous iteration) is on - * the stack and the current (unshifted) leg is in the given variable. Computes the resulting - * output leg and puts in into the given local variable, but leaves a copy of the current - * unshifted leg on the stack. - * - * @param rv the method visitor - * @param bitShift the number of bits to shift - * @param index the index of the local variable for the current leg - * @implNote This cannot yet be factored with the shifting operators, because those - * take a variable for the shift amount. The subpiece offset is always constant. - * If/when we optimize shift operators with constant shift amounts, then we can - * consider factoring the common parts with this. - */ - private static void generateShiftWithPrevLeg(MethodVisitor rv, int bitShift, int index) { - // [...,prevLegIn] - rv.visitLdcInsn(Integer.SIZE - bitShift); - rv.visitInsn(ISHL); - // [...,prevLegIn:SLACK] - rv.visitVarInsn(ILOAD, index); - // [...,prevLegIn:SLACK,legIn] - rv.visitInsn(DUP_X1); - // [...,legIn,prevLegIn:SLACK,legIn] - rv.visitLdcInsn(bitShift); - rv.visitInsn(IUSHR); - // [...,legIn,prevLegIn:SLACK,legIn:SHIFT] - rv.visitInsn(IOR); - // [...,legIn,legOut] - rv.visitVarInsn(ISTORE, index); - // [...,legIn] - } - - private static MpIntJitType generateMpIntSubPiece(JitCodeGenerator gen, JitSubPieceOp op, - MpIntJitType type, MethodVisitor mv) { - MpIntJitType outMpType = MpIntJitType.forSize(op.out().size()); - int outLegCount = outMpType.legsAlloc(); - int legsLeft = type.legsAlloc(); - int popCount = op.offset() / Integer.BYTES; - int byteShift = op.offset() % Integer.BYTES; - for (int i = 0; i < popCount; i++) { - mv.visitInsn(POP); - legsLeft--; - } - - JitAllocationModel am = gen.getAllocationModel(); - try (JvmTempAlloc subpieces = am.allocateTemp(mv, "subpiece", outLegCount)) { - for (int i = 0; i < outLegCount; i++) { - mv.visitVarInsn(ISTORE, subpieces.idx(i)); - // NOTE: More significant legs have higher indices (reverse of stack) - legsLeft--; - } - - if (byteShift > 0) { - int curLeg = outLegCount - 1; - if (legsLeft > 0) { - // [...,prevLegIn] - generateShiftWithPrevLeg(mv, byteShift * Byte.SIZE, subpieces.idx(curLeg)); - // [...,legIn] - legsLeft--; - curLeg--; - } - else { - // [...] - mv.visitVarInsn(ILOAD, subpieces.idx(curLeg)); - // [...,legIn] - mv.visitInsn(DUP); - // [...,legIn,legIn] - mv.visitLdcInsn(byteShift * Byte.SIZE); - mv.visitInsn(IUSHR); - // [...,legIn,legOut] - mv.visitVarInsn(ISTORE, subpieces.idx(curLeg)); - // [...,legIn] - curLeg--; - } - while (curLeg >= 0) { - generateShiftWithPrevLeg(mv, byteShift * Byte.SIZE, subpieces.idx(curLeg)); - legsLeft--; - curLeg--; - } - } - while (legsLeft > 0) { - mv.visitInsn(POP); - legsLeft--; - } - // NOTE: More significant legs have higher indices - for (int i = outLegCount - 1; i >= 0; i--) { - mv.visitVarInsn(ILOAD, subpieces.idx(i)); - } - } - return outMpType; - } - @Override - public void generateRunCode(JitCodeGenerator gen, JitSubPieceOp op, JitBlock block, - MethodVisitor rv) { - JitType vType = gen.generateValReadCode(op.u(), op.uType(), Ext.ZERO); - JitType outType = switch (vType) { - case IntJitType vIType -> { - rv.visitLdcInsn(op.offset() * Byte.SIZE); - rv.visitInsn(IUSHR); - yield vIType; - } - case LongJitType vLType -> { - rv.visitLdcInsn(op.offset() * Byte.SIZE); - rv.visitInsn(LUSHR); - yield vLType; - } - case MpIntJitType vMpType -> generateMpIntSubPiece(gen, op, vMpType, rv); - default -> throw new AssertionError(); - }; - gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); - } + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitSubPieceOp op, JitBlock block, Scope scope) { + JitType uType = gen.resolveType(op.u(), op.uType()); + JitType outType = gen.resolveType(op.out(), op.type()); + ValGen uGen = ValGen.lookup(op.u()).subpiece(op.offset(), op.out().size()); + int pieceSize = Math.min(uType.size() - op.offset(), outType.size()); + JitType pType = JitTypeBehavior.INTEGER.type(pieceSize); + + return new LiveOpResult(switch (pType) { + case IntJitType t -> em + .emit(uGen::genReadToStack, localThis, gen, op.u(), t, Ext.ZERO) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case LongJitType t -> em + .emit(uGen::genReadToStack, localThis, gen, op.u(), t, Ext.ZERO) + .emit(gen::genWriteFromStack, localThis, op.out(), t, Ext.ZERO, scope); + case MpIntJitType t -> { + var result = em + .emit(uGen::genReadToOpnd, localThis, gen, op.u(), t, Ext.ZERO, scope); + yield result.em() + .emit(gen::genWriteFromOpnd, localThis, op.out(), result.opnd(), + Ext.ZERO, scope); + } + default -> throw new AssertionError(); + }); + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SynthSubPieceOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SynthSubPieceOpGen.java index 298d4195a1..f87696c79b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SynthSubPieceOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/SynthSubPieceOpGen.java @@ -15,11 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitVarScopeModel; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitSynthSubPieceOp; /** @@ -36,8 +41,9 @@ public enum SynthSubPieceOpGen implements OpGen { GEN; @Override - public void generateRunCode(JitCodeGenerator gen, JitSynthSubPieceOp op, JitBlock block, - MethodVisitor rv) { + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitSynthSubPieceOp op, JitBlock block, Scope scope) { throw new AssertionError("Cannnot generate synthetic op"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java index a2f87f58ee..3b426e2e77 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnOpGen.java @@ -15,12 +15,7 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; import ghidra.pcode.emu.jit.op.JitUnOp; /** @@ -49,36 +44,4 @@ public interface UnOpGen extends OpGen { default Ext ext() { return Ext.forSigned(isSigned()); } - - /** - * Emit code for the unary operator - * - *

    - * At this point the operand is on the stack. After this returns, code to write the result from - * the stack into the destination operand will be emitted. - * - * @param gen the code generator - * @param op the operator - * @param block the block containing the operator - * @param uType the actual type of the operand - * @param rv the method visitor - * @return the actual type of the result - */ - JitType generateUnOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType uType, - MethodVisitor rv); - - /** - * {@inheritDoc} - * - *

    - * This default implementation emits code to load the operand, invokes - * {@link #generateUnOpRunCode(JitCodeGenerator, JitUnOp, JitBlock, JitType, MethodVisitor) - * gen-unop}, and finally emits code to write the destination operand. - */ - @Override - default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { - JitType uType = gen.generateValReadCode(op.u(), op.uType(), ext()); - JitType outType = generateUnOpRunCode(gen, op, block, uType, rv); - gen.generateVarWriteCode(op.out(), outType, ext()); - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java index 34761c09a6..2256007e4f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/UnimplementedOpGen.java @@ -17,16 +17,23 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitPassage.DecodeErrorPcodeOp; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.RunFixedLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator.PcGen; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPoint; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.op.JitUnimplementedOp; import ghidra.pcode.error.LowlevelError; import ghidra.pcode.exec.DecodePcodeExecutionException; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.RegisterValue; /** * The generator for a {@link JitUnimplementedOp unimplemented}. @@ -41,35 +48,38 @@ public enum UnimplementedOpGen implements OpGen { GEN; @Override - public void generateRunCode(JitCodeGenerator gen, JitUnimplementedOp op, JitBlock block, - MethodVisitor rv) { - long counter = gen.getAddressForOp(op.op()).getOffset(); - - gen.generatePassageExit(block, () -> { - rv.visitLdcInsn(counter); - }, gen.getExitContext(op.op()), rv); - + public OpResult genRun(Emitter em, + Local> localThis, Local localCtxmod, RetReq> retReq, + JitCodeGenerator gen, JitUnimplementedOp op, JitBlock block, Scope scope) { + Address counter = gen.getAddressForOp(op.op()); + PcGen pcGen = PcGen.loadOffset(counter); + RegisterValue ctx = gen.getExitContext(op.op()); String message = gen.getErrorMessage(op.op()); + if (op.op() instanceof DecodeErrorPcodeOp) { - RunFixedLocal.THIS.generateLoadCode(rv); - rv.visitLdcInsn(message); - rv.visitLdcInsn(counter); - rv.visitMethodInsn(INVOKEINTERFACE, NAME_JIT_COMPILED_PASSAGE, "createDecodeError", - MDESC_JIT_COMPILED_PASSAGE__CREATE_DECODE_ERROR, true); - rv.visitInsn(ATHROW); - } - else { - // [...] - rv.visitTypeInsn(NEW, NAME_LOW_LEVEL_ERROR); - // [...,error:NEW] - rv.visitInsn(DUP); - // [...,error:NEW,error:NEW] - rv.visitLdcInsn(message); - // [...,error:NEW,error:NEW,message] - rv.visitMethodInsn(INVOKESPECIAL, NAME_LOW_LEVEL_ERROR, "", - MDESC_LOW_LEVEL_ERROR__$INIT, false); - // [...,error] - rv.visitInsn(ATHROW); + return new DeadOpResult(em + .emit(gen::genExit, localThis, block, pcGen, ctx) + .emit(Op::aload, localThis) + .emit(Op::ldc__a, message) + .emit(Op::ldc__l, counter.getOffset()) + .emit(Op::invokeinterface, T_JIT_COMPILED_PASSAGE, "createDecodeError", + MDESC_JIT_COMPILED_PASSAGE__CREATE_DECODE_ERROR) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::ret) + .emit(Op::athrow)); } + return new DeadOpResult(em + .emit(gen::genExit, localThis, block, pcGen, ctx) + .emit(Op::new_, T_LOWLEVEL_ERROR) + .emit(Op::dup) + .emit(Op::ldc__a, message) + .emit(Op::invokespecial, T_LOWLEVEL_ERROR, "", MDESC_LOWLEVEL_ERROR__$INIT, + false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::athrow)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/ConstSimpleOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/ConstSimpleOpnd.java new file mode 100644 index 0000000000..4a80dbc60b --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/ConstSimpleOpnd.java @@ -0,0 +1,46 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.util.Emitter; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; + +/** + * A constant that can be pushed onto the JVM stack + * + * @param the JVM type of the constant + * @param the p-code type of the constant + */ +interface ConstSimpleOpnd, JT extends SimpleJitType> + extends SimpleOpnd { + + /** + * Generate a name should this need conversion into a temporary variable + * + * @return the name + */ + default String tempName() { + return "%d_tempFromRo".formatted(name()); + } + + @Override + default > Emitter writeDirect(Emitter em) { + throw new UnsupportedOperationException(); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/DoubleConstOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/DoubleConstOpnd.java new file mode 100644 index 0000000000..c8bb84d321 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/DoubleConstOpnd.java @@ -0,0 +1,45 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; + +/** + * A constant {@code double} + */ +record DoubleConstOpnd(double value, DoubleJitType type) + implements ConstSimpleOpnd { + + @Override + public String name() { + return "const_double_%f".formatted(value).replace(".", "dot"); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::ldc__d, value); + } + + @Override + public > SimpleOpndEm + write(Emitter em, Scope scope) { + return DoubleLocalOpnd.temp(type(), tempName(), scope).write(em, scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/DoubleLocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/DoubleLocalOpnd.java new file mode 100644 index 0000000000..f27bec74d4 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/DoubleLocalOpnd.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TDouble; + +/** + * A {@code double} local variable + */ +record DoubleLocalOpnd(DoubleJitType type, Local local) + implements LocalOpnd { + + static DoubleLocalOpnd of(DoubleJitType type, Local local) { + return new DoubleLocalOpnd(type, local); + } + + static DoubleLocalOpnd temp(DoubleJitType type, String name, Scope scope) { + return of(type, scope.decl(type.bType(), name)); + } + + static > SimpleOpndEm + create(Emitter em, DoubleJitType type, String name, Scope scope) { + return temp(type, name, scope).write(em, scope); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::dload, local); + } + + @Override + public > Emitter writeDirect(Emitter em) { + return em.emit(Op::dstore, local); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/FloatConstOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/FloatConstOpnd.java new file mode 100644 index 0000000000..9850eece59 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/FloatConstOpnd.java @@ -0,0 +1,42 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.FloatJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; + +record FloatConstOpnd(float value, FloatJitType type) + implements ConstSimpleOpnd { + + @Override + public String name() { + return "const_float_%f".formatted(value).replace(".", "dot"); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::ldc__f, value); + } + + @Override + public > SimpleOpndEm + write(Emitter em, Scope scope) { + return FloatLocalOpnd.temp(type(), tempName(), scope).write(em, scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/FloatLocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/FloatLocalOpnd.java new file mode 100644 index 0000000000..dac0ed9729 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/FloatLocalOpnd.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.FloatJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TFloat; + +/** + * A {@code float} local variable + */ +record FloatLocalOpnd(FloatJitType type, Local local) + implements LocalOpnd { + + static FloatLocalOpnd of(FloatJitType type, Local local) { + return new FloatLocalOpnd(type, local); + } + + static FloatLocalOpnd temp(FloatJitType type, String name, Scope scope) { + return of(type, scope.decl(type.bType(), name)); + } + + static > SimpleOpndEm + create(Emitter em, FloatJitType type, String name, Scope scope) { + return temp(type, name, scope).write(em, scope); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::fload, local); + } + + @Override + public > Emitter writeDirect(Emitter em) { + return em.emit(Op::fstore, local); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntConstOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntConstOpnd.java new file mode 100644 index 0000000000..2dab2d1321 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntConstOpnd.java @@ -0,0 +1,73 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; + +/** + * A constant {@code int} + * + * @param value the value + * @param type the p-code type + */ +public record IntConstOpnd(int value, IntJitType type) + implements ConstSimpleOpnd { + + /** 0 of type {@link IntJitType#I1 int1} */ + public static final IntConstOpnd ZERO_I1 = new IntConstOpnd(0, IntJitType.I1); + /** 0 of type {@link IntJitType#I2 int2} */ + public static final IntConstOpnd ZERO_I2 = new IntConstOpnd(0, IntJitType.I2); + /** 0 of type {@link IntJitType#I3 int3} */ + public static final IntConstOpnd ZERO_I3 = new IntConstOpnd(0, IntJitType.I3); + /** 0 of type {@link IntJitType#I4 int4} */ + public static final IntConstOpnd ZERO_I4 = new IntConstOpnd(0, IntJitType.I4); + + /** + * Get a constant 0 of the given p-code {@link IntJitType int} type. + * + * @param type the type + * @return the constant 0 + */ + public static IntConstOpnd zero(IntJitType type) { + return switch (type.size()) { + case 1 -> ZERO_I1; + case 2 -> ZERO_I2; + case 3 -> ZERO_I3; + case 4 -> ZERO_I4; + default -> throw new AssertionError(); + }; + } + + @Override + public String name() { + return "const_int_0x%x".formatted(value); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::ldc__i, value); + } + + @Override + public > SimpleOpndEm + write(Emitter em, Scope scope) { + return IntLocalOpnd.temp(type(), tempName(), scope).write(em, scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntLocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntLocalOpnd.java new file mode 100644 index 0000000000..f594c27f96 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntLocalOpnd.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; + +/** + * A {@code int} local variable + */ +record IntLocalOpnd(IntJitType type, Local local) implements LocalOpnd { + + static IntLocalOpnd of(IntJitType type, Local local) { + return new IntLocalOpnd(type, local); + } + + static IntLocalOpnd temp(IntJitType type, String name, Scope scope) { + return of(type, scope.decl(type.bType(), name)); + } + + static > SimpleOpndEm create( + Emitter em, IntJitType type, String name, Scope scope) { + return temp(type, name, scope).write(em, scope); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::iload, local); + } + + @Override + public > Emitter writeDirect(Emitter em) { + return em.emit(Op::istore, local); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntReadOnlyLocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntReadOnlyLocalOpnd.java new file mode 100644 index 0000000000..761dd322bb --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/IntReadOnlyLocalOpnd.java @@ -0,0 +1,62 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; + +/** + * A {@code int} local variable that cannot be used for temporary values + * + * @see SimpleOpnd#ofIntReadOnly(IntJitType, Local) + */ +record IntReadOnlyLocalOpnd(IntJitType type, Local local) + implements LocalOpnd { + + static IntReadOnlyLocalOpnd of(IntJitType type, Local local) { + return new IntReadOnlyLocalOpnd(type, local); + } + + static IntReadOnlyLocalOpnd temp(IntJitType type, String name, Scope scope) { + return of(type, scope.decl(type.bType(), name)); + } + + static > SimpleOpndEm + create(Emitter em, IntJitType type, String name, Scope scope) { + IntReadOnlyLocalOpnd ro = IntReadOnlyLocalOpnd.temp(type, name, scope); + return new SimpleOpndEm<>(ro, em.emit(Op::istore, ro.local)); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::iload, local); + } + + @Override + public > SimpleOpndEm + write(Emitter em, Scope scope) { + String name = "%s_tempFromRo".formatted(this.name()); + return IntLocalOpnd.temp(type(), name, scope).write(em, scope); + } + + @Override + public > Emitter writeDirect(Emitter em) { + throw new UnsupportedOperationException(); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LocalOpnd.java new file mode 100644 index 0000000000..b4ad215065 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LocalOpnd.java @@ -0,0 +1,40 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.gen.util.Local; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; + +/** + * A mutable operand that can be contained in a single JVM local + * + * @param the JVM type + * @param the p-code type + */ +interface LocalOpnd, JT extends SimpleJitType> + extends SimpleOpnd { + + /** + * {@return the local handle} + */ + Local local(); + + @Override + default String name() { + return local().name(); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LongConstOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LongConstOpnd.java new file mode 100644 index 0000000000..c8e6e5beee --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LongConstOpnd.java @@ -0,0 +1,45 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; + +/** + * A constant {@code long} + */ +record LongConstOpnd(long value, LongJitType type) + implements ConstSimpleOpnd { + + @Override + public String name() { + return "const_long_0x%x".formatted(value); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::ldc__l, value); + } + + @Override + public > SimpleOpndEm + write(Emitter em, Scope scope) { + return LongLocalOpnd.temp(type(), tempName(), scope).write(em, scope); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LongLocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LongLocalOpnd.java new file mode 100644 index 0000000000..728562d1c7 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/LongLocalOpnd.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.TLong; + +/** + * A {@code long} local variable + */ +record LongLocalOpnd(LongJitType type, Local local) + implements LocalOpnd { + + static LongLocalOpnd of(LongJitType type, Local local) { + return new LongLocalOpnd(type, local); + } + + static LongLocalOpnd temp(LongJitType type, String name, Scope scope) { + return of(type, scope.decl(type.bType(), name)); + } + + static > SimpleOpndEm create( + Emitter em, LongJitType type, String name, Scope scope) { + return temp(type, name, scope).write(em, scope); + } + + @Override + public Emitter> read(Emitter em) { + return em.emit(Op::lload, local); + } + + @Override + public > Emitter writeDirect(Emitter em) { + return em.emit(Op::lstore, local); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/MpIntConstOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/MpIntConstOpnd.java new file mode 100644 index 0000000000..d70ebbe58f --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/MpIntConstOpnd.java @@ -0,0 +1,45 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; + +/** + * A constant multi-precision integer operand + */ +record MpIntConstOpnd(MpIntJitType type, String name, List legsLE) + implements Opnd { + + static List computeLegs(BigInteger value, MpIntJitType type) { + List legs = new ArrayList<>(); + int count = type.legsAlloc(); + for (int i = 0; i < count; i++) { + IntJitType t = type.legTypesLE().get(i); + legs.add(new IntConstOpnd(value.intValue(), t)); + value = value.shiftRight(Integer.SIZE); + } + return List.copyOf(legs); + } + + public MpIntConstOpnd(BigInteger value, MpIntJitType type) { + this(type, "const_mpint_0x%s".formatted(value.toString(16)), computeLegs(value, type)); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/MpIntLocalOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/MpIntLocalOpnd.java new file mode 100644 index 0000000000..0663326e23 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/MpIntLocalOpnd.java @@ -0,0 +1,56 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.gen.util.Types.TInt; + +/** + * A (usually mutable) multi-precision integer operand + *

    + * This may be composed of simple mutable and constant operands. The most common cause of constant + * operands is zero extension. It is also possible the same mutable (local) operand may appear more + * than once. The most common cause of such duplication is sign extension. + * + * @param type the p-code type + * @param name a name (prefix) to use for generated temporary legs + * @param legsLE the legs in little-endian order + */ +public record MpIntLocalOpnd(MpIntJitType type, String name, + List> legsLE) + implements Opnd { + + /** + * Create a multi-precision integer operand from the given legs + * + * @param type the p-code type + * @param name the name (prefix) to use for generate temporary legs + * @param legsLE the legs in little-endian order + * @return the operand + */ + public static MpIntLocalOpnd of(MpIntJitType type, String name, + List> legsLE) { + return new MpIntLocalOpnd(type, name, legsLE); + } + + public MpIntLocalOpnd { + legsLE = List.copyOf(legsLE); + // Assert leg types match? + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/Opnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/Opnd.java new file mode 100644 index 0000000000..9cc2b59211 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/Opnd.java @@ -0,0 +1,1464 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.GenConsts; +import ghidra.pcode.emu.jit.gen.opnd.SimpleOpnd.SimpleOpndEm; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * A (sometimes temporary) operand + *

    + * This class is also the namespace for a number of convesion operations. Please note that + * "conversions" here deal entirely in bits. While a lot of machinery is needed to represent p-code + * values, esp., when the number of bytes exceeds a JVM long, in essence, every conversion + * operation, if it performs any operation at all, is merely bit truncation or extension. + * Otherwise, all we are doing is convincing the JVM that the operand's type has changed. In + * particular, an int-to-float conversion is not accomplished using {@link Op#i2f(Emitter) + * i2f}, as that would actually change the raw bit contents of the value. Rather, we use + * {@link Float#intBitsToFloat(int)}. + * + * @param the p-code type + */ +public interface Opnd { + + /** + * An operand-emitter tuple + * + * @param the p-code type of the operand + * @param the stack of the emitter + */ + record OpndEm(Opnd opnd, Emitter em) { + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OpndEm castBack(TT to, T from) { + assert from == to; + return (OpndEm) this; + } + } + + /** + * An interface for converting between simple stack operands + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type + * @param the "to" p-code type + */ + interface StackToStackConv< + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TJT extends SimpleJitType> { + + /** + * Convert a stack operand to another stack operand + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + > Emitter> + convertStackToStack(Emitter em, FJT from, TJT to, Ext ext); + } + + /** + * An interface for converting simple stack operands to multi-precision operands + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type for each mp leg + * @param the "to" p-code type for each mp leg + * @param the "to" p-code type + */ + interface StackToMpConv< + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TLT extends SimpleJitType, + TJT extends LeggedJitType> { + + /** + * Convert a stack operand to an mp operand in locals + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param name the name to give the resulting operand + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary locals + * @return the resulting operand and the emitter with ... + */ + > OpndEm convertStackToOpnd(Emitter em, + FJT from, String name, TJT to, Ext ext, Scope scope); + + /** + * Convert a stack operand to an mp operand in an array + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param name the name to give the resulting operand + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary locals + * @param slack the number of extra (more significant) elements to allocate in the array + * @return the emitter with ..., arrayref + */ + > Emitter>> convertStackToArray( + Emitter em, FJT from, String name, TJT to, Ext ext, Scope scope, int slack); + } + + /** + * An interface for converting multi-precision operands to simple stack operands + * + * @param the "from" JVM type for each mp leg + * @param the "from" p-code type for each mp leg + * @param the "from" p-code type + * @param the "to" JVM type + * @param the "to" p-code type + */ + interface MpToStackConv< + FT extends BPrim, FLT extends SimpleJitType, + FJT extends LeggedJitType, + TT extends BPrim, TJT extends SimpleJitType> { + + /** + * Convert an mp operand in locals to a stack operand + * + * @param the tail of the stack (...) + * @param em the emitter + * @param from the source operand + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + Emitter> convertOpndToStack(Emitter em, Opnd from, + TJT to, Ext ext); + + /** + * Convert an mp operand in an array to a stack operand + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param em the emitter + * @param from the source p-code type + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + >> Emitter> + convertArrayToStack(Emitter em, FJT from, TJT to, Ext ext); + } + + /** + * An interface for converting between multi-precision operands + * + * @param the "from" JVM type for each mp leg + * @param the "from" p-code type for each mp leg + * @param the "from" p-code type + * @param the "to" JVM type for each mp leg + * @param the "to" p-code type for each mp leg + * @param the "to" p-code type + */ + interface MpToMpConv< + FT extends BPrim, FLT extends SimpleJitType, + FJT extends LeggedJitType, + TT extends BPrim, TLT extends SimpleJitType, + TJT extends LeggedJitType> { + + /** + * Convert an operand in locals to another in locals + *

    + * NOTE: This may be accomplished in part be re-using legs from the source operand in the + * destination operand + * + * @param the tail of the stack (...) + * @param em the emitter + * @param from the source operand + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary variables + * @return the resulting operand and emitter with ... + */ + OpndEm convertOpndToOpnd(Emitter em, Opnd from, TJT to, + Ext ext, Scope scope); + + /** + * Convert an operand in locals to an array + * + * @param the tail of the stack (...) + * @param em the emitter + * @param from the source operand + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary variables + * @param slack the number of extra (more significant) elements to allocate in the array + * @return the emitter with ..., arrayref + */ + Emitter>> convertOpndToArray(Emitter em, + Opnd from, TJT to, Ext ext, Scope scope, int slack); + } + + /** + * Check if the given int-to-int conversion would require extension + * + * @param from the source p-code type + * @param to the destination p-code type + * @return true if extension is needed, i.e., {@code to} is strictly larger than {@code from} + */ + static boolean needsIntExt(IntJitType from, IntJitType to) { + return to.size() < from.size(); + } + + /** + * Check if the given long-to-long conversion would require extension + * + * @param from the source p-code type + * @param to the destination p-code type + * @return true if extension is needed, i.e., {@code to} is strictly larger than {@code from} + */ + static boolean needsLongExt(LongJitType from, LongJitType to) { + return to.size() < from.size(); + } + + /** + * Emit a long left shift, selecting signed or unsigned by the given extension + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lextshr(Emitter em, Ext ext) { + return switch (ext) { + case ZERO -> em.emit(Op::lushr); + case SIGN -> em.emit(Op::lshr); + }; + } + + /** + * Converter from int to int + */ + enum IntToInt implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, IntJitType from, IntJitType to, Ext ext) { + if (!needsIntExt(from, to)) { + return em.emit(Misc::cast1); + } + int shamt = Integer.SIZE - to.size() * Byte.SIZE; + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, -1 >>> shamt) + .emit(Op::iand); + case SIGN -> switch (to.size()) { + case 1 -> em + .emit(Op::i2b); + case 2 -> em + .emit(Op::i2s); + default -> em // 3 is all that's really left + .emit(Op::ldc__i, shamt) + .emit(Op::ishl) + .emit(Op::ldc__i, shamt) + .emit(Op::ishr); + }; + }; + } + } + + /** + * Converter from int to long + */ + enum IntToLong implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, IntJitType from, LongJitType to, Ext ext) { + return switch (ext) { + case ZERO -> em + .emit(Op::invokestatic, GenConsts.TR_INTEGER, "toUnsignedLong", + GenConsts.MDESC_INTEGER__TO_UNSIGNED_LONG, false) + .step(Inv::takeArg) + .step(Inv::ret); + case SIGN -> em + .emit(IntToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(Op::i2l); + }; + } + } + + /** + * Converter from int to mp-int + */ + enum IntToMpInt implements StackToMpConv { + INSTANCE; + + public OpndEm doConvert(Emitter em, + SimpleOpnd temp, String name, MpIntJitType to, Ext ext, + Scope scope) { + List> legs = new ArrayList<>(); + IntJitType typeLsl = to.legTypesLE().get(0); + var lsl = Opnd.needsIntExt(temp.type(), typeLsl) + ? em + .emit(temp::read) + .emit(IntToInt.INSTANCE::convertStackToStack, temp.type(), typeLsl, ext) + .emit(temp::write, scope) + : new SimpleOpndEm<>(temp, em); + legs.add(lsl.opnd()); + var sign = switch (ext) { + case ZERO -> new SimpleOpndEm<>(IntConstOpnd.ZERO_I4, em); + case SIGN -> lsl.em() + .emit(lsl.opnd()::read) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) + .emit(Opnd::createIntReadOnly, IntJitType.I4, "%s_convSign".formatted(name), + scope); + }; + for (int i = 1; i < to.legsAlloc(); i++) { + legs.add(sign.opnd()); + } + return new OpndEm<>(MpIntLocalOpnd.of(to, "%s_convMpInt".formatted(name), legs), + sign.em()); + } + + @Override + public > OpndEm + convertStackToOpnd(Emitter em, IntJitType from, String name, MpIntJitType to, + Ext ext, Scope scope) { + IntJitType typeLsl = to.legTypesLE().get(0); + var lsl = em + .emit(IntToInt.INSTANCE::convertStackToStack, from, typeLsl, ext) + .emit(IntLocalOpnd.temp(typeLsl, "%s_convLeg0".formatted(name), scope)::write, + scope); + return doConvert(lsl.em(), lsl.opnd(), name, to, ext, scope); + } + + @Override + public > Emitter>> + convertStackToArray(Emitter em, IntJitType from, String name, MpIntJitType to, + Ext ext, Scope scope, int slack) { + int legCount = to.legsAlloc(); + Local> arr = scope.decl(Types.T_INT_ARR, "%s_convArr".formatted(name)); + try (SubScope ss = scope.sub()) { + IntJitType typeLsl = to.legTypesLE().get(0); + Local lsl = ss.decl(typeLsl.bType(), "temp_lsl"); + var ckExt = em + .emit(IntToInt.INSTANCE::convertStackToStack, from, typeLsl, ext) + .emit(Op::istore, lsl) + .emit(Op::ldc__i, legCount + slack) + .emit(Op::newarray, Types.T_INT) + .emit(Op::astore, arr) + .emit(Op::aload, arr) + .emit(Op::ldc__i, 0) + .emit(Op::iload, lsl) + .emit(Op::iastore); + switch (ext) { + case ZERO -> { + } + case SIGN -> { + Local sign = ss.decl(Types.T_INT, "temp_sign"); + ckExt = ckExt + .emit(Op::iload, lsl) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) + .emit(Op::istore, sign); + for (int i = 1; i < legCount; i++) { + ckExt = ckExt + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(Op::iload, sign) + .emit(Op::iastore); + } + } + } + return ckExt + .emit(Op::aload, arr); + } + } + } + + /** + * Converter from int to float + */ + enum IntToFloat implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, IntJitType from, FloatJitType to, Ext ext) { + return em + .emit(Op::invokestatic, GenConsts.TR_FLOAT, "intBitsToFloat", + GenConsts.MDESC_FLOAT__INT_BITS_TO_FLOAT, false) + .step(Inv::takeArg) + .step(Inv::ret); + } + } + + /** + * Converter from int to double + */ + enum IntToDouble implements StackToStackConv { + INSTANCE; // In theory, should never happen, but if it does, truncate. + + @Override + public > Emitter> + convertStackToStack(Emitter em, IntJitType from, DoubleJitType to, Ext ext) { + return em + .emit(IntToLong.INSTANCE::convertStackToStack, from, LongJitType.I8, ext) + .emit(LongToDouble.INSTANCE::convertStackToStack, LongJitType.I8, to, ext); + } + } + + /** + * Converter from long to int + */ + enum LongToInt implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, LongJitType from, IntJitType to, Ext ext) { + return em + .emit(Op::l2i) + .emit(IntToInt.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + } + + /** + * Converter from long to long + */ + enum LongToLong implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, LongJitType from, LongJitType to, Ext ext) { + if (!needsLongExt(from, to)) { + return em.emit(Misc::cast1); + } + int shamt = Long.SIZE - to.size() * Byte.SIZE; + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__l, -1L >>> shamt) + .emit(Op::land); + case SIGN -> em + .emit(Op::ldc__i, shamt) + .emit(Op::lshl) + .emit(Op::ldc__i, shamt) + .emit(Op::lshr); + }; + } + } + + /** + * Converter from long to mp-int + */ + enum LongToMpInt implements StackToMpConv { + INSTANCE; + + @Override + public > OpndEm + convertStackToOpnd(Emitter em, LongJitType from, String name, MpIntJitType to, + Ext ext, Scope scope) { + var upperOnStack = em + .emit(Op::dup2__2) + .emit(Op::ldc__i, Integer.SIZE) + .emit(Opnd::lextshr, ext) + .emit(Op::l2i); + var sign = switch (ext) { + case ZERO -> new SimpleOpndEm<>(IntConstOpnd.ZERO_I4, upperOnStack); + case SIGN -> upperOnStack + .emit(Op::dup) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) + .emit(Opnd::createIntReadOnly, IntJitType.I4, "%s_convSign".formatted(name), + scope); + }; + upperOnStack = sign.em(); + + List> legs = new ArrayList<>(); + SimpleOpnd lower = + IntLocalOpnd.temp(IntJitType.I4, "%s_convLegLower".formatted(name), scope); + legs.add(lower); // We'll initialize it last + + var upper = sign.em() + .emit(IntLocalOpnd::create, IntJitType.I4, "%s_convLegUpper".formatted(name), + scope); + legs.add(upper.opnd()); + + for (int i = 2; i < to.legsAlloc(); i++) { + legs.add(sign.opnd()); + } + + return new OpndEm<>(MpIntLocalOpnd.of(to, "%s_convMpInt".formatted(name), legs), + upper.em() + .emit(Op::l2i) + .emit(lower::writeDirect)); + } + + @Override + public > Emitter>> + convertStackToArray(Emitter em, LongJitType from, String name, MpIntJitType to, + Ext ext, Scope scope, int slack) { + int legCount = to.legsAlloc(); + Local> arr = scope.decl(Types.T_INT_ARR, "%s_convArr".formatted(name)); + try (SubScope ss = scope.sub()) { + IntJitType typeLsl = to.legTypesLE().get(0); + IntJitType typeMsl = to.legTypesLE().get(1); + Local lsl = ss.decl(typeLsl.bType(), "temp_lsl"); + Local msl = ss.decl(typeMsl.bType(), "temp_msl"); + var ckExt = em + .emit(Op::dup2__2) + .emit(Op::l2i) + .emit(Op::istore, lsl) + + .emit(Op::ldc__i, Integer.SIZE) + .emit(Opnd::lextshr, ext) + .emit(Op::l2i) + .emit(Op::istore, msl) + + .emit(Op::ldc__i, legCount + slack) + .emit(Op::newarray, Types.T_INT) + .emit(Op::astore, arr) + + .emit(Op::aload, arr) + .emit(Op::ldc__i, 0) + .emit(Op::iload, lsl) + .emit(Op::iastore) + + .emit(Op::aload, arr) + .emit(Op::ldc__i, 1) + .emit(Op::iload, msl) + .emit(Op::iastore); + switch (ext) { + case ZERO -> { + } + case SIGN -> { + Local sign = ss.decl(Types.T_INT, "temp_sign"); + ckExt = ckExt + .emit(Op::iload, msl) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) + .emit(Op::istore, sign); + for (int i = 2; i < legCount; i++) { + ckExt = ckExt + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(Op::iload, sign) + .emit(Op::iastore); + } + } + } + return ckExt + .emit(Op::aload, arr); + } + } + } + + /** + * Converter from long to float + */ + enum LongToFloat implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, LongJitType from, FloatJitType to, Ext ext) { + return em + .emit(LongToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(IntToFloat.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + } + + /** + * Converter from long to double + */ + enum LongToDouble implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, LongJitType from, DoubleJitType to, Ext ext) { + if (to.size() != from.size()) { + throw new AssertionError("Size mismatch"); + } + return em + .emit(Op::invokestatic, GenConsts.TR_DOUBLE, "longBitsToDouble", + GenConsts.MDESC_DOUBLE__LONG_BITS_TO_DOUBLE, false) + .step(Inv::takeArg) + .step(Inv::ret); + } + } + + /** + * Converter from mp-int to (simple) int + */ + enum MpIntToInt implements MpToStackConv { + INSTANCE; + + @Override + public Emitter> convertOpndToStack(Emitter em, + Opnd from, IntJitType to, Ext ext) { + var lsl = from.type().castLegsLE(from).get(0); + return em + .emit(lsl::read) + .emit(IntToInt.INSTANCE::convertStackToStack, lsl.type(), to, ext); + } + + @Override + public >> Emitter> + convertArrayToStack(Emitter em, MpIntJitType from, IntJitType to, Ext ext) { + var typeLsl = from.legTypesLE().get(0); + return em + .emit(Op::ldc__i, 0) + .emit(Op::iaload) + .emit(IntToInt.INSTANCE::convertStackToStack, typeLsl, to, ext); + } + } + + /** + * Converter from mp-int to long + */ + enum MpIntToLong + implements MpToStackConv { + INSTANCE; + + @Override + public Emitter> convertOpndToStack(Emitter em, + Opnd from, LongJitType to, Ext ext) { + var legs = from.type().castLegsLE(from); + return em + .emit(legs.get(1)::read) + .emit(legs.get(0)::read) + .emit(Op::invokestatic, GenConsts.T_JIT_COMPILED_PASSAGE, "conv2IntToLong", + GenConsts.MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(LongToLong.INSTANCE::convertStackToStack, LongJitType.I8, to, ext); + } + + @Override + public >> Emitter> + convertArrayToStack(Emitter em, MpIntJitType from, LongJitType to, Ext ext) { + return em + .emit(Op::dup) + .emit(Op::ldc__i, 1) + .emit(Op::iaload) + .emit(Op::swap) + .emit(Op::ldc__i, 0) + .emit(Op::iaload) + .emit(Op::invokestatic, GenConsts.T_JIT_COMPILED_PASSAGE, "conv2IntToLong", + GenConsts.MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, true) + .step(Inv::takeArg) + .step(Inv::takeArg) + .step(Inv::ret) + .emit(LongToLong.INSTANCE::convertStackToStack, LongJitType.I8, to, ext); + } + } + + /** + * Converter from mp-int to mp-int + */ + enum MpIntToMpInt + implements MpToMpConv { + INSTANCE; + + @Override + public OpndEm convertOpndToOpnd(Emitter em, + Opnd from, MpIntJitType to, Ext ext, Scope scope) { + if (to.size() == from.type().size()) { + return new OpndEm<>(from, em); + } + List> fromLegs = from.type().castLegsLE(from); + List toLegTypes = to.legTypesLE(); + int legsIn = from.legsLE().size(); + int legsOut = to.legsAlloc(); + int defLegs = Integer.min(legsIn, legsOut); + + List> toLegs = new ArrayList<>(); + for (int i = 0; i < defLegs; i++) { + var curLeg = fromLegs.get(i); + toLegs.add(switch (curLeg) { + case IntReadOnlyLocalOpnd ro when ext == Ext.SIGN -> ro; + case IntConstOpnd c when ext == Ext.ZERO -> c; + default -> { + if (!needsIntExt(curLeg.type(), toLegTypes.get(i))) { + yield curLeg; + } + String name = "%s_convLeg%d".formatted(from.name(), i); + var result = em + .emit(curLeg::read) + .emit(Opnd::convertIntToInt, IntJitType.I4, curLeg.type(), ext) + .emit(Opnd::convertIntToInt, curLeg.type(), toLegTypes.get(i), ext) + .emit(Opnd::createInt, toLegTypes.get(i), name, scope); + em = result.em(); + yield result.opnd(); + } + }); + } + if (legsOut > defLegs) { + var sign = switch (ext) { + case ZERO -> new SimpleOpndEm<>(IntConstOpnd.ZERO_I4, em); + case SIGN -> switch (toLegs.getLast()) { + case IntConstOpnd c -> new SimpleOpndEm<>(c, em); + default -> em + .emit(toLegs.getLast()::read) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) // Signed + .emit(Opnd::createIntReadOnly, IntJitType.I4, + "%s_sign".formatted(from.name()), scope); + }; + }; + em = sign.em(); + for (int i = defLegs; i < legsOut; i++) { + toLegs.add(sign.opnd()); + } + } + return new OpndEm<>( + MpIntLocalOpnd.of(to, "%s_convMpInt".formatted(from.name()), toLegs), em); + } + + /** + * Emit code that extends a value to fill the rest of an array + *

    + * For sign extension, this will assume the last filled element of the array so far is the + * leg having the sign bit. It shifts and extends that bit to fill a new temporary leg and + * uses it to fill the remaining more-significant legs. NOTE: {@code legsOut} may be less + * than the actual size of the array, since slack elements may have been allocated. + * + * @param the tail of the stack (...) + * @param em the emitter + * @param arr a handle to the local containing the array + * @param legsOut the number of output legs + * @param defLegs the number of legs already filled + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary variables + * @return the emitter with ... + */ + public static Emitter doGenArrExt(Emitter em, Local> arr, + int legsOut, int defLegs, Ext ext, Scope scope) { + if (legsOut <= defLegs) { + return em; + } + return switch (ext) { + case ZERO -> em; // Uninitialized array elements are already 0 + case SIGN -> { + try (SubScope ss = scope.sub()) { + Local sign = ss.decl(Types.T_INT, "temp_sign"); + em = em + .emit(Op::aload, arr) + .emit(Op::ldc__i, defLegs - 1) + .emit(Op::iaload) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr) + .emit(Op::istore, sign); + for (int i = defLegs; i < legsOut; i++) { + em = em + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(Op::iload, sign) + .emit(Op::iastore); + } + yield em; + } + } + }; + } + + @Override + public Emitter>> convertOpndToArray(Emitter em, + Opnd from, MpIntJitType to, Ext ext, Scope scope, int slack) { + List> fromLegs = from.type().castLegsLE(from); + List toLegTypes = to.legTypesLE(); + int legsIn = from.type().legsAlloc(); + int legsOut = to.legsAlloc(); + int defLegs = Integer.min(legsIn, legsOut); + + Local> arr = + scope.decl(Types.T_INT_ARR, "%s_convArr".formatted(from.name())); + em = em + .emit(Op::ldc__i, legsOut + slack) + .emit(Op::newarray, Types.T_INT) + .emit(Op::astore, arr); + + for (int i = 0; i < defLegs; i++) { + SimpleOpnd fromLeg = fromLegs.get(i); + IntJitType toLegType = toLegTypes.get(i); + em = em + .emit(Op::aload, arr) + .emit(Op::ldc__i, i) + .emit(fromLeg::read) + .emit(Opnd::convertIntToInt, fromLeg.type(), toLegType, ext) + .emit(Op::iastore); + } + return em + .emit(MpIntToMpInt::doGenArrExt, arr, legsOut, defLegs, ext, scope) + .emit(Op::aload, arr); + } + } + + /** + * Converter from mp-int to (simple) float + */ + enum MpIntToFloat + implements MpToStackConv { + INSTANCE; + + @Override + public Emitter> convertOpndToStack(Emitter em, + Opnd from, FloatJitType to, Ext ext) { + return em + .emit(MpIntToInt.INSTANCE::convertOpndToStack, from, IntJitType.I4, ext) + .emit(IntToFloat.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + + @Override + public >> Emitter> + convertArrayToStack(Emitter em, MpIntJitType from, FloatJitType to, Ext ext) { + return em + .emit(MpIntToInt.INSTANCE::convertArrayToStack, from, IntJitType.I4, ext) + .emit(IntToFloat.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + } + + /** + * Converter from mp-int to double + */ + enum MpIntToDouble + implements MpToStackConv { + INSTANCE; + + @Override + public Emitter> convertOpndToStack(Emitter em, + Opnd from, DoubleJitType to, Ext ext) { + return em + .emit(MpIntToLong.INSTANCE::convertOpndToStack, from, LongJitType.I8, ext) + .emit(LongToDouble.INSTANCE::convertStackToStack, LongJitType.I8, to, ext); + } + + @Override + public >> Emitter> + convertArrayToStack(Emitter em, MpIntJitType from, DoubleJitType to, Ext ext) { + return em + .emit(MpIntToLong.INSTANCE::convertArrayToStack, from, LongJitType.I8, ext) + .emit(LongToDouble.INSTANCE::convertStackToStack, LongJitType.I8, to, ext); + } + } + + /** + * Converter from float to int + */ + enum FloatToInt implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, FloatJitType from, IntJitType to, Ext ext) { + if (to.size() != from.size()) { + throw new AssertionError("Size mismatch"); + } + return em + .emit(Op::invokestatic, GenConsts.TR_FLOAT, "floatToRawIntBits", + GenConsts.MDESC_FLOAT__FLOAT_TO_RAW_INT_BITS, false) + .step(Inv::takeArg) + .step(Inv::ret); + } + } + + /** + * Converter from float to long + */ + enum FloatToLong implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, FloatJitType from, LongJitType to, Ext ext) { + return em + .emit(FloatToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(IntToLong.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + } + + /** + * Converter from float to mp-int + */ + enum FloatToMpInt + implements StackToMpConv { + INSTANCE; + + @Override + public > OpndEm + convertStackToOpnd(Emitter em, FloatJitType from, String name, MpIntJitType to, + Ext ext, Scope scope) { + return em + .emit(FloatToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(IntToMpInt.INSTANCE::convertStackToOpnd, IntJitType.I4, name, to, ext, + scope); + } + + @Override + public > Emitter>> + convertStackToArray(Emitter em, FloatJitType from, String name, MpIntJitType to, + Ext ext, Scope scope, int slack) { + return em + .emit(FloatToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(IntToMpInt.INSTANCE::convertStackToArray, IntJitType.I4, name, to, ext, + scope, slack); + } + } + + /** + * Converter from float to float + */ + enum FloatToFloat implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, FloatJitType from, FloatJitType to, Ext ext) { + return em.emit(Misc::cast1); + } + } + + /** + * Converter from float to double + */ + enum FloatToDouble implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, FloatJitType from, DoubleJitType to, Ext ext) { + return em + .emit(FloatToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(IntToDouble.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + } + + /** + * Converter from double to int + */ + enum DoubleToInt implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, DoubleJitType from, IntJitType to, Ext ext) { + return em + .emit(DoubleToLong.INSTANCE::convertStackToStack, from, LongJitType.I8, ext) + .emit(LongToInt.INSTANCE::convertStackToStack, LongJitType.I8, to, ext); + } + } + + /** + * Converter from double to long + */ + enum DoubleToLong implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, DoubleJitType from, LongJitType to, Ext ext) { + if (to.size() != from.size()) { + throw new AssertionError("Size mismatch"); + } + return em + .emit(Op::invokestatic, GenConsts.TR_DOUBLE, "doubleToRawLongBits", + GenConsts.MDESC_DOUBLE__DOUBLE_TO_RAW_LONG_BITS, false) + .step(Inv::takeArg) + .step(Inv::ret); + } + } + + /** + * Converter from double to mp-int + */ + enum DoubleToMpInt + implements StackToMpConv { + INSTANCE; + + @Override + public > OpndEm + convertStackToOpnd(Emitter em, DoubleJitType from, String name, MpIntJitType to, + Ext ext, Scope scope) { + return em + .emit(DoubleToLong.INSTANCE::convertStackToStack, from, LongJitType.I8, ext) + .emit(LongToMpInt.INSTANCE::convertStackToOpnd, LongJitType.I8, name, to, ext, + scope); + } + + @Override + public > Emitter>> + convertStackToArray(Emitter em, DoubleJitType from, String name, + MpIntJitType to, Ext ext, Scope scope, int slack) { + return em + .emit(DoubleToLong.INSTANCE::convertStackToStack, from, LongJitType.I8, ext) + .emit(LongToMpInt.INSTANCE::convertStackToArray, LongJitType.I8, name, to, ext, + scope, slack); + } + } + + /** + * Converter from double to float + */ + enum DoubleToFloat implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, DoubleJitType from, FloatJitType to, Ext ext) { + return em + .emit(DoubleToInt.INSTANCE::convertStackToStack, from, IntJitType.I4, ext) + .emit(IntToFloat.INSTANCE::convertStackToStack, IntJitType.I4, to, ext); + } + } + + /** + * Converter from double to double + */ + enum DoubleToDouble + implements StackToStackConv { + INSTANCE; + + @Override + public > Emitter> + convertStackToStack(Emitter em, DoubleJitType from, DoubleJitType to, Ext ext) { + return em.emit(Misc::cast1); + } + } + + /** + * Create a constant int operand of the given type + * + * @param type the p-code type + * @param value the value + * @return the constant + */ + public static SimpleOpnd constOf(IntJitType type, int value) { + if (value == 0) { + return IntConstOpnd.zero(type); + } + return new IntConstOpnd(value, type); + } + + /** + * Create a constant long operand of the given type + * + * @param type the p-code type + * @param value the value + * @return the constant + */ + public static SimpleOpnd constOf(LongJitType type, long value) { + return new LongConstOpnd(value, type); + } + + /** + * Create a constant float operand of the given type + * + * @param type the p-code type + * @param value the value + * @return the constant + */ + public static SimpleOpnd constOf(FloatJitType type, float value) { + return new FloatConstOpnd(value, type); + } + + /** + * Create a constant double operand of the given type + * + * @param type the p-code type + * @param value the value + * @return the constant + */ + public static SimpleOpnd constOf(DoubleJitType type, double value) { + return new DoubleConstOpnd(value, type); + } + + /** + * Create a constant mp-int operand of the given type + * + * @param type the p-code type + * @param value the value + * @return the constant + */ + public static Opnd constOf(MpIntJitType type, BigInteger value) { + return new MpIntConstOpnd(value, type); + } + + /** + * Emit code to convert a simple int to a boolean + *

    + * This treats any non-zero value as true. Only zero is treated as false. The result is either 1 + * (true) or 0 (false). In other words, this converts any non-zero value to 1. Zero is left as + * 0. + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return ..., result + */ + public static > Emitter> intToBool( + Emitter em) { + var lblTrue = em + .emit(Op::ifne); + var lblDone = lblTrue.em() + .emit(Op::ldc__i, 0) + .emit(Op::goto_); + return lblDone.em() + .emit(Lbl::placeDead, lblTrue.lbl()) + .emit(Op::ldc__i, 1) + .emit(Lbl::place, lblDone.lbl()); + } + + /** + * Emit nothing, but cast the emitter by asserting two given p-code types are identical + *

    + * This is often used in switch statements where the cases are specific types. Likely the switch + * variable has a generic type. For a given case, we know that generic type is identical to a + * given p-code type, but to convince the Java compiler, we need to cast. This method provides a + * structured mechanism for that cast to prevent mistakes. Additionally, at runtime, if + * assertions are enabled, this will fail when the given types are not actually identical. + * + * @param the "to" JVM type + * @param the "to" p-code type + * @param the "from" JVM type + * @param the "from" p-code type + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the p-code type + * @param to the same p-code type, but with an apparently different type at compile time + * @return the emitter with ..., value (unchanged) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static < + TT extends BPrim, TJT extends SimpleJitType, + FT extends BPrim, FJT extends SimpleJitType, + N1 extends Next, N0 extends Ent> + Emitter> castStack1(Emitter em, FJT from, TJT to) { + assert from == to; + return (Emitter) em; + } + + /** + * Create an operand of the given p-code type from the value on the stack + * + * @param the JVM type + * @param the p-code type + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param type the p-code type + * @param name the name of the local variable to create + * @param scope a scope for the local variable + * @return the operand and emitter with ... + */ + public static < + T extends BPrim, JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> + SimpleOpndEm create(Emitter em, JT type, String name, Scope scope) { + return switch (type) { + case IntJitType t -> IntLocalOpnd.create(castStack1(em, type, t), t, name, scope) + .castBack(type); + case LongJitType t -> LongLocalOpnd.create(castStack1(em, type, t), t, name, scope) + .castBack(type); + case FloatJitType t -> FloatLocalOpnd.create(castStack1(em, type, t), t, name, scope) + .castBack(type); + case DoubleJitType t -> DoubleLocalOpnd.create(castStack1(em, type, t), t, name, scope) + .castBack(type); + default -> throw new AssertionError(); + }; + } + + /** + * Create an int operand from the value on the stack + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param type the p-code type + * @param name the name of the local variable to create + * @param scope a scope for the local variable + * @return the operand and emitter with ... + */ + public static > SimpleOpndEm + createInt(Emitter em, IntJitType type, String name, Scope scope) { + return IntLocalOpnd.create(em, type, name, scope); + } + + /** + * Create a read-only int operand from the value on the stack + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param type the p-code type + * @param name the name of the local variable to create + * @param scope a scope for the local variable + * @return the operand and emitter with ... + * @see SimpleOpnd#ofIntReadOnly(IntJitType, Local) + */ + public static > SimpleOpndEm + createIntReadOnly(Emitter em, IntJitType type, String name, Scope scope) { + return IntReadOnlyLocalOpnd.create(em, type, name, scope); + } + + /** + * Obtain the converter between two given simple types + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type + * @param the "to" p-code type + * @param from the source p-code type + * @param to the destination p-code type + * @return the converter + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + static < + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TJT extends SimpleJitType> + StackToStackConv getStackToStack(FJT from, TJT to) { + return (StackToStackConv) switch (from) { + case IntJitType ft -> switch (to) { + case IntJitType tt -> IntToInt.INSTANCE; + case LongJitType tt -> IntToLong.INSTANCE; + case FloatJitType tt -> IntToFloat.INSTANCE; + case DoubleJitType tt -> IntToDouble.INSTANCE; + default -> throw new AssertionError(); + }; + case LongJitType ft -> switch (to) { + case IntJitType tt -> LongToInt.INSTANCE; + case LongJitType tt -> LongToLong.INSTANCE; + case FloatJitType tt -> LongToFloat.INSTANCE; + case DoubleJitType tt -> LongToDouble.INSTANCE; + default -> throw new AssertionError(); + }; + case FloatJitType ft -> switch (to) { + case IntJitType tt -> FloatToInt.INSTANCE; + case LongJitType tt -> FloatToLong.INSTANCE; + case FloatJitType tt -> FloatToFloat.INSTANCE; + case DoubleJitType tt -> FloatToDouble.INSTANCE; + default -> throw new AssertionError(); + }; + case DoubleJitType ft -> switch (to) { + case IntJitType tt -> DoubleToInt.INSTANCE; + case LongJitType tt -> DoubleToLong.INSTANCE; + case FloatJitType tt -> DoubleToFloat.INSTANCE; + case DoubleJitType tt -> DoubleToDouble.INSTANCE; + default -> throw new AssertionError(); + }; + default -> throw new AssertionError(); + }; + } + + /** + * Convert from a given simple type to another simple type + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type + * @param the "to" p-code type + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + public static < + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TJT extends SimpleJitType, + N1 extends Next, N0 extends Ent> + Emitter> convert(Emitter em, FJT from, TJT to, Ext ext) { + return getStackToStack(from, to).convertStackToStack(em, from, to, ext); + } + + /** + * Convert from an int type to another int type + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + public static > Emitter> + convertIntToInt(Emitter em, IntJitType from, IntJitType to, Ext ext) { + return IntToInt.INSTANCE.convertStackToStack(em, from, to, ext); + } + + /** + * Obtain the converter from a simple type to an mp type + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type for each mp leg + * @param the "to" p-code type for each mp leg + * @param the "to" p-code type + * @param from the source p-code type + * @param to the destination p-code type + * @return the converter + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + static < + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TLT extends SimpleJitType, + TJT extends LeggedJitType> + StackToMpConv getStackToMp(FJT from, TJT to) { + return (StackToMpConv) switch (from) { + case IntJitType ft -> switch (to) { + case MpIntJitType tt -> IntToMpInt.INSTANCE; + default -> throw new AssertionError(); + }; + case LongJitType ft -> switch (to) { + case MpIntJitType tt -> LongToMpInt.INSTANCE; + default -> throw new AssertionError(); + }; + case FloatJitType ft -> switch (to) { + case MpIntJitType tt -> FloatToMpInt.INSTANCE; + default -> throw new AssertionError(); + }; + case DoubleJitType ft -> switch (to) { + case MpIntJitType tt -> DoubleToMpInt.INSTANCE; + default -> throw new AssertionError(); + }; + default -> throw new AssertionError(); + }; + } + + /** + * Convert from a simple type to an mp type in local variables + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type for each mp leg + * @param the "to" p-code type for each mp leg + * @param the "to" p-code type + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param name a name (prefix) for the mp-int + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated variables + * @return the resulting operand and emitter with ... + */ + public static < + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TLT extends SimpleJitType, + TJT extends LeggedJitType, + N1 extends Next, N0 extends Ent> + OpndEm + convertToOpnd(Emitter em, FJT from, String name, TJT to, Ext ext, Scope scope) { + return getStackToMp(from, to).convertStackToOpnd(em, from, name, to, ext, scope); + } + + /** + * Convert from a simple type to an mp type in an array + * + * @param the "from" JVM type + * @param the "from" p-code type + * @param the "to" JVM type for each mp leg + * @param the "to" p-code type for each mp leg + * @param the "to" p-code type + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param from the source p-code type + * @param name a name (prefix) for the mp-int + * @param to the destination p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated variables + * @param slack the number of extra (more significant) elements to allocate in the array + * @return the emitter with ..., arrayref + */ + public static < + FT extends BPrim, FJT extends SimpleJitType, + TT extends BPrim, TLT extends SimpleJitType, + TJT extends LeggedJitType, + N1 extends Next, N0 extends Ent> + Emitter>> convertToArray(Emitter em, FJT from, String name, + TJT to, Ext ext, Scope scope, int slack) { + return getStackToMp(from, to).convertStackToArray(em, from, name, to, ext, scope, slack); + } + + /** + * Kinds of extension + */ + enum Ext { + /** Zero extension */ + ZERO, + /** Sign extension */ + SIGN; + + /** + * Get the extension based on signedness + * + * @param signed true for signed, false for unsigned + * @return the kind of extension + */ + public static Ext forSigned(boolean signed) { + return signed ? SIGN : ZERO; + } + } + + /** + * {@return the p-code type} + */ + T type(); + + /** + * {@return the name} + */ + String name(); + + /** + * {@return the legs in little-endian order} + *

    + * For non-legged types, this returns the singleton list containing only this operand + */ + List> legsLE(); +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/SimpleOpnd.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/SimpleOpnd.java new file mode 100644 index 0000000000..b2d2f93889 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/opnd/SimpleOpnd.java @@ -0,0 +1,148 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.opnd; + +import java.util.List; + +import ghidra.pcode.emu.jit.analysis.JitType.*; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * An operand stored in a single JVM local variable + * + * @param the JVM type + * @param the p-code type + */ +public interface SimpleOpnd, JT extends SimpleJitType> extends Opnd { + + /** + * Create a simple local operand + * + * @param the JVM type + * @param the p-code type + * @param type the p-code type + * @param local the JVM local + * @return the operand + */ + @SuppressWarnings("unchecked") + static , JT extends SimpleJitType> SimpleOpnd of( + JT type, Local local) { + return (SimpleOpnd) switch (type) { + case IntJitType t -> IntLocalOpnd.of(t, (Local) local); + case LongJitType t -> LongLocalOpnd.of(t, (Local) local); + case FloatJitType t -> FloatLocalOpnd.of(t, (Local) local); + case DoubleJitType t -> DoubleLocalOpnd.of(t, (Local) local); + default -> throw new AssertionError(); + }; + } + + /** + * Create a read-only {@code int} local operand + *

    + * Multi-precision integer operators work by composing several locals into a single p-code + * variable. Some of these operators need temporary variables. To avoid generating tons of + * those, we generally allow the temporary locals to be mutated. However, the local variables + * allocated to hold p-code variables cannot be mutated until the full output value has been + * successfully computed. Furthermore, we certainly cannot mutate any input operand by mistake. + * Using a read-only local for input operands ensures this does not happen. An attempt to write + * to one of these will instead generate a new temporary local, assign the value to it, and + * return the new operand. An attempt to write directly to this operand will result in an + * exception being thrown at generation time. + * + * @param type the p-code type + * @param local the local handle + * @return the read-only operand + */ + static SimpleOpnd ofIntReadOnly(IntJitType type, Local local) { + return IntReadOnlyLocalOpnd.of(type, local); + } + + /** + * An operand-emitter tuple + * + * @param the JVM type of the operand + * @param the p-code type of the operand + * @param the emitter's stack + */ + record SimpleOpndEm, JT extends SimpleJitType, N extends Next>( + SimpleOpnd opnd, Emitter em) { + + /** + * Cast the operand safely between generic and concrete type + *

    + * The given types are checked for equality at runtime, if assertions are enabled + * + * @param the "to" JVM type + * @param the "to" p-code type + * @param to the destination p-code type + * @return this cast to the same type, but expressed generically + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public , TJT extends SimpleJitType> SimpleOpndEm + castBack(TJT to) { + assert to == this.opnd.type(); + return (SimpleOpndEm) this; + } + } + + /** + * Emit code to read the operand onto the stack + * + * @param the tail of the stack (...) + * @param em the emitter + * @return the emitter with ..., value + */ + Emitter> read(Emitter em); + + /** + * Emit code to write the operand from the stack + *

    + * This will generate a new operand if this operand is read-only. Callers must therefore be + * prepared to take the result in place of this operand. + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param scope a scope for generated temporary variables + * @return the resulting operand and emitter with ... + */ + default > SimpleOpndEm write(Emitter em, + Scope scope) { + return new SimpleOpndEm<>(this, writeDirect(em)); + } + + /** + * Emit code to write the operand, without generating a new operand + *

    + * This will throw an exception during generation if this operand is read-only. This should only + * be used when the caller is certain the operand can be written and when a scope is not + * available. + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ... + */ + > Emitter writeDirect(Emitter em); + + @Override + default List> legsLE() { + return List.of(this); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java index 3ca550fc95..9a7dd8a04f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/tgt/JitCompiledPassage.java @@ -1370,7 +1370,7 @@ public interface JitCompiledPassage { * @param amt the amt as in {@code val << amt}, in little-endian order * @return the value */ - static long intLeft(int val, int[] amt) { + static int intLeft(int val, int[] amt) { if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) { return 0; } @@ -1831,7 +1831,9 @@ public interface JitCompiledPassage { for (int j = sizeL - 1; j >= 0; j--) { r <<= Integer.SIZE; r += inL[j] & MASK_I2UL; - out[j] = (int) (r / inR); + if (out != null) { + out[j] = (int) (r / inR); + } r %= inR; inL[j] = 0; // So that the mp-int inL is truly the remainder } @@ -1953,7 +1955,9 @@ public interface JitCompiledPassage { carry >>>= Integer.SIZE; } } - out[j] = (int) qHat; // Completion of D5 + if (out != null) { + out[j] = (int) qHat; // Completion of D5 + } /** * D7 [Loop on j] @@ -1987,7 +1991,7 @@ public interface JitCompiledPassage { neg(inR, inR.length); } divide(out, inL, inR); - if (signL != signR) { + if (signL != signR && out != null) { neg(out, out.length); } if (signL) { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/DoubleReadGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/DoubleReadGen.java deleted file mode 100644 index 15b88602fe..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/DoubleReadGen.java +++ /dev/null @@ -1,49 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType; -import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for reading doubles - * - *

    - * This is accomplished by reading a long and then converting it. - */ -public enum DoubleReadGen implements TypedAccessGen { - /** The big-endian instance */ - BE(LongReadGen.BE), - /** The little-endian instance */ - LE(LongReadGen.LE); - - final LongReadGen longGen; - - private DoubleReadGen(LongReadGen longGen) { - this.longGen = longGen; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - longGen.generateCode(gen, vn, rv); - TypeConversions.generateLongToDouble(LongJitType.forSize(vn.getSize()), DoubleJitType.F8, - rv); - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/DoubleWriteGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/DoubleWriteGen.java deleted file mode 100644 index 04bc6ff920..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/DoubleWriteGen.java +++ /dev/null @@ -1,49 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType; -import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for writing doubles - * - *

    - * This is accomplished by converting to a long and then writing it. - */ -public enum DoubleWriteGen implements TypedAccessGen { - /** The big-endian instance */ - BE(LongWriteGen.BE), - /** The little-endian instance */ - LE(LongWriteGen.LE); - - final LongWriteGen longGen; - - private DoubleWriteGen(LongWriteGen longGen) { - this.longGen = longGen; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - TypeConversions.generateDoubleToLong(DoubleJitType.F8, LongJitType.forSize(vn.getSize()), - rv); - longGen.generateCode(gen, vn, rv); - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/ExportsLegAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/ExportsLegAccessGen.java deleted file mode 100644 index 2d035842a0..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/ExportsLegAccessGen.java +++ /dev/null @@ -1,63 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.BLOCK_SIZE; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.pcode.Varnode; - -/** - * A generator that exports part of its implementation for use in a {@link MpTypedAccessGen}. - * - *

    - * This really just avoids the re-creation of {@link Varnode} objects for each leg of a large - * varnode. The method instead takes the (space,offset,size) triple as well as the offset of the - * block containing its start. - */ -public interface ExportsLegAccessGen extends TypedAccessGen { - /** - * Emit code to access one JVM int, either a whole variable or one leg of a multi-precision int - * variable. - * - *

    - * Legs that span blocks are handled as in - * {@link #generateCode(JitCodeGenerator, Varnode, MethodVisitor)}. - * - * - * @param gen the code generator - * @param space the address space of the varnode - * @param block the block offset containing the varnode (or leg) - * @param off the offset of the varnode (or leg) - * @param size the size of the varnode in bytes (or leg) - * @param rv the method visitor - */ - void generateMpCodeLeg(JitCodeGenerator gen, AddressSpace space, long block, int off, - int size, MethodVisitor rv); - - @Override - default void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - AddressSpace space = vn.getAddress().getAddressSpace(); - long offset = vn.getOffset(); - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - int size = vn.getSize(); - generateMpCodeLeg(gen, space, block, off, size, rv); - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/FloatReadGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/FloatReadGen.java deleted file mode 100644 index 3e1ed83f63..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/FloatReadGen.java +++ /dev/null @@ -1,48 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType.FloatJitType; -import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for reading floats - * - *

    - * This is accomplished by reading an int and then converting it. - */ -public enum FloatReadGen implements TypedAccessGen { - /** The big-endian instance */ - BE(IntReadGen.BE), - /** The little-endian instance */ - LE(IntReadGen.LE); - - final IntReadGen intGen; - - private FloatReadGen(IntReadGen intGen) { - this.intGen = intGen; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - intGen.generateCode(gen, vn, rv); - TypeConversions.generateIntToFloat(IntJitType.forSize(vn.getSize()), FloatJitType.F4, rv); - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/FloatWriteGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/FloatWriteGen.java deleted file mode 100644 index 7ac955615b..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/FloatWriteGen.java +++ /dev/null @@ -1,48 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType.FloatJitType; -import ghidra.pcode.emu.jit.analysis.JitType.IntJitType; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for writing floats - * - *

    - * This is accomplished by converting to an int and then writing it. - */ -public enum FloatWriteGen implements TypedAccessGen { - /** The big-endian instance */ - BE(IntWriteGen.BE), - /** The little-endian instance */ - LE(IntWriteGen.LE); - - final IntWriteGen intGen; - - private FloatWriteGen(IntWriteGen intGen) { - this.intGen = intGen; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - TypeConversions.generateFloatToInt(FloatJitType.F4, IntJitType.forSize(vn.getSize()), rv); - intGen.generateCode(gen, vn, rv); - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/IntReadGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/IntReadGen.java deleted file mode 100644 index 744dc92199..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/IntReadGen.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.FieldForArrDirect; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; - -/** - * The generator for reading integers. - */ -public enum IntReadGen implements MethodAccessGen, ExportsLegAccessGen { - /** The big-endian instance */ - BE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "readInt1"; - case 2 -> "readIntBE2"; - case 3 -> "readIntBE3"; - case 4 -> "readIntBE4"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateMpCodeLeg(JitCodeGenerator gen, AddressSpace space, long block, - int off, int size, MethodVisitor rv) { - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - rv.visitLdcInsn(off + size - BLOCK_SIZE); - rv.visitInsn(ISHL); - - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - rv.visitInsn(IOR); - } - }, - /** The little-endian instance */ - LE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "readInt1"; - case 2 -> "readIntLE2"; - case 3 -> "readIntLE3"; - case 4 -> "readIntLE4"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateMpCodeLeg(JitCodeGenerator gen, AddressSpace space, long block, - int off, int size, MethodVisitor rv) { - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - rv.visitLdcInsn(BLOCK_SIZE - off); - rv.visitInsn(ISHL); - - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__READ_INTX, true); - rv.visitInsn(IOR); - } - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/IntWriteGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/IntWriteGen.java deleted file mode 100644 index 56563a3c57..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/IntWriteGen.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.FieldForArrDirect; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; - -/** - * The generator for writing integers. - */ -public enum IntWriteGen implements MethodAccessGen, ExportsLegAccessGen { - /** The big-endian instance */ - BE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "writeInt1"; - case 2 -> "writeIntBE2"; - case 3 -> "writeIntBE3"; - case 4 -> "writeIntBE4"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateMpCodeLeg(JitCodeGenerator gen, AddressSpace space, long block, - int off, int size, MethodVisitor rv) { - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - rv.visitInsn(DUP); - rv.visitLdcInsn(off + size - BLOCK_SIZE); - rv.visitInsn(IUSHR); - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - } - }, - /** The little-endian instance */ - LE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "writeInt1"; - case 2 -> "writeIntLE2"; - case 3 -> "writeIntLE3"; - case 4 -> "writeIntLE4"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateMpCodeLeg(JitCodeGenerator gen, AddressSpace space, long block, - int off, int size, MethodVisitor rv) { - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - rv.visitInsn(DUP); - rv.visitLdcInsn(BLOCK_SIZE - off); - rv.visitInsn(IUSHR); - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX, true); - } - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/LongReadGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/LongReadGen.java deleted file mode 100644 index 12a486949b..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/LongReadGen.java +++ /dev/null @@ -1,132 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.FieldForArrDirect; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for reading longs. - */ -public enum LongReadGen implements MethodAccessGen { - /** The big-endian instance */ - BE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "readLong1"; - case 2 -> "readLongBE2"; - case 3 -> "readLongBE3"; - case 4 -> "readLongBE4"; - case 5 -> "readLongBE5"; - case 6 -> "readLongBE6"; - case 7 -> "readLongBE7"; - case 8 -> "readLongBE8"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - long offset = vn.getOffset(); - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - int size = vn.getSize(); - AddressSpace space = vn.getAddress().getAddressSpace(); - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - rv.visitLdcInsn(off + size - BLOCK_SIZE); - rv.visitInsn(LSHL); - - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - rv.visitInsn(LOR); - } - }, - /** The little-endian instance */ - LE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "readLong1"; - case 2 -> "readLongLE2"; - case 3 -> "readLongLE3"; - case 4 -> "readLongLE4"; - case 5 -> "readLongLE5"; - case 6 -> "readLongLE6"; - case 7 -> "readLongLE7"; - case 8 -> "readLongLE8"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - long offset = vn.getOffset(); - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - int size = vn.getSize(); - AddressSpace space = vn.getAddress().getAddressSpace(); - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - rv.visitLdcInsn(BLOCK_SIZE - off); - rv.visitInsn(LSHL); - - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__READ_LONGX, true); - rv.visitInsn(LOR); - } - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/LongWriteGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/LongWriteGen.java deleted file mode 100644 index d912ffe264..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/LongWriteGen.java +++ /dev/null @@ -1,132 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import static org.objectweb.asm.Opcodes.*; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.FieldForArrDirect; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.pcode.Varnode; - -/** - * Bytes writer for longs in big endian order. - */ -public enum LongWriteGen implements MethodAccessGen { - /** The big-endian instance */ - BE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "writeLong1"; - case 2 -> "writeLongBE2"; - case 3 -> "writeLongBE3"; - case 4 -> "writeLongBE4"; - case 5 -> "writeLongBE5"; - case 6 -> "writeLongBE6"; - case 7 -> "writeLongBE7"; - case 8 -> "writeLongBE8"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - long offset = vn.getOffset(); - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - int size = vn.getSize(); - AddressSpace space = vn.getAddress().getAddressSpace(); - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - rv.visitInsn(DUP2); - rv.visitInsn(off + size - BLOCK_SIZE); - rv.visitInsn(LUSHR); - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - } - }, - /** The little-endian instance */ - LE { - @Override - public String chooseName(int size) { - return switch (size) { - case 1 -> "writeLong1"; - case 2 -> "writeLongLE2"; - case 3 -> "writeLongLE3"; - case 4 -> "writeLongLE4"; - case 5 -> "writeLongLE5"; - case 6 -> "writeLongLE6"; - case 7 -> "writeLongLE7"; - case 8 -> "writeLongLE8"; - default -> throw new AssertionError(); - }; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - long offset = vn.getOffset(); - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - int size = vn.getSize(); - AddressSpace space = vn.getAddress().getAddressSpace(); - FieldForArrDirect blkField = gen.requestFieldForArrDirect(space.getAddress(block)); - if (off + size <= BLOCK_SIZE) { - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, chooseName(size), - MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - return; - } - - FieldForArrDirect nxtField = - gen.requestFieldForArrDirect(space.getAddress(block + BLOCK_SIZE)); - rv.visitInsn(DUP2); - rv.visitLdcInsn(BLOCK_SIZE - off); - rv.visitInsn(LUSHR); - nxtField.generateLoadCode(gen, rv); - rv.visitLdcInsn(0); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(off + size - BLOCK_SIZE), MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - - blkField.generateLoadCode(gen, rv); - rv.visitLdcInsn(off); - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, - chooseName(BLOCK_SIZE - off), - MDESC_JIT_COMPILED_PASSAGE__WRITE_LONGX, true); - } - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpIntReadGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpIntReadGen.java deleted file mode 100644 index dc4b744f0f..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpIntReadGen.java +++ /dev/null @@ -1,91 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.BLOCK_SIZE; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for reading multi-precision ints. - */ -public enum MpIntReadGen implements MpTypedAccessGen { - /** The big-endian instance */ - BE { - @Override - public IntReadGen getLegGen() { - return IntReadGen.BE; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - ExportsLegAccessGen legGen = getLegGen(); - - AddressSpace space = vn.getAddress().getAddressSpace(); - int countFull = vn.getSize() / Integer.BYTES; - int remSize = vn.getSize() % Integer.BYTES; - long offset = vn.getOffset(); - if (remSize > 0) { - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, remSize, rv); - offset += remSize; - } - for (int i = 0; i < countFull; i++) { - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, Integer.BYTES, rv); - offset += Integer.BYTES; - } - } - }, - /** The little-endian instance */ - LE { - @Override - public IntReadGen getLegGen() { - return IntReadGen.LE; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - ExportsLegAccessGen legGen = getLegGen(); - - AddressSpace space = vn.getAddress().getAddressSpace(); - int countFull = vn.getSize() / Integer.BYTES; - int remSize = vn.getSize() % Integer.BYTES; - long offset = vn.getOffset() + vn.getSize(); - if (remSize > 0) { - offset -= remSize; - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, remSize, rv); - } - for (int i = 0; i < countFull; i++) { - offset -= Integer.BYTES; - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, Integer.BYTES, rv); - } - } - }; - - @Override - public abstract IntReadGen getLegGen(); -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpIntWriteGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpIntWriteGen.java deleted file mode 100644 index 3bb76b84d1..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpIntWriteGen.java +++ /dev/null @@ -1,93 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.BLOCK_SIZE; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.pcode.Varnode; - -/** - * The generator for writing multi-precision ints. - */ -public enum MpIntWriteGen implements MpTypedAccessGen { - /** The big-endian instance */ - BE { - @Override - public IntWriteGen getLegGen() { - return IntWriteGen.BE; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - ExportsLegAccessGen legGen = getLegGen(); - - // Stack semantics, so must work in reverse of read - AddressSpace space = vn.getAddress().getAddressSpace(); - int countFull = vn.getSize() / Integer.BYTES; - int remSize = vn.getSize() % Integer.BYTES; - long offset = vn.getOffset() + vn.getSize(); - for (int i = 0; i < countFull; i++) { - offset -= Integer.BYTES; - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, Integer.BYTES, rv); - } - if (remSize > 0) { - offset -= remSize; - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, remSize, rv); - } - } - }, - /** The little-endian instance */ - LE { - @Override - public IntWriteGen getLegGen() { - return IntWriteGen.LE; - } - - @Override - public void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv) { - ExportsLegAccessGen legGen = getLegGen(); - - // Stack semantics, so must work in reverse of read - AddressSpace space = vn.getAddress().getAddressSpace(); - int countFull = vn.getSize() / Integer.BYTES; - int remSize = vn.getSize() % Integer.BYTES; - long offset = vn.getOffset(); - for (int i = 0; i < countFull; i++) { - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, Integer.BYTES, rv); - offset += Integer.BYTES; - } - if (remSize > 0) { - long block = offset / BLOCK_SIZE * BLOCK_SIZE; - int off = (int) (offset - block); - legGen.generateMpCodeLeg(gen, space, block, off, remSize, rv); - offset += remSize; - } - } - }; - - @Override - public abstract IntWriteGen getLegGen(); -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpTypedAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpTypedAccessGen.java deleted file mode 100644 index 64ba908239..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/MpTypedAccessGen.java +++ /dev/null @@ -1,55 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.pcode.Varnode; - -/** - * A generator for a multi-precision integer type. - * - *

    - * This depends on the generator for single integer types. Each will need to work out how to compose - * the leg generator given the stack ordering, byte order, and read/write operation. - */ -public interface MpTypedAccessGen extends TypedAccessGen { - /** - * Get a generator for individual legs of this multi-precision access generator - * - * @return the leg generator - */ - ExportsLegAccessGen getLegGen(); - - /** - * {@inheritDoc} - * - *

    - * This uses several JVM stack entries. The varnode must be too large to fit in a single JVM - * primitive, or else it does not require "multi-precision" handling. A leg that spans blocks is - * handled as in - * {@link ExportsLegAccessGen#generateMpCodeLeg(JitCodeGenerator, AddressSpace, long, int, int, MethodVisitor)}. - * The legs are ordered on the stack such that the least significant portion is on top. - * - * @param gen the code generator - * @param vn the varnode - * @param rv the method visitor - */ - @Override - void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv); -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java deleted file mode 100644 index 61129df9a7..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypeConversions.java +++ /dev/null @@ -1,902 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import static ghidra.pcode.emu.jit.gen.GenConsts.*; - -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - -import ghidra.lifecycle.Unfinished; -import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; -import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc; -import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.op.BinOpGen; -import ghidra.pcode.emu.jit.op.JitBinOp; -import ghidra.program.model.pcode.PcodeOp; - -/** - * The generator for various type conversion. - * - *

    - * These conversions are no more than bitwise casts. The underlying bits are unchanged, but the - * interpretation and/or the way the JVM has tagged them does. - * - *

    - * Many of the methods (and also many other bits of the code generator) follow a convention where - * the input type(s) are passed as parameter(s), and the resulting type is returned. In many cases - * the desired type is also taken as a parameter. Upon success, we'd expect that desired type to be - * the exact type returned, but this may not always be the case. This convention ensures all pieces - * of the generator know the p-code type (and thus JVM type) of the variable at the top of the JVM - * stack. - * - *

    - * Type conversions are applicable at a few boundaries: - *

      - *
    • To ensure use-def values conform to the requirements of the operands where they are used and - * defined. The {@link JitTypeModel} aims to reduce the number of conversions required by assigning - * appropriate types to the use-def value nodes, but this will not necessarily eliminate them - * all.
    • - *
    • Within the implementation of an operator, type conversions may be necessary to ensure the - * p-code types of input operands conform with the JVM types required by the emitted bytecodes, and - * that the output JVM type conforms to the p-code type of the output operand.
    • - *
    • When loading or storing as bytes from the {@link JitBytesPcodeExecutorState state}. The - * conversion from and to bytes is done using JVM integral types, and so the value may be converted - * if the operand requires a floating-point type.
    • - *
    - */ -public interface TypeConversions extends Opcodes { - - /** - * Kinds of extension - */ - enum Ext { - /** Zero extension */ - ZERO, - /** Sign extension */ - SIGN; - - public static Ext forSigned(boolean signed) { - return signed ? SIGN : ZERO; - } - } - - /** - * Emit an {@link Opcodes#IAND} to reduce the number of bits to those permitted in an int of the - * given size. - * - *

    - * For example to mask from an {@link IntJitType#I4 int4} to an {@link IntJitType#I2}, this - * would emit {@code iand 0xffff}. If the source size is smaller than or equal to that of the - * destination, nothing is emitted. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - */ - static void checkGenIntExt(JitType from, IntJitType to, Ext ext, MethodVisitor mv) { - if (to.size() < from.size() && to.size() < Integer.BYTES) { - int shamt = Integer.SIZE - to.size() * Byte.SIZE; - switch (ext) { - case ZERO -> { - mv.visitLdcInsn(-1 >>> shamt); - mv.visitInsn(IAND); - } - case SIGN -> { - mv.visitLdcInsn(shamt); - mv.visitInsn(ISHL); - mv.visitLdcInsn(shamt); - mv.visitInsn(ISHR); - } - } - } - } - - /** - * Emit bytecode to convert one p-code in (in a JVM int) to another - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static IntJitType generateIntToInt(IntJitType from, IntJitType to, Ext ext, MethodVisitor mv) { - checkGenIntExt(from, to, ext, mv); - return to; - } - - /** - * Emit bytecode to convert one p-code int (in a JVM long) to one in a JVM int. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static IntJitType generateLongToInt(LongJitType from, IntJitType to, Ext ext, - MethodVisitor mv) { - mv.visitInsn(L2I); - checkGenIntExt(from, to, ext, mv); - return to; - } - - /** - * Emit bytecode to convert a {@link FloatJitType#F4 float4} to an {@link IntJitType#I4 int4}. - * - * @param from the source type (must be {@link FloatJitType#F4 float4}) - * @param to the destination type (must be {@link IntJitType#I4 int4}) - * @param mv the method visitor - * @return the destination type ({@link IntJitType#I4 int4}) - */ - static IntJitType generateFloatToInt(FloatJitType from, IntJitType to, MethodVisitor mv) { - if (to.size() != from.size()) { - throw new AssertionError("Size mismatch"); - } - mv.visitMethodInsn(INVOKESTATIC, NAME_FLOAT, "floatToRawIntBits", - MDESC_FLOAT__FLOAT_TO_RAW_INT_BITS, false); - return to; - } - - /** - * Emit bytecode to convert a mult-precision int to a p-code int that fits in a JVM int. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static IntJitType generateMpIntToInt(MpIntJitType from, IntJitType to, Ext ext, - MethodVisitor mv) { - if (to.size() == from.size()) { - // We're done. The one leg on the stack becomes the int - return to; - } - // Remove all but the least-significant leg - for (int i = 1; i < from.legsAlloc(); i++) { - // [...,legN-1,legN] - mv.visitInsn(SWAP); - // [...,legN,legN-1] - mv.visitInsn(POP); - // [...,legN] - } - checkGenIntExt(from, to, ext, mv); - return to; - } - - /** - * Emit bytecode to convert any (compatible) type to a p-code int that fits in a JVM int. - * - *

    - * The only acceptable floating-point source type is {@link FloatJitType#F4 float4}. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static IntJitType generateToInt(JitType from, IntJitType to, Ext ext, MethodVisitor mv) { - return switch (from) { - case IntJitType iFrom -> generateIntToInt(iFrom, to, ext, mv); // in case of ext - case LongJitType lFrom -> generateLongToInt(lFrom, to, ext, mv); - case FloatJitType fFrom -> generateFloatToInt(fFrom, to, mv); - case DoubleJitType dFrom -> throw new AssertionError("Size mismatch"); - case MpIntJitType mpFrom -> generateMpIntToInt(mpFrom, to, ext, mv); - default -> throw new AssertionError(); - }; - } - - /** - * Emit an {@link Opcodes#LAND} to reduce the number of bits to those permitted in an int of the - * given size. - * - *

    - * For example to mask from a {@link LongJitType#I8 int8} to a {@link LongJitType#I6}, this - * would emit {@code land 0x0ffffffffffffL}. If the source size is smaller than or equal to that - * of the destination, nothing is emitted. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - */ - static void checkGenLongExt(JitType from, LongJitType to, Ext ext, MethodVisitor mv) { - if (to.size() < from.size()) { - int shamt = Long.SIZE - to.size() * Byte.SIZE; - switch (ext) { - case ZERO -> { - mv.visitLdcInsn(-1L >>> shamt); - mv.visitInsn(LAND); - } - case SIGN -> { - mv.visitLdcInsn(shamt); - mv.visitInsn(DUP); - mv.visitInsn(LSHL); - mv.visitInsn(LSHR); - } - } - } - } - - /** - * Emit bytecode to convert one p-code int (in a JVM int) to one in a JVM long. - * - *

    - * Care must be taken to ensure conversions to larger types extend with zeros (unsigned). - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static LongJitType generateIntToLong(IntJitType from, LongJitType to, Ext ext, - MethodVisitor mv) { - switch (ext) { - case ZERO -> mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "toUnsignedLong", - MDESC_INTEGER__TO_UNSIGNED_LONG, false); - case SIGN -> { - generateSExt(from, mv); - mv.visitInsn(I2L); - } - } - // In theory, never necessary, unless long is used temporarily with size 1-4. - checkGenLongExt(from, to, ext, mv); - return to; - } - - /** - * Emit bytecode to convert one p-code int (in a JVM long) to another - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static LongJitType generateLongToLong(LongJitType from, LongJitType to, Ext ext, - MethodVisitor mv) { - checkGenLongExt(from, to, ext, mv); - return to; - } - - /** - * Emit bytecode to convert a {@link DoubleJitType#F8 float8} to an {@link LongJitType#I8 int8}. - * - * @param from the source type (must be {@link DoubleJitType#F8 float8}) - * @param to the destination type (must be {@link LongJitType#I8 int8}) - * @param mv the method visitor - * @return the destination type ({@link LongJitType#I8 int8}) - */ - static LongJitType generateDoubleToLong(DoubleJitType from, LongJitType to, MethodVisitor mv) { - if (to.size() != from.size()) { - throw new AssertionError("Size mismatch"); - } - mv.visitMethodInsn(INVOKESTATIC, NAME_DOUBLE, "doubleToRawLongBits", - MDESC_DOUBLE__DOUBLE_TO_RAW_LONG_BITS, false); - return to; - } - - /** - * Emit bytecode to convert a mult-precision int to a p-code int that fits in a JVM long. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static LongJitType generateMpIntToLong(MpIntJitType from, LongJitType to, Ext ext, - MethodVisitor mv) { - if (from.legsAlloc() == 1) { - generateIntToLong(IntJitType.forSize(from.size()), to, ext, mv); - return to; - } - // Remove all but the 2 least-significant legs - for (int i = 2; i < from.legsAlloc(); i++) { - // [...,legN-2,legN-1,legN] - mv.visitInsn(DUP2_X1); - // [...,legN-1,legN,legN-2,legN-1,legN] - mv.visitInsn(POP2); - // [...,legN-1,legN,legN-2] - mv.visitInsn(POP); - // [...,legN-1,legN] - } - mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "conv2IntToLong", - MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, true); - return to; - } - - /** - * Emit bytecode to convert any (compatible) type to a p-code that fits in a JVM long. - * - *

    - * The only acceptable floating-point source type is {@link DoubleJitType#F8 float8}. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static LongJitType generateToLong(JitType from, LongJitType to, Ext ext, MethodVisitor mv) { - return switch (from) { - case IntJitType iFrom -> generateIntToLong(iFrom, to, ext, mv); - case LongJitType lFrom -> generateLongToLong(lFrom, to, ext, mv); // in case of mask - case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); - case DoubleJitType dFrom -> generateDoubleToLong(dFrom, to, mv); - case MpIntJitType mpFrom -> generateMpIntToLong(mpFrom, to, ext, mv); - default -> throw new AssertionError(); - }; - } - - /** - * Emit bytecode to convert an {@link IntJitType#I4 int4} to a {@link FloatJitType#F4 float4}. - * - * @param from the source type (must be {@link IntJitType#I4 int4}) - * @param to the destination type (must be {@link FloatJitType#F4 float4}) - * @param mv the method visitor - * @return the destination type ({@link FloatJitType#F4 float4}) - */ - static FloatJitType generateIntToFloat(IntJitType from, FloatJitType to, MethodVisitor mv) { - if (to.size() != from.size()) { - throw new AssertionError("Size mismatch"); - } - mv.visitMethodInsn(INVOKESTATIC, NAME_FLOAT, "intBitsToFloat", - MDESC_FLOAT__INT_BITS_TO_FLOAT, false); - return to; - } - - /** - * Emit bytecode to convert any (compatible) type to a {@link FloatJitType#F4 float4}. - * - * @param from the source type ({@link IntJitType#I4 int4} or {@link FloatJitType#F4 float4}) - * @param to the destination type - * @param mv the method visitor - * @return the destination type ({@link FloatJitType#F4 float4}) - */ - static FloatJitType generateToFloat(JitType from, FloatJitType to, MethodVisitor mv) { - return switch (from) { - case IntJitType iFrom -> generateIntToFloat(iFrom, to, mv); - case LongJitType lFrom -> throw new AssertionError("Size mismatch"); - case FloatJitType fFrom -> to; - case DoubleJitType dFrom -> throw new AssertionError("Size mismatch"); - case MpIntJitType mpFrom -> throw new AssertionError("Size mismatch"); - default -> throw new AssertionError(); - }; - } - - /** - * Emit bytecode to convert an {@link LongJitType#I8 int8} to a {@link DoubleJitType#F8 float8}. - * - * @param from the source type (must be {@link LongJitType#I8 int8}) - * @param to the destination type (must be {@link DoubleJitType#F8 float8}) - * @param mv the method visitor - * @return the destination type ({@link DoubleJitType#F8 float8}) - */ - static DoubleJitType generateLongToDouble(LongJitType from, DoubleJitType to, - MethodVisitor mv) { - if (to.size() != from.size()) { - throw new AssertionError("Size mismatch"); - } - mv.visitMethodInsn(INVOKESTATIC, NAME_DOUBLE, "longBitsToDouble", - MDESC_DOUBLE__LONG_BITS_TO_DOUBLE, false); - return to; - } - - /** - * Emit bytecode to convert any (compatible) type to a {@link DoubleJitType#F8 float8}. - * - * @param from the source type ({@link LongJitType#I8 int8} or {@link DoubleJitType#F8 float8}) - * @param to the destination type - * @param mv the method visitor - * @return the destination type ({@link DoubleJitType#F8 float8}) - */ - static DoubleJitType generateToDouble(JitType from, DoubleJitType to, MethodVisitor mv) { - return switch (from) { - case IntJitType iFrom -> throw new AssertionError("Size mismatch"); - case LongJitType lFrom -> generateLongToDouble(lFrom, to, mv); - case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); - case DoubleJitType dFrom -> to; - case MpIntJitType mpFrom -> throw new AssertionError("Size mismatch"); - default -> throw new AssertionError(); - }; - } - - /** - * Emit bytecode to convert a p-code int that fits in a JVM int to a multi-precision int. - * - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static MpIntJitType generateIntToMpInt(IntJitType from, MpIntJitType to, Ext ext, - MethodVisitor mv) { - if (to.legsAlloc() == 1) { - checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv); - return to; - } - // Insert as many more significant legs as needed - // First, figure out what those additional legs should be - // [lsl:INT] - switch (ext) { - case ZERO -> mv.visitLdcInsn(0); // [0:I,lsl:I] - case SIGN -> { - mv.visitInsn(DUP); - // [lsl:INT,lsl:INT] - mv.visitLdcInsn(Integer.SIZE - 1); - // [31:INT,lsl:INT,lsl:INT] - mv.visitInsn(ISHR); - // [sign:INT,lsl:INT] - } - } - // NB. Because "from" is the least-significant leg, I can just do this repeatedly. - // Do two! less: - // Start at 1, because the lsl is already present. - // End 1 before total, because last op will be SWAP instead. - for (int i = 1; i < to.legsAlloc() - 1; i++) { - mv.visitInsn(DUP_X1); - // [sign:INT,lsl:INT,sign:INT,...] - } - mv.visitInsn(SWAP); - // [lsl:INT,sign:INT,sign:INT,...] - return to; - } - - /** - * Emit bytecode to convert a p-code int that is in a JVM long to multi-precision int. - * - * @param gen the code generator - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static MpIntJitType generateLongToMpInt(JitCodeGenerator gen, LongJitType from, MpIntJitType to, - Ext ext, MethodVisitor mv) { - if (to.legsAlloc() == 1) { - mv.visitInsn(L2I); - checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv); - return to; - } - if (from.size() <= Integer.BYTES) { - mv.visitInsn(L2I); - generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv); - return to; - } - // Convert, then insert as many more significant legs as needed - - /** Can't just invoke a static method, because two ints have to result */ - // [val:LONG] - mv.visitInsn(DUP2); - // [val:LONG,val:LONG] - mv.visitLdcInsn(Integer.SIZE); - mv.visitInsn(LUSHR); - mv.visitInsn(L2I); - - /** This is the upper leg, which may need extending */ - if (to.size() < Long.BYTES) { - checkGenIntExt(IntJitType.forSize(from.size() - Integer.BYTES), - IntJitType.forSize(to.size() - Integer.BYTES), ext, mv); - } - - int tempCount = switch (ext) { - case ZERO -> 0; - case SIGN -> 1; - }; - try (JvmTempAlloc sign = gen.getAllocationModel().allocateTemp(mv, "sign", tempCount)) { - switch (ext) { - case ZERO -> { - } - case SIGN -> { - mv.visitInsn(DUP); - mv.visitLdcInsn(Integer.SIZE - 1); - mv.visitInsn(ISHR); - mv.visitVarInsn(ISTORE, sign.idx(0)); - } - } - - // [val:LONG,msl:INT] - mv.visitInsn(DUP_X2); - // [msl:INT,val:LONG,msl:INT] - mv.visitInsn(POP); - // [msl:INT,val:LONG] - mv.visitInsn(L2I); - // [msl:INT,lsl:INT] - - // Now add legs - if (to.legsAlloc() > 2) { - switch (ext) { - case ZERO -> mv.visitLdcInsn(0); - case SIGN -> mv.visitVarInsn(ILOAD, sign.idx(0)); - } - // [msl:INT,lsl:INT,sign:INT] - for (int i = 2; i < to.legsAlloc(); i++) { - // [msl:INT,lsl:INT,sign:INT] - mv.visitInsn(DUP_X2); - // [sign:INT,msl:INT,lsl:INT,sign:INT] - } - // [...,sign:INT,msl:INT,lsl:INT,sign:INT] - mv.visitInsn(POP); - // [...,sign:INT,msl:INT,lsl:INT] - } - } - return to; - } - - /** - * Emit bytecode to convert a mult-precision int from one size to another - * - * @param gen the code generator - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static MpIntJitType generateMpIntToMpInt(JitCodeGenerator gen, MpIntJitType from, - MpIntJitType to, Ext ext, MethodVisitor mv) { - if (to.size() == from.size()) { - // Nothing to convert - return to; - } - // Some special cases to avoid use of local variables: - if (to.legsAlloc() == 1) { - generateMpIntToInt(from, IntJitType.forSize(to.size()), ext, mv); - return to; - } - if (from.legsAlloc() == 1) { - generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv); - return to; - } - - // Okay, now it's complicated - int legsIn = from.legsAlloc(); - int legsOut = to.legsAlloc(); - int localsCount = Integer.min(legsIn, legsOut); - - try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", localsCount)) { - for (int i = 0; i < localsCount; i++) { - mv.visitVarInsn(ISTORE, temp.idx(i)); - } - - // Add or remove legs - int toAdd = legsOut - legsIn; - if (toAdd >= 1) { - switch (ext) { - case ZERO -> mv.visitLdcInsn(0); - case SIGN -> { - mv.visitVarInsn(ILOAD, temp.idx(localsCount - 1)); - mv.visitLdcInsn(Integer.SIZE - 1); - mv.visitInsn(ISHR); - } - } - } - for (int i = 1; i < toAdd; i++) { - mv.visitInsn(DUP); - } - int toRemove = -toAdd; - for (int i = 0; i < toRemove; i++) { - mv.visitInsn(POP); - } - - // Start pushing them back, but the most significant may need extending - mv.visitVarInsn(ILOAD, temp.idx(localsCount - 1)); - if (to.size() < from.size()) { - checkGenIntExt( - from, // already checked size, so anything greater - IntJitType.forSize(to.partialSize()), ext, mv); - } - // push the rest back - for (int i = 1; i < localsCount; i++) { - mv.visitVarInsn(ILOAD, temp.idx(localsCount - i - 1)); - } - } - return to; - } - - /** - * Emit bytecode to convert any (compatible) type to a p-code int that fits in a JVM int. - * - *

    - * No floating-point source types are currently acceptable. Support for floats of size other - * than 4 and 8 bytes is a work in progress. - * - * @param gen the code generator - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the destination type - */ - static MpIntJitType generateToMpInt(JitCodeGenerator gen, JitType from, MpIntJitType to, - Ext ext, MethodVisitor mv) { - return switch (from) { - case IntJitType iFrom -> generateIntToMpInt(iFrom, to, ext, mv); - case LongJitType lFrom -> generateLongToMpInt(gen, lFrom, to, ext, mv); - case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); - case DoubleJitType dFrom -> throw new AssertionError("Size mismatch"); - case MpIntJitType mpFrom -> generateMpIntToMpInt(gen, mpFrom, to, ext, mv); - default -> throw new AssertionError(); - }; - } - - /** - * Emit bytecode to convert the value on top of the JVM stack from one p-code type to another. - * - *

    - * If the source and destination are already of the same type, or if conversion between them - * does not require any bytecode, then no bytecode is emitted. - * - * @param gen the code generator - * @param from the source type - * @param to the destination type - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the resulting (destination) type - */ - static JitType generate(JitCodeGenerator gen, JitType from, JitType to, Ext ext, - MethodVisitor mv) { - return switch (to) { - case IntJitType iTo -> generateToInt(from, iTo, ext, mv); - case LongJitType lTo -> generateToLong(from, lTo, ext, mv); - case FloatJitType fTo -> generateToFloat(from, fTo, mv); - case DoubleJitType dTo -> generateToDouble(from, dTo, mv); - case MpIntJitType mpTo -> generateToMpInt(gen, from, mpTo, ext, mv); - default -> throw new AssertionError(); - }; - } - - /** - * Collapse an mp-int or long to a single int. - * - *

    - * If and only if the input is all zeros will the output also be all zeros. Otherwise, the - * output can be any non-zero value. - * - *

    - * There is no explicit "{@code boolean}" p-code type. Instead, like C, many of the operators - * take an {@link JitTypeBehavior#INTEGER int} type and require "false" to be represented by the - * value 0. Any non-zero value is interpreted as "true." That said, conventionally, all p-code - * booleans ought to be an {@link IntJitType#I1 int1} where "true" is represented by 1 and - * "false" is represented by 0. The p-code operators that output "boolean" values are all - * implemented to follow this convention, except that size is determined by the Slaspec author. - * - *

    - * This conversion deals with input operands used as booleans that do not conform to these - * conventions. If, e.g., a {@link PcodeOp#CBRANCH cbranch} is given a condition operand of type - * {@link LongJitType#I8 int8}, we have to ensure that all bits, not just the lower 32, are - * considered. This is trivially accomplished by pushing {@code 0L} and emitting an - * {@link #LCMP}, which consumes the JVM long and replaces it with a JVM int representing the - * same boolean value. For multi-precision ints, we reduce all the legs using {@link #IOR}. If a - * float is used as a boolean, it must be converted to an int first. - * - * @param from the type of the value currently on the stack - * @param mv the method visitor - * @see #generateLdcFalse(JitType, MethodVisitor) - * @see #generateLdcTrue(JitType, MethodVisitor) - */ - static void generateIntToBool(JitType from, MethodVisitor mv) { - switch (from) { - case IntJitType iFrom -> { - } - case LongJitType lFrom -> { - mv.visitLdcInsn(0L); - mv.visitInsn(LCMP); - } - case MpIntJitType(int size) -> { - for (int i = 0; i < size - Integer.BYTES; i += Integer.BYTES) { - mv.visitInsn(IOR); - } - } - default -> throw new AssertionError(); - } - } - - /** - * Remove a value of the given type from the JVM stack. - * - *

    - * Depending on the type, we must emit either {@link #POP} or {@link #POP2}. This is used to - * ignore an input or drop an output. For example, the boolean operators may short circuit - * examination of the second operand, in which case it must be popped. Also, if a userop returns - * a value, but the p-code does not provide an output operand, the return value must be popped. - * - * @param type the type - * @param mv the method visitor - */ - static void generatePop(JitType type, MethodVisitor mv) { - switch (type) { - case IntJitType iType -> mv.visitInsn(POP); - case LongJitType lType -> mv.visitInsn(POP2); - case FloatJitType fType -> mv.visitInsn(POP); - case DoubleJitType dType -> mv.visitInsn(POP2); - case MpIntJitType(int size) -> { - for (int i = 0; i < size; i += Integer.BYTES) { - mv.visitInsn(POP); - } - } - case MpFloatJitType(int size) -> Unfinished.TODO("MpFloat"); - default -> throw new AssertionError(); - } - } - - /** - * Generate a "boolean" true value of the given type - * - *

    - * This performs the inverse of {@link #generateIntToBool(JitType, MethodVisitor)}, but for the - * constant "true." Instead of loading a constant 1 into an {@link IntJitType#I1 int1} and then - * converting to the desired type, this can just load the constant 1 directly as the desired - * type. - * - *

    - * This is often used with conditional jumps to produce a boolean output. - * - * @param type an integer type - * @param mv the method visitor - * @see #generateLdcFalse(JitType, MethodVisitor) - * @see #generateIntToBool(JitType, MethodVisitor) - */ - static void generateLdcTrue(JitType type, MethodVisitor mv) { - switch (type) { - case IntJitType iType -> mv.visitLdcInsn(1); - case LongJitType lType -> mv.visitLdcInsn(1L); - case MpIntJitType(int size) -> { - for (int i = 0; i < size - Integer.BYTES; i += Integer.BYTES) { - mv.visitLdcInsn(0); - } - mv.visitLdcInsn(1); - } - default -> throw new AssertionError(); - } - } - - /** - * Generate a "boolean" false value of the given type - * - *

    - * This performs the inverse of {@link #generateIntToBool(JitType, MethodVisitor)}, but for the - * constant "false." Instead of loading a constant 0 into an {@link IntJitType#I1 int1} and then - * converting to the desired type, this can just load the constant 0 directly as the desired - * type. - * - *

    - * This is often used with conditional jumps to produce a boolean output. - * - * @param type an integer type - * @param mv the method visitor - * @see #generateLdcFalse(JitType, MethodVisitor) - * @see #generateIntToBool(JitType, MethodVisitor) - */ - static void generateLdcFalse(JitType type, MethodVisitor mv) { - switch (type) { - case IntJitType iType -> mv.visitLdcInsn(0); - case LongJitType lType -> mv.visitLdcInsn(0L); - case MpIntJitType(int size) -> { - for (int i = 0; i < size; i += Integer.BYTES) { - mv.visitLdcInsn(0); - } - } - default -> throw new AssertionError(); - } - } - - /** - * Emit code to extend a signed value of the given type to fill its host JVM type. - * - * @param type the p-code type - * @param mv the method visitor - * @return the p-code type that exactly fits the host JVM type, i.e., the resulting p-code type. - */ - static JitType generateSExt(JitType type, MethodVisitor mv) { - switch (type) { - case IntJitType(int size) -> { - int shamt = Integer.SIZE - size * Byte.SIZE; - if (shamt != 0) { - mv.visitLdcInsn(shamt); - mv.visitInsn(ISHL); - mv.visitLdcInsn(shamt); - mv.visitInsn(ISHR); - } - } - case LongJitType(int size) -> { - int shamt = Long.SIZE - size * Byte.SIZE; - if (shamt != 0) { - mv.visitLdcInsn(shamt); - mv.visitInsn(LSHL); - mv.visitLdcInsn(shamt); - mv.visitInsn(LSHR); - } - } - default -> throw new AssertionError(); - } - return type.ext(); - } - - /** - * Select the larger of two types and emit code to convert an unsigned value of the first type - * to the host JVM type of the selected type. - * - *

    - * JVM bytecodes for binary operators often require that both operands have the same size. - * Consider that the JVM provides a {@link #IADD} and a {@link #LADD}, but no "{@code ILADD}". - * Both operands must be JVM ints, or both must be JVM longs. This method provides an idiom for - * converting both operands to the same type. Ideally, we choose the smallest type possible (as - * opposed to just converting everything to long always), but we must choose a type large enough - * to accommodate the larger of the two p-code operands. - * - *

    - * For a binary operator requiring type uniformity, we must apply this method immediately after - * loading each operand onto the stack. That operand's type is passed as {@code myType} and the - * type of the other operand as {@code otherType}. Consider the left operand. We must override - * {@link BinOpGen#afterLeft(JitCodeGenerator, JitBinOp, JitType, JitType, MethodVisitor) - * afterLeft} if we're using {@link BinOpGen}. If the left type is the larger, then we select it - * and we need only extend the left operand to fill its host JVM type. (We'll deal with the - * right operand in a moment.) If the right type is larger, then we select it and we extend the - * left to fill the right's host JVM type. We then return the resulting left type so - * that we'll know what it was when emitting the actual operator bytecodes. Things work - * similarly for the right operand, which we handle within - * {@link BinOpGen#generateBinOpRunCode(JitCodeGenerator, JitBinOp, JitBlock, JitType, JitType, MethodVisitor) - * generateBinOpRunCode} if we're using it. The two resulting types should now be equal, and we - * can examine them and emit the correct bytecodes. - * - * @param gen the code generator - * @param myType the type of an operand, probably in a binary operator - * @param otherType the type of the other operand of a binary operator - * @param ext whether the extension is signed or not - * @param mv the method visitor - * @return the new type of the operand - */ - static JitType forceUniform(JitCodeGenerator gen, JitType myType, JitType otherType, - Ext ext, MethodVisitor mv) { - // TODO: Why was .ext() being used here (inconsistently, too) - return switch (myType) { - case IntJitType mt -> switch (otherType) { - case IntJitType ot -> mt; - case LongJitType ot -> generateIntToLong(mt, ot, ext, mv); - // FIXME: Would be nice to allow non-uniform mp-int sizes - case MpIntJitType ot -> generateIntToMpInt(mt, ot, ext, mv); - default -> throw new AssertionError(); - }; - case LongJitType mt -> switch (otherType) { - case IntJitType ot -> mt; // Other operand needs up-conversion - case LongJitType ot -> mt; - // FIXME: Would be nice to allow non-uniform mp-int sizes - case MpIntJitType ot -> generateLongToMpInt(gen, mt, ot, ext, mv); - default -> throw new AssertionError(); - }; - // FIXME: Would be nice to allow non-uniform mp-int sizes - case MpIntJitType mt -> switch (otherType) { - case IntJitType ot -> mt; // Other operand needs up-conversion - case LongJitType ot -> mt; // Other operand needs up-conversion - case MpIntJitType ot -> generateMpIntToMpInt(gen, mt, ot, ext, mv); - default -> throw new AssertionError(); - }; - default -> throw new AssertionError(); - }; - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypedAccessGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypedAccessGen.java deleted file mode 100644 index 93ebfa1873..0000000000 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/type/TypedAccessGen.java +++ /dev/null @@ -1,115 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen.type; - -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.op.JitLoadOp; -import ghidra.pcode.emu.jit.op.JitStoreOp; -import ghidra.program.model.lang.Endian; -import ghidra.program.model.pcode.Varnode; - -/** - * A generator to emit code that accesses variables of various size in a - * {@link JitBytesPcodeExecutorState state}, for a specific type, byte order, and access. - * - *

    - * This is used by variable birthing and retirement as well as direct memory accesses. Dynamic - * memory accesses, i.e., {@link JitLoadOp load} and {@link JitStoreOp store} op do not use this, - * though they may borrow some portions. - */ -public interface TypedAccessGen { - - /** - * Lookup the generator for reading variables for the given type - * - * @param endian the byte order - * @param type the variable's type - * @return the access generator - */ - public static TypedAccessGen lookupReader(Endian endian, JitType type) { - return switch (endian) { - case BIG -> switch (type) { - case IntJitType t -> IntReadGen.BE; - case LongJitType t -> LongReadGen.BE; - case FloatJitType t -> FloatReadGen.BE; - case DoubleJitType t -> DoubleReadGen.BE; - case MpIntJitType t -> MpIntReadGen.BE; - default -> throw new AssertionError(); - }; - case LITTLE -> switch (type) { - case IntJitType t -> IntReadGen.LE; - case LongJitType t -> LongReadGen.LE; - case FloatJitType t -> FloatReadGen.LE; - case DoubleJitType t -> DoubleReadGen.LE; - case MpIntJitType t -> MpIntReadGen.LE; - default -> throw new AssertionError(); - }; - }; - } - - /** - * Lookup the generator for writing variables for the given type - * - * @param endian the byte order - * @param type the variable's type - * @return the access generator - */ - public static TypedAccessGen lookupWriter(Endian endian, JitType type) { - return switch (endian) { - case BIG -> switch (type) { - case IntJitType t -> IntWriteGen.BE; - case LongJitType t -> LongWriteGen.BE; - case FloatJitType t -> FloatWriteGen.BE; - case DoubleJitType t -> DoubleWriteGen.BE; - case MpIntJitType t -> MpIntWriteGen.BE; - default -> throw new AssertionError(); - }; - case LITTLE -> switch (type) { - case IntJitType t -> IntWriteGen.LE; - case LongJitType t -> LongWriteGen.LE; - case FloatJitType t -> FloatWriteGen.LE; - case DoubleJitType t -> DoubleWriteGen.LE; - case MpIntJitType t -> MpIntWriteGen.LE; - default -> throw new AssertionError(); - }; - }; - } - - /** - * Emit code to access a varnode. - * - *

    - * If reading, the result is placed on the JVM stack. If writing, the value is popped from the - * JVM stack. - * - *

    - * If the varnode fits completely in the block (the common case), then this accesses the bytes - * from the one block, using the method chosen by size. If the varnode extends into the next - * block, then this will split the varnode into two portions according to machine byte order. - * Each portion is accessed using the method for the size of that portion. If reading, the - * results are reassembled into a single value and pushed onto the JVM stack. - * - * @param gen the code generator - * @param vn the varnode - * @param rv the method visitor - */ - void generateCode(JitCodeGenerator gen, Varnode vn, MethodVisitor rv); -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Check.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Check.java new file mode 100644 index 0000000000..988e03ea0f --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Check.java @@ -0,0 +1,44 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +/** + * Utility for explicitly checking the stack at a given point in a bytecode sequence. + */ +public interface Check { + /** + * Explicitly check the stack at this point in the bytecode sequence + *

    + * This is meant to be used with chosen type parameters, e.g.: + * + *

    +	 * return em
    +	 * 		.emit(Op::ldc__i, 42)
    +	 * 		.emit(Check::<Ent<Bot, TInt>> expect);
    +	 * 
    + *

    + * Granted, that's not a particularly complicated case warranting such a check, it demonstrates + * the idiom for placing the check. These are often only in place while the sequence is devised, + * and then removed. + * + * @param the expected stack + * @param em the emitter typed with the expected stack + * @return the same emitter + */ + static Emitter expect(Emitter em) { + return em; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/ChildScope.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/ChildScope.java new file mode 100644 index 0000000000..98a22449f1 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/ChildScope.java @@ -0,0 +1,39 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; + +/** + * The implementation of a child scope for local variable declarations + * + * @param the stack at scope start and finish (not really enforced) + */ +class ChildScope extends RootScope implements SubScope { + protected final RootScope parentScope; + + ChildScope(Emitter em, RootScope parentScope) { + super(em, parentScope.nextLocal); + this.parentScope = parentScope; + parentScope.childScope = this; + } + + @Override + public void close() { + super.close(); + parentScope.childScope = null; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Emitter.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Emitter.java new file mode 100644 index 0000000000..bc46359b81 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Emitter.java @@ -0,0 +1,648 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import java.lang.System.Logger; +import java.util.ArrayList; +import java.util.function.BiFunction; +import java.util.function.Function; + +import org.objectweb.asm.*; + +import ghidra.pcode.emu.jit.gen.util.Methods.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * The central object for emitting type checked JVM bytecode. + *

    + * This is either genius or a sign of some deep pathology. On one hand it allows the type-safe + * generation of bytecode in Java classfiles. On the other, it requires an often onerous type + * signature on any method of appreciable sophistication that uses it. The justification for this + * utility library stems from our difficulties with error reporting in the ASM library. We certainly + * appreciate the effort that has gone into that library, and must recognize its success in that it + * has been used by the OpenJDK itself and eventually prompted them to devise an official classfile + * API. Nevertheless, its analyses (e.g., max-stack computation) fail with inscrutable messages. + * Admittedly, this only happens when we have generated invalid bytecode. For example, popping too + * many items off the stack usually results in an {@link ArrayIndexOutOfBoundsException} instead of, + * "Hey, you can't pop that here: [offset]". Similarly, if you push a long and then pop an int, you + * typically get a {@link NullPointerException}. Unfortunately, these errors do not occur with the + * offending {@code visitXInstruction()} call on the stack, but instead during + * {@link MethodVisitor#visitMaxs(int, int)}, and so we could not easily debug and identify the + * cause. We did find some ways to place breakpoints and at least derive the bytecode offset. We + * then used additional dumps and instrumentation to map that back to our source that generated the + * offending instruction. This has been an extremely onerous process. Additionally, when refactoring + * bytecode generation, we are left with little if any assistance from the compiler or IDE. These + * utilities seek to improve the situation. + *

    + * Our goal is to devise a way leverage Java's Generics and its type checker to enforce stack + * consistency of generated JVM bytecode. We want the Java compiler to reject code that tries, for + * example, to emit an {@code iload} followed by an {@code lstore}, because there is clearly an + * {@code int} on the stack where a {@code long} is required. We accomplish this by encoding the + * stack contents (or at least the local knowledge of the stack contents) in this emitter's type + * variable {@code }. We encode the types of stack entries using a Lisp-style list. The bottom of + * the stack is encoded as {@link Bot}. A list is encoded with {@link Ent} where the first type + * parameter is the tail of the list (for things further down the stack), and the second type + * parameter encodes the JVM machine type, e.g., {@link TInt}, of the element at that position. The + * head of this list, i.e., the type {@code }, is the top of the stack. + *

    + * The resulting syntax for emitting code is a bit strange, but still quite effective in practice. A + * problem we encounter in Java (and most OOP languages to our knowledge) is that an instance method + * can always be invoked on a variable, no matter the variable's type parameters. Sure, we can + * always throw an exception at runtime, but we want the compiler to reject it, which implies static + * checking. Thus, while instance methods can be used for pure pushes, we cannot use them to + * validate stack contents, e.g., for pops. Suppose we'd like to specify the {@code lcmp} bytecode + * op. This would require a {@link TLong long} at the top of the stack, but there's no way we can + * restrict {@code } on the implied {@code this} parameter. Nor is there an obvious way to unpack + * the contents of {@code } so that we can remove the {@link TLong} and add a {@link TInt}. + * Instead, we must turn to static methods. + *

    + * This presents a different problem. We'd like to provide a syntax where the ops appear in the + * order they are emitted. Usually, we'd chain instance methods, like such: + * + *

    + * em
    + * 		.ldc(1)
    + * 		.pop();
    + * 
    + *

    + * However, we've already ruled out instance methods. Were we to use static methods, we'd get + * something like: + * + *

    + * Op.pop(Op.ldc(em, 1));
    + * 
    + * + *

    + * However, that fails to display the ops in order. We could instead use: + * + *

    + * var em1 = Op.ldc(em, 1);
    + * var em2 = Op.pop(em1);
    + * 
    + * + * However, that requires more syntactic kruft, not to mention the manual bookkeeping to ensure we + * use the previous {@code em}n at each step. To work around this, we define instance + * methods, e.g., {@link #emit(Function)}, that can accept references to static methods we provide, + * each representing a JVM bytecode instruction. This allows those static methods to impose a + * required structure on the stack. The static method can then return an emitter with a type + * encoding the new stack contents. (See the {@link Op} class for examples.) Thus, we have a syntax + * like: + * + *
    + * em
    + * 		.emit(Op::ldc__i, 1)
    + * 		.emit(Op::pop);
    + * 
    + *

    + * While not ideal, it is succinct, allows method chaining, and displays the ops in order of + * emission. (Note that we use this pattern even for pure pushes, where restricting {@code } is + * not necessary, just for syntactic consistency.) There are some rubs for operators that have + * different forms, e.g., {@link Op#ldc__i(Emitter, int)}, but as a matter of opinion, having to + * specify the intended form here is a benefit. The meat of this class is just the specification of + * the many arities of {@code emit}. It also includes some utilities for declaring local variables, + * and the entry points for generating and defining methods. + *

    + * To give an overall taste of using this utility library, here is an example for dynamically + * generating a class that implements an interface. Note that the interface is not + * dynamically generated. This is a common pattern as it allows the generated method to be invoked + * without reflection. + * + *

    + * interface MyIf {
    + * 	int myMethod(int a, String b);
    + * }
    + * 
    + * <THIS extends MyIf> void doGenerate(ClassVisitor cv) {
    + * 	var mdescMyMethod = MthDesc.derive(MyIf::myMethod)
    + * 			.check(MthDesc::returns, Types.T_INT)
    + * 			.check(MthDesc::param, Types.T_INT)
    + * 			.check(MthDesc::param, Types.refOf(String.class))
    + * 			.check(MthDesc::build);
    + * 	TRef<THIS> typeThis = Types.refExtends(MyIf.class, "Lmy.pkg.ImplMyIf;");
    + * 	var paramsMyMethod = new Object() {
    + * 		Local<TRef<THIS>> this_;
    + * 		Local<TInt> a;
    + * 		Local<TRef<String>> b;
    + * 	};
    + * 	var retMyMethod = Emitter.start(typeThis, cv, ACC_PUBLIC, "myMethod", mdescMyMethod)
    + * 			.param(Def::param, Types.refOf(String.class), l -> paramsMyMethod.b = l)
    + * 			.param(Def::param, Types.T_INT, l -> paramsMyMethod.a = l)
    + * 			.param(Def::done, typeThis, l -> paramsMyMethod.this_ = l);
    + * 	retMyMethod.em()
    + * 			.emit(Op::iload, paramsMyMethod.a)
    + * 			.emit(Op::ldc__i, 10)
    + * 			.emit(Op::imul)
    + * 			.emit(Op::ireturn, retMyMethod.ret())
    + * 			.emit(Misc::finish);
    + * }
    + * 
    + *

    + * Yes, there is a bit of repetition; however, this accomplishes all our goals and a little more. + * Note that the generated bytecode is essentially type checked all the way through to the method + * definition in the {@code MyIf} interface. Here is the key: We were to change the {@code MyIf} + * interface, the compiler (and our IDE) would point out the inconsistency. The first such + * errors would be on {@code mdescMyMethod}. So, we would adjust it to match the new definition. The + * compiler would then point out issues at {@code retMyMethod} -- assuming the parameters to + * {@code myMethod} changed, and not just the return type. We would adjust it, along with the + * contents of {@code paramsMyMethod} to accept the new parameter handles. If the return type of + * {@code myMethod} changed, then the inferred type of {@code retMyMethod} will change accordingly. + *

    + * Now for the generated bytecode. The {@link Op#iload(Emitter, Local)} requires the given variable + * handle to have type {@link TInt}, and so if the parameter "{@code a}" changed type, the compiler + * will point out that the opcode must also change. Similarly, the {@link Op#imul(Emitter)} requires + * two ints and pushes an int result, so any resulting inconsistency will be caught. Finally, when + * calling {@link Op#ireturn(Emitter, RetReq)}, two things are checked: 1) there is indeed an int on + * the stack, and 2) the return type of the method, witnessed by {@code retMyMethod.ret()}, is also + * an int. There are some occasional wrinkles, but for the most part, once we resolve all the + * compilation errors, we are assured of type consistency in the generated code, both internally and + * in its interface to other compiled code. + * + * @param the contents of the stack after having emitted all the previous bytecodes + */ +public class Emitter { + static final Logger LOGGER = System.getLogger("Emitter"); + + /** The wrapped ASM method visitor */ + final MethodVisitor mv; + /** The root scope of local declarations */ + final Scope rootScope; + + /** + * Create a new emitter by wrapping the given method visitor. + *

    + * Direct use of this constructor is not recommended, but is useful during transition from + * unchecked to checked bytecode generation. + * + * @param mv the ASM method visitor + */ + public Emitter(MethodVisitor mv) { + this.mv = mv; + rootScope = new RootScope<>(this, 0); + } + + /** + * Stack contents + * + *

    + * There is really only one instance of {@link Next} and that is {@link SingletonEnt#INSTANCE}. + * We just cast it to the various types. Otherwise, these interfaces just exist as a means of + * leveraging Java's type checker. + */ + public interface Next { + /** + * The bottom of the stack + */ + Bot BOTTOM = SingletonEnt.INSTANCE; + } + + /** + * An entry on the stack + * + * @param the tail (portions below) of the stack + * @param the top entry of this stack (or portion) + */ + public interface Ent extends Next { + } + + /** + * The bottom of the stack, i.e., the empty stack + */ + public interface Bot extends Next { + } + + /** + * Use in place of stack contents when code emitted at this point would be unreachable + *

    + * Note that this does not extend {@link Next}, which is why {@link Emitter} does not require + * {@code N} to extend {@link Next}. This interface also has no implementation. + */ + public interface Dead { + } + + /** + * Defines the singleton instance of {@link Next} + * + * @param the tail + * @param the top entry + */ + private record SingletonEnt() implements Ent, Bot { + private static final SingletonEnt INSTANCE = new SingletonEnt<>(); + } + + /** + * Get the root scope for declaring local variables + * + * @return the root scope + */ + public Scope rootScope() { + return rootScope; + } + + /** + * Emit a 0-argument operator + *

    + * This can also be used to invoke generator subroutines whose only argument is the emitter. + * + * @param the return type + * @param func the method reference, e.g., {@link Op#pop(Emitter)}. + * @return the value returned by {@code func} + */ + public R emit(Function, R> func) { + return func.apply(this); + } + + /** + * Emit a 1-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference, e.g., {@link Op#ldc__i(Emitter, int)}. + * @param arg1 the argument (other than the emitter) to pass to {@code func} + * @return the value returned by {@code func} + */ + public R emit(BiFunction, A1, R> func, A1 arg1) { + return func.apply(this, arg1); + } + + /** + * A 3-argument function + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the return type + */ + public interface A3Function { + /** + * Invoke the function + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @return the result + */ + R apply(A0 arg0, A1 arg1, A2 arg2); + } + + /** + * A 3-argument consumer + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + */ + public interface A3Consumer { + /** + * Invoke the consumer + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + */ + void accept(A0 arg0, A1 arg1, A2 arg2); + } + + /** + * Emit a 2-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference + * @param arg1 an argument (other than the emitter) to pass to {@code func} + * @param arg2 the next argument + * @return the value returned by {@code func} + */ + public R emit(A3Function, A1, A2, R> func, A1 arg1, A2 arg2) { + return func.apply(this, arg1, arg2); + } + + /** + * A 4-argument function + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the return type + */ + public interface A4Function { + /** + * Invoke the function + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @param arg3 the next argument + * @return the result + */ + R apply(A0 arg0, A1 arg1, A2 arg2, A3 arg3); + } + + /** + * A 4-argument consumer + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + */ + public interface A4Consumer { + /** + * Invoke the consumer + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @param arg3 the next argument + */ + void accept(A0 arg0, A1 arg1, A2 arg2, A3 arg3); + } + + /** + * Emit a 3-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference + * @param arg1 an argument (other than the emitter) to pass to {@code func} + * @param arg2 the next argument + * @param arg3 the next argument + * @return the value returned by {@code func} + */ + public R emit(A4Function, A1, A2, A3, R> func, A1 arg1, A2 arg2, + A3 arg3) { + return func.apply(this, arg1, arg2, arg3); + } + + /** + * A 5-argument function + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the return type + */ + public interface A5Function { + /** + * Invoke the function + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @return the result + */ + R apply(A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4); + } + + /** + * Emit a 4-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference + * @param arg1 an argument (other than the emitter) to pass to {@code func} + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @return the value returned by {@code func} + */ + public R emit(A5Function, A1, A2, A3, A4, R> func, + A1 arg1, A2 arg2, A3 arg3, A4 arg4) { + return func.apply(this, arg1, arg2, arg3, arg4); + } + + /** + * A 6-argument function + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the return type + */ + public interface A6Function { + /** + * Invoke the function + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @param arg5 the next argument + * @return the result + */ + R apply(A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5); + } + + /** + * Emit a 5-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference + * @param arg1 an argument (other than the emitter) to pass to {@code func} + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @param arg5 the next argument + * @return the value returned by {@code func} + */ + public R emit( + A6Function, A1, A2, A3, A4, A5, R> func, A1 arg1, A2 arg2, + A3 arg3, A4 arg4, A5 arg5) { + return func.apply(this, arg1, arg2, arg3, arg4, arg5); + } + + /** + * A 7-argument function + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the return type + */ + public interface A7Function { + /** + * Invoke the function + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @param arg5 the next argument + * @param arg6 the next argument + * @return the result + */ + R apply(A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5, A6 arg6); + } + + /** + * Emit a 6-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference + * @param arg1 an argument (other than the emitter) to pass to {@code func} + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @param arg5 the next argument + * @param arg6 the next argument + * @return the value returned by {@code func} + */ + public R emit( + A7Function, A1, A2, A3, A4, A5, A6, R> func, A1 arg1, + A2 arg2, A3 arg3, A4 arg4, A5 arg5, A6 arg6) { + return func.apply(this, arg1, arg2, arg3, arg4, arg5, arg6); + } + + /** + * A 7-argument function + * + * @param the first argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the next argument type + * @param the return type + */ + public interface A8Function { + /** + * Invoke the function + * + * @param arg0 the first argument + * @param arg1 the next argument + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @param arg5 the next argument + * @param arg6 the next argument + * @param arg7 the next argument + * @return the result + */ + R apply(A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4, A5 arg5, A6 arg6, A7 arg7); + } + + /** + * Emit a 7-argument operator + *

    + * This can also be used to invoke generator subroutines. + * + * @param the return type + * @param func the method reference + * @param arg1 an argument (other than the emitter) to pass to {@code func} + * @param arg2 the next argument + * @param arg3 the next argument + * @param arg4 the next argument + * @param arg5 the next argument + * @param arg6 the next argument + * @param arg7 the next argument + * @return the value returned by {@code func} + */ + public R emit( + A8Function, A1, A2, A3, A4, A5, A6, A7, R> func, A1 arg1, A2 arg2, + A3 arg3, A4 arg4, A5 arg5, A6 arg6, A7 arg7) { + return func.apply(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + } + + /** + * (Not recommended) Wrap the given method visitor with assumed stack contents + *

    + * {@link #start(ClassVisitor, int, String, MthDesc)} or + * {@link #start(TRef, ClassVisitor, int, String, MthDesc)} is recommended instead. + * + * @param the stack contents + * @param mv the ASM method visitor + * @param assumedStack the assumed stack contents + * @return the emitter + */ + public static Emitter assume(MethodVisitor mv, N assumedStack) { + return new Emitter<>(mv); + } + + /** + * Wrap the given method visitor assuming an empty stack + *

    + * {@link #start(ClassVisitor, int, String, MthDesc)} or + * {@link #start(TRef, ClassVisitor, int, String, MthDesc)} is recommended instead. + * + * @param mv the ASM method visitor + * @return the emitter + */ + public static Emitter start(MethodVisitor mv) { + mv.visitCode(); + return assume(mv, Next.BOTTOM); + } + + /** + * Define a static method + * + * @param the type returned by the method + * @param the parameter types of the method + * @param cv the ASM class visitor + * @param access the access flags (static is added automatically) + * @param name the name of the method + * @param desc the method descriptor + * @return an object to aid further definition of the method + */ + public static Def start(ClassVisitor cv, int access, + String name, MthDesc desc) { + access |= Opcodes.ACC_STATIC; + MethodVisitor mv = cv.visitMethod(access, name, desc.desc(), null, null); + return new Def<>(start(mv), new ArrayList<>()); + } + + /** + * Define an instance method + * + * @param the type returned by the method + * @param the type owning the method + * @param the parameter types of the method + * @param owner the owner type (as a reference type) + * @param cv the ASM class visitor + * @param access the access flags (static is forcibly removed) + * @param name the name of the method + * @param desc the method descriptor + * @return an object to aid further definition of the method + */ + public static ObjDef start(TRef owner, + ClassVisitor cv, int access, String name, MthDesc desc) { + access &= ~Opcodes.ACC_STATIC; + MethodVisitor mv = cv.visitMethod(access, name, desc.desc(), null, null); + return new ObjDef<>(start(mv), new ArrayList<>()); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Fld.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Fld.java new file mode 100644 index 0000000000..2a770770a3 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Fld.java @@ -0,0 +1,177 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import org.apache.commons.lang3.reflect.TypeLiteral; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Type; + +import ghidra.pcode.emu.jit.JitJvmTypeUtils; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * Utilities for declaring fields in an ASM {@link ClassVisitor} + *

    + * LATER: We do not yet return a "field handle." Ideally, we would and that would be the required + * argument for {@link Op#getfield(Emitter, TRef, String, BNonVoid)} and related ops. + */ +public interface Fld { + /** + * Declare an initialized boolean field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TBool type, String name, boolean init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized byte field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TByte type, String name, byte init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized short field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TShort type, String name, short init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized int field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TInt type, String name, int init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized long field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TLong type, String name, long init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized float field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TFloat type, String name, float init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized double field + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TDouble type, String name, double init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an initialized reference field + *

    + * Note that only certain types of fields can have initial values specified in this manner. A + * {@link String} is one such type. For other types, the initializer must be provided in a + * generated class initializer (for static fields) or constructor (for instance fields). + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + * @param init the initial value + */ + static void decl(ClassVisitor cv, int flags, TRef type, String name, T init) { + cv.visitField(flags, name, type.type().getDescriptor(), null, init); + } + + /** + * Declare an uninitialized field of any type + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type + * @param name the name + */ + static void decl(ClassVisitor cv, int flags, SNonVoid type, String name) { + cv.visitField(flags, name, type.type().getDescriptor(), null, null); + } + + /** + * Declare an uninitialized field of any type with a type signature + * + * @param cv the class visitor + * @param flags the flags as in + * {@link ClassVisitor#visitField(int, String, String, String, Object)} + * @param type the type with signature + * @param name the name + */ + static void decl(ClassVisitor cv, int flags, TypeLiteral type, String name) { + Class erased = JitJvmTypeUtils.erase(type.value); + String signature = erased == type.value + ? null + : JitJvmTypeUtils.typeToSignature(type.value); + cv.visitField(flags, name, Type.getDescriptor(erased), signature, null); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Lbl.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Lbl.java new file mode 100644 index 0000000000..c196a218e9 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Lbl.java @@ -0,0 +1,186 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import org.objectweb.asm.Label; + +import ghidra.pcode.emu.jit.gen.util.Emitter.Dead; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Methods.RetReq; + +/** + * Utility for defining and placing labels. + *

    + * These are used as control-flow targets, to specify the scope of local variables, and to specify + * the bounds of {@code try-catch} blocks. + *

    + * Labels, and the possibility of control flow, necessitate some care when trying to validate stack + * contents in generated code. Again, our goal is to find an acceptable syntax that also provides as + * much flexibility as possible for later maintenance and refactoring. The requirements: + *

      + *
    • Where code can jump, the stack at the jump target must agree with the resulting stack at the + * jump site.
    • + *
    • If code is only reachable by a jump, then the stack there must be the resulting stack at the + * jump site.
    • + *
    • If code is reachable by multiple jumps and/or fall-through, then the stack must agree along + * all those paths.
    • + *
    + *

    + * To enforce these requirements, we encode thhe stack contents at a label's position in the same + * manner as we encode the emitter's current stack contents. Now, when a label is placed, there are + * two possibilities: 1) The code is reachable, in which case the label's and the emitter's stacks + * must agree. 2) The code is unreachable, in which case the emitter's incoming stack does not + * matter. Its resulting stack is the label's stack, and the code at this point is now presumed + * reachable. Because we would have collisions due to type erasure, these two cases are implemented + * in non-overloaded methods {@link #place(Emitter, Lbl)} and {@link #placeDead(Emitter, Lbl)}. + *

    + * As an example, we show an {@code if-else} construct: + * + *

    + * var lblLess = em
    + * 		.emit(Op::iload, params.a)
    + * 		.emit(Op::ldc__i, 20)
    + * 		.emit(Op::if_icmple);
    + * var lblDone = lblLess.em()
    + * 		.emit(Op::ldc__i, 0xcafe)
    + * 		.emit(Op::goto_);
    + * return lblDone.em()
    + * 		.emit(Lbl::placeDead, lblLess.lbl())
    + * 		.emit(Op::ldc__i, 0xbabe)
    + * 		.emit(Lbl::place, lblDone.lbl())
    + * 		.emit(Op::ireturn, retReq);
    + * 
    + *

    + * This would be equivalent to + * + *

    + * int myFunc(int a) {
    + * 	if (a <= 20) {
    + * 		return 0xbabe;
    + * 	}
    + * 	else {
    + * 		return 0xcafe;
    + * 	}
    + * }
    + * 
    + *

    + * Note that we allow the Java compiler to infer the type of the label targeted by the + * {@link Op#if_icmple(Emitter)}. That form of the operator generates a label for us, and the + * inferred type of {@code lblLess} is an {@link LblEm}{@code }, representing the empty stack, + * because the conditional jump consumes both ints without pushing anything. The emitter in that + * returned tuple has the same stack contents, representing the fall-through case, and so we emit + * code into the false branch. To avoid falling through into the true branch, we emit an + * unconditional jump, i.e., {@link Op#goto_(Emitter)}, again taking advantage of Java's type + * inference to automatically derive the stack contents. Similar to the previous jump instruction, + * this returns a tuple, but this time, while the label still expects an empty stack, the emitter + * now has {@code :=}{@link Dead}, because any code emitted after this point would be + * unreachable. It is worth noting that none of the methods in {@link Op} accept a dead + * emitter. The only way (don't you dare cast it!) to resurrect the emitter is to place a label + * using {@link #placeDead(Emitter, Lbl)}. This is fitting, since we need to emit the true branch, + * so we place {@code lblLess} and emit the appropriate code. There is no need to jump after the + * true branch. We just allow both branches to flow into the same + * {@link Op#ireturn(Emitter, RetReq)}. Thus, we place {@code lblDone}, which is checked by the Java + * compiler to have matching stack contents, and finally emit the return. + *

    + * There is some manual bookkeeping here to ensure we use each previous emitter, but this is not too + * much worse than the manual bookkeeping needed to track label placement. In our experience, when + * we get that wrong, the compiler reports it as inconsistent, anyway. One drawback to using type + * inference is that the label's name does not appear in the jump instruction that targets it. We do + * not currently have a solution to that complaint. + * + * @param the stack contents where the label is placed (or must be placed) + * @param label the wrapped ASM label + */ +public record Lbl(Label label) { + /** + * A tuple providing both a (new) label and a resulting emitter + * + * @param the label's stack contents + * @param the emitter's stack contents, which will be the same as the label's, unless it is + * {@link Dead}. + * @param lbl the label + * @param em the emitter + */ + public record LblEm(Lbl lbl, Emitter em) {} + + /** + * Create a fresh label with any expected stack contents + *

    + * Using this to forward declare labels requires the user to explicate the expected stack, which + * may not be ideal, as it may require updating during refactoring. Consider using + * {@link Lbl#place(Emitter)} instead, which facilitates inference of the stack contents. + * + * @param the expected stack contents + * @return the label + */ + public static Lbl create() { + return new Lbl<>(new Label()); + } + + /** + * Generate a place a label where execution could already reach + *

    + * The returned label's stack will match this emitter's stack, since the code could be reached + * by multiple paths, likely fall-through and a jump to the returned label. + * + * @param the emitter's and the label's stack, i.e., as where the returned label is + * referenced + * @param em the emitter + * @return the label and emitter + */ + public static LblEm place(Emitter em) { + Lbl lbl = create(); + em.mv.visitLabel(lbl.label); + return new LblEm<>(lbl, em); + } + + /** + * Place the given label at a place where execution could already reach + *

    + * The emitter's stack and the label's stack must agree, since the code is reachable by multiple + * paths, likely fallthrough and a jump to the given label. + * + * @param the emitter's and the label's stack, i.e., as where the given label is referenced + * @param em the emitter + * @param lbl the label to place + * @return the same emitter + */ + public static Emitter place(Emitter em, Lbl lbl) { + em.mv.visitLabel(lbl.label); + return em; + } + + /** + * Place the given label at a place where execution could not otherwise reach + *

    + * The emitter must be dead, i.e., if it were to emit code, that code would be unreachable. By + * placing a referenced label at this place, the code following becomes reachable, and so the + * given emitter becomes alive again, having the stack that results from the referenced code. If + * the label has not yet been referenced, it must be forward declared with the expected stack. + * There is no equivalent of {@link #place(Emitter)} for a dead emitter, because there is no way + * to know the resulting stack. + * + * @param the stack where the given label is referenced + * @param em the emitter for otherwise-unreachable code + * @param lbl the label to place + * @return the emitter, as reachable via the given label + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Emitter placeDead(Emitter em, Lbl lbl) { + em.mv.visitLabel(lbl.label); + return (Emitter) em; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Local.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Local.java new file mode 100644 index 0000000000..16bb97e49a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Local.java @@ -0,0 +1,78 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import java.util.function.Consumer; + +import org.objectweb.asm.MethodVisitor; + +import ghidra.pcode.emu.jit.gen.util.Methods.Def; +import ghidra.pcode.emu.jit.gen.util.Methods.Def.ParamFunction; +import ghidra.pcode.emu.jit.gen.util.Methods.ObjDef; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * The handle to a local variable + *

    + * Direct use of the canonical constructor is not recommended. We may later hide this behind an + * interface. + *

    + * The JVM {@code load} and {@code store} instructions all take an index argument. We would + * rather not have to keep track of indices, but instead wrap them in some named handle. These + * handles are generated by {@link Scope#decl(BNonVoid, String)}, + * {@link Def#param(ParamFunction, BNonVoid, String, Consumer)}, and + * {@link Def#done(ObjDef, TRef, Consumer)}. For the most part, the user need not worry at all about + * indices, only types. + * + * @param the (machine) type of the variable. + * @param type the type + * @param name the name + * @param index the index + */ +public record Local(T type, String name, int index) { + + /** + * Construct a local variable handle + *

    + * Direct use of this method is not recommended. It may be made private later. + * + * @param the type of the variable + * @param type the type + * @param name the name + * @param index the index + * @return the handle + */ + public static Local of(T type, String name, int index) { + return new Local<>(type, name, index); + } + + /** + * Declare a given local variable + * + * @param the stack contents of the emitter + * @param em the emitter (Nothing is actually emitted, but we need the wrapped ASM + * {@link MethodVisitor}.) + * @param local the handle to the local + * @param start the start of the scope + * @param end the end of the scope + * @return the same emitter + */ + static Emitter decl(Emitter em, Local local, Lbl start, Lbl end) { + em.mv.visitLocalVariable(local.name, local.type.type().getDescriptor(), null, + start.label(), end.label(), local.index); + return em; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java new file mode 100644 index 0000000000..d5304c416c --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Methods.java @@ -0,0 +1,1197 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.function.*; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Def.ParamFunction; +import ghidra.pcode.emu.jit.gen.util.Methods.Def.ThisFunction; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * Utilities for invoking, declaring, and defining methods + *

    + * Method invocation requires a bit more kruft than we would like, it that kruft does have its + * benefits. (For an example of method definition, see {@link Emitter}.) + *

    + * Consider the example where we'd like to invoke {@link Integer#compare(int, int)}: + * + *

    + * var mdescIntegerCompare = MthDesc.derive(Integer::compare)
    + * 		.check(MthDesc::returns, Types.T_INT)
    + * 		.check(MthDesc::param, Types.T_INT)
    + * 		.check(MthDesc::param, Types.T_INT)
    + * 		.check(MthDesc::build);
    + * em
    + * 		.emit(Op::iload, params.a)
    + * 		.emit(Op::ldc__i, 20)
    + * 		.emit(Op::invokestatic, Types.refOf(Integer.class), "compare", mdescIntegerCompare,
    + * 			false)
    + * 		.step(Inv::takeArg)
    + * 		.step(Inv::takeArg)
    + * 		.step(Inv::ret)
    + * 		.emit(Op::ireturn, retReq);
    + * 
    + *

    + * The first requirement (as in the case of defining a method) is to obtain the descriptor of the + * target method. There are a few ways to generate method descriptors, but the safest when calling a + * compiled or library method is to derive it from a reference to that method. There is no such + * thing as a "method literal" in Java, but there are method references, and we can match the types + * of those references. One wrinkle to this, however, is that we cannot distinguish auto-boxed + * parameters from those that genuinely accept the boxed type, i.e., both {@code void f(int a)} and + * {@code void g(Integer b)} can be made into references of type {@link Consumer}{@code }, + * because {@code } is not a legal type signature. Thus, we require the user to call + * {@link MthDescCheckedBuilderP#check(BiFunction, Object)} with, e.g., + * {@link MthDesc#param(MthDescCheckedBuilderP, TInt)} to specify that an {@link Integer} is + * actually an {@code int}. However, we cannot check that the user did this correctly until runtime. + * Still, we are able to prevent a {@code Hippo} parameter from receiving an {@code int}, and that + * is much better than nothing. + *

    + * Once we have the method descriptor, we can use it in invocation operators, e.g., + * {@link Op#invokestatic(Emitter, TRef, String, MthDesc, boolean)}. In an of itself, this operator + * does not consume nor push anything to the stack. Instead, it returns an {@link Inv} + * object, which facilitates the popping and checking of each parameter, followed by the pushing of + * the returned value, if non-void. It is not obvious to us (if such a technique even exists) to pop + * an arbitrary number of entries from {@code } in a single method. Instead, we have to treat + * Java's type checker as a sort of automaton that we can step, one pop at a time, by invoking a + * method. This method is {@link Inv#takeArg(Inv)}, which for chaining purposes, is most easily + * invoked using the aptly-named {@link Inv#step(Function)} instance method. We would rather not + * have to do it this way, as it is unnecessary kruft that may also have a run-time cost. One + * benefit, however, is that if the arguments on the stack do not match the parameters required by + * the descriptor, the first mismatched {@code takeArg} line (corresponding to the right-most + * mismatched parameter) will fail to compile, and so we know which argument is incorrect. + * Finally, we must do one last step to to push the return value, e.g., {@link Inv#ret(Inv)}. This + * will check that all parameters have been popped, and then push a value of the descriptor's return + * type. It returns the resulting emitter. For a void method, use {@link Inv#retVoid(Inv)} to avert + * the push. Unfortunately, {@code ret} is still permitted, but at least downstream operators are + * likely to fail, since nothing should consume {@link TVoid}. + */ +public interface Methods { + + /** + * A method descriptor + * + * @param the (machine) type returned by the method + * @param the parameter types encoded as in {@link Emitter} where the top corresponds to the + * right-most parameter. + * @param desc the descriptor as a string, as in + * {@link MethodVisitor#visitMethodInsn(int, String, String, String, boolean)}. + */ + public record MthDesc(String desc) { + + /** + * Begin building a method descriptor that returns the given (machine) type + * + * @param the return type + * @param retType the return type + * @return the builder + */ + public static MthDescBuilder returns(MR retType) { + return new MthDescBuilder<>(retType.type()); + } + + /** + * Begin building a method descriptor that returns the given (source) type + * + * @param retType the return type + * @return the builder + */ + public static MthDescBuilder returns(SType retType) { + return new MthDescBuilder<>(retType.type()); + } + + /** + * Obtain a method descriptor for a reflected method of unknown or unspecified type + *

    + * All bets are off for static type checking, but this at least obtains the descriptor as a + * string at runtime. + * + * @param method the method + * @return the untyped descriptor + */ + public static MthDesc reflect(Method method) { + return new MthDesc<>(Type.getMethodDescriptor(method)); + } + + /** + * Begin building a method descriptor derived from the given method reference + *

    + * NOTE: This is imperfect because method refs allow {@code ? super} for the return type and + * {@code ? extends} for each parameter. Furthermore, primitives must be boxed, and so we + * can't distinguish primitive parameters from their boxed types. Still, this is better than + * nothing. For an example of this, see {@link Methods} or {@link Emitter}. + * + * @param the return type, boxed + * @param the first argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static MthDescCheckedBuilderR> + derive(Function func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the return type, boxed + * @param the first argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static MthDescCheckedBuilderR, A0>> + derive(BiFunction func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the return type, boxed + * @param the first argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static + MthDescCheckedBuilderR, A1>, A0>> + derive(A3Function func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the first argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static + MthDescCheckedBuilderR, A1>, A0>> + derive(A3Consumer func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the return type, boxed + * @param the first argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static + MthDescCheckedBuilderR, A2>, A1>, A0>> + derive(A4Function func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Begin building a method descriptor derived from the given method reference + * + * @see #derive(Function) + * @param the first argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param another argument type, boxed + * @param func the method reference + * @return the checked builder + */ + public static + MthDescCheckedBuilderR, A2>, A1>, A0>> + derive(A4Consumer func) { + return new MthDescCheckedBuilderR<>(); + } + + /** + * Specify the return type of a checked builder + *

    + * This may not be used for primitive types, but can be used if the method genuinely returns + * the boxed type. + * + * @param the (perhaps boxed) return type + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP, Bot, CN> + returns(MthDescCheckedBuilderR builder, TRef retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a void return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TVoid retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a boolean return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TBool retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a byte return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TByte retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a char return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TChar retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a short return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TShort retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify an int return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TInt retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a long return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TLong retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a float return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TFloat retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a double return type for a checked builder + * + * @param the boxed parameter types for later checking + * @param builder the (stage 1) builder + * @param retType the return type + * @return the (stage 2) builder + */ + public static MthDescCheckedBuilderP + returns(MthDescCheckedBuilderR builder, TDouble retType) { + return new MthDescCheckedBuilderP<>(retType.type()); + } + + /** + * Specify a reference parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param

    the parameter type + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP>, CN1> + param(MthDescCheckedBuilderP builder, TRef

    paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a boolean parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TBool paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a byte parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TByte paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a char parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TChar paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a short parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TShort paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify an int parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TInt paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a long parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TLong paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a float parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TFloat paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Specify a double parameter type + * + * @param the method return type + * @param the actual parameter types specified so far + * @param the boxed parameter types remaining + * @param the boxed type for the parameter whose type is being specified + * @param builder the builder + * @param paramType the specified parameter type + * @return the builder + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public static > MthDescCheckedBuilderP, CN1> + param(MthDescCheckedBuilderP builder, TDouble paramType) { + builder.paramTypes.add(paramType.type()); + return (MthDescCheckedBuilderP) builder; + } + + /** + * Finish building the method descriptor (checked) + *

    + * This cannot be invoked until the (boxed) parameter types remaining is empty + * + * @param the method return type + * @param the actual parameter types, all specified + * @param builder the builder with no remaining boxed (unspecified) parameter types + * @return the method descriptor + */ + public static MthDesc + build(MthDescCheckedBuilderP builder) { + return new MthDesc<>( + Type.getMethodDescriptor(builder.retType, builder.paramTypes.toArray(Type[]::new))); + } + } + + /** + * An unchecked builder of a method descriptor + * + * @param the (machine) return type + * @param the parameter (machine) types specified so far, encoded as in {@link Emitter}. + */ + public static class MthDescBuilder { + private final Type retType; + private final List paramTypes = new ArrayList<>(); + + MthDescBuilder(Type retType) { + this.retType = retType; + } + + /** + * Add a parameter (to the right) + * + * @param

    the type of the parameter + * @param paramType the type of the parameter + * @return the builder + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public

    MthDescBuilder> param(P paramType) { + paramTypes.add(paramType.type()); + return (MthDescBuilder) this; + } + + /** + * Add a parameter (to the right) + * + * @param paramType the (source) type of the parameter + * @return the builder + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public MthDescBuilder> param(SType paramType) { + paramTypes.add(paramType.type()); + return (MthDescBuilder) this; + } + + /** + * Finished building the method descriptor + * + * @return the method descriptor + */ + public MthDesc build() { + return new MthDesc<>( + Type.getMethodDescriptor(retType, paramTypes.toArray(Type[]::new))); + } + } + + /** + * The analog of {@link Next}, but for unspecified parameter types to be checked + */ + interface CkNext { + } + + /** + * The analog of {@link Ent}, but for {@link CkNext} + * + * @param the tail of the list + * @param the (possibly boxed) parameter type + */ + interface CkEnt extends CkNext { + } + + /** + * The analog of {@link Bot}, but for {@link CkNext} + */ + interface CkBot extends CkNext { + } + + /** + * A checked builder (stage 1) of a method descriptor + *

    + * Only {@link MthDesc#returns(BType)} or similar may be used on this stage 1 builder. + * + * @param the return type to be specified later with checking + * @param the parameter types to be specified later with checking + */ + public static class MthDescCheckedBuilderR { + + /*package*/ MthDescCheckedBuilderR() { + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param the first argument type of {@code func} + * @param func the method to invoke + * @param arg1 the first argument to {@code func} + * @return the return value from {@code func} + */ + public R check(BiFunction, A1, R> func, + A1 arg1) { + return func.apply(this, arg1); + } + } + + /** + * A checked builder (stage 2) of a method descriptor + *

    + * Only {@link MthDesc#param(MthDescCheckedBuilderP, TRef)} or similar and + * {@link MthDesc#build(MthDescCheckedBuilderP)} may be used on this stage 2 builder. + * + * @param the method return type + * @param the actual parameter types specified so far, encoded as in {@link Emitter}. + * @param the boxed parameter types remaining to be specified, encoded similarly to + * {@link Emitter}, but using {@link CkNext} and in reverse order. + */ + public static class MthDescCheckedBuilderP { + private final Type retType; + private final List paramTypes = new ArrayList<>(); + + MthDescCheckedBuilderP(Type retType) { + this.retType = retType; + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param func the method to invoke + * @return the return value from {@code func} + */ + public R check(Function, R> func) { + return func.apply(this); + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param the first argument type of {@code func} + * @param func the method to invoke + * @param arg1 the first argument to {@code func} + * @return the return value from {@code func} + */ + public R check(BiFunction, A1, R> func, + A1 arg1) { + return func.apply(this, arg1); + } + } + + /** + * An invocation object to facilitate the checked popping of arguments for a static method + * invocation and the final push of its returned value. + * + * @param the return type + * @param the contents of the JVM stack + * @param the unmatched parameters types remaining + * @param em the emitter, which will be given back when the invocation check is complete + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public record Inv(Emitter em) { + + /** + * Pop an argument and match/check it against the next (right-most unmatched) parameter + *

    + * NOTE: This will not work for polymorphic arguments. For ref-typed arguments, use + * {@link #takeRefArg(Inv)}. + * + * @param the parameter type popped from the remaining parameter types + * @param the argument type popped from the stack contents + * @param the return type + * @param the new remaining parameter types + * @param the current parameter types having the popped parameter on top + * @param the new remaining stack contents + * @param the current stack contents having the popped argument on top + * @param inv the invocation object + * @return the invocation object with remaining parameters and stack contents + */ + public static , + SN1 extends Next, SN0 extends Ent> Inv + takeArg(Inv inv) { + return (Inv) inv; + } + + /** + * Pop a polymorphic reference argument and match/check it against the next (right-most + * unmatched parameter) + * + * @param the parameter's object type + * @param the argument's object type + * @param the parameter type popped from the remaining parameter types + * @param the argument type popped from the stack contents + * @param the return type + * @param the new remaining parameter types + * @param the current parameter types having the popped parameter on top + * @param the new remaining stack contents + * @param the current stack contents having the popped argument on top + * @param inv the invocation object + * @return the invocation object with remaining parameters and stack contents + */ + public static , A1 extends TRef, + MR extends BType, + MN1 extends Next, MN0 extends Ent, + SN1 extends Next, SN0 extends Ent> Inv + takeRefArg(Inv inv) { + return (Inv) inv; + } + + /** + * Pop an argument and match/check it against the next (right-most unmatched) parameter + *

    + * NOTE: This will not work for polymorphic arguments. For ref-typed arguments, use + * {@link #takeRefArg(ObjInv)}. + * + * @param the method's owning type + * @param the parameter type popped from the remaining parameter types + * @param the argument type popped from the stack contents + * @param the return type + * @param the new remaining parameter types + * @param the current parameter types having the popped parameter on top + * @param the new remaining stack contents + * @param the current stack contents having the popped argument on top + * @param inv the invocation object + * @return the invocation object with remaining parameters and stack contents + */ + public static , + SN1 extends Next, SN0 extends Ent> ObjInv + takeArg(ObjInv inv) { + return (ObjInv) inv; + } + + /** + * Pop a polymorphic reference argument and match/check it against the next (right-most + * unmatched parameter) + * + * @param the method's owning type + * @param the parameter's object type + * @param the argument's object type + * @param the parameter type popped from the remaining parameter types + * @param the argument type popped from the stack contents + * @param the return type + * @param the new remaining parameter types + * @param the current parameter types having the popped parameter on top + * @param the new remaining stack contents + * @param the current stack contents having the popped argument on top + * @param inv the invocation object + * @return the invocation object with remaining parameters and stack contents + */ + public static , A1 extends TRef, + MR extends BType, + MN1 extends Next, MN0 extends Ent, + SN1 extends Next, SN0 extends Ent> ObjInv + takeRefArg(ObjInv inv) { + return (ObjInv) inv; + } + + /** + * Pop an argument and a parameter without checking + *

    + * NOTE: This should only be used with {@link MthDesc#reflect(Method)}. When dealing with a + * parameter list whose length is only known at runtime, recursion should be favored, so + * that each argument pushed by the emitter is provably paired with a parameter denoted by + * calling this method. + * + * @param the method's owning type + * @param the return type + * @param the new remaining stack contents + * @param the current stack contents having the popped argument on top + * @param inv the invocation object + * @return the invocation object with remaining parameters and stack contents + */ + public static > + ObjInv takeQArg(ObjInv inv) { + return (ObjInv) inv; + } + + /** + * Pop the object reference from the stack and check it against the owning type + *

    + * This must be used, but only once the parameter type list is empty + * + * @param the method's owning type + * @param the return type + * @param the new remaining stack contents + * @param the current stack contents having popped the reference on top + * @param inv the invocation object + * @return the invocation object with remaining stack contents + */ + public static >> Inv + takeObjRef(ObjInv inv) { + return new Inv(inv.em); + } + + /** + * Pop the object reference from the stack without checking it + *

    + * NOTE: This should only be used with {@link MthDesc#reflect(Method)}. This must be used, + * but only when sufficient arguments have been popped to satisfy the reflected parameters. + * It is up to the caller to know how many arguments are expected. + * + * @param the method's owning type + * @param the return type + * @param the new remaining stack contents + * @param the current stack contents having popped the reference on top + * @param inv the invocation object + * @return the invocation object with remaining stack contents + */ + public static >> Inv + takeQObjRef(ObjInv inv) { + return new Inv(inv.em); + } + + /** + * Finish checking an invocation of a static void method + * + * @param the stack contents after the invocation + * @param inv the invocation object + * @return the emitter typed with the resulting stack + */ + public static Emitter retVoid(Inv inv) { + return inv.em; + } + + /** + * Finish an invocation of a static void method without checking + *

    + * NOTE: This should only be used with {@link MthDesc#reflect(Method)}. + * + * @param the stack contents after the invocation + * @param inv the invocation object + * @return the emitter typed with the resulting stack + */ + public static Emitter retQVoid(Inv inv) { + return inv.em; + } + + /** + * Finish checking an invocation of a static method + * + * @param the return type + * @param the stack contents before pushing the returned result + * @param inv the invocation object + * @return the emitter typed with the resulting stack, i.e., having pushed the returned + * value + */ + public static Emitter> + ret(Inv inv) { + return (Emitter) inv.em; + } + + /** + * Finish invocation of a static method without checking + *

    + * NOTE: This should only be used with {@link MthDesc#reflect(Method)}. + * + * @param the asserted return type + * @param the stack contents before pushing the returned result + * @param inv the invocation object + * @param returnType the asserted return type + * @return the emitter typed with the resulting stack, i.e., having pushed the returned + * value + */ + public static Emitter> + retQ(Inv inv, RT returnType) { + return (Emitter) inv.em; + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param func the method to invoke + * @return the return value from {@code func} + */ + public R step(Function, R> func) { + return func.apply(this); + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param the first argument type of {@code func} + * @param func the method to invoke + * @param arg1 the first argument to {@code func} + * @return the return value from {@code func} + */ + public R step(BiFunction, A1, R> func, A1 arg1) { + return func.apply(this, arg1); + } + } + + /** + * An invocation object to facilitate the checked popping of arguments for an instance method + * invocation and the final push of its returned value. + * + * @param the method's owning type + * @param the return type + * @param the contents of the JVM stack + * @param the unmatched parameters types remaining + * @param em the emitter, which will be given back when the invocation check is complete + */ + public record ObjInv(Emitter em) { + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param func the method to invoke + * @return the return value from {@code func} + */ + public R step(Function, R> func) { + return func.apply(this); + } + } + + /** + * A defined parameter, which was checked against a method descriptor + * + * @param the (machine) type + * @param type the type + * @param name the name + * @param receiver a callback to receive the declared local, once all parameters are defined and + * checked + */ + record MthParam(T type, String name, Consumer> receiver) { + + void declare(Scope scope) { + receiver.accept(scope.decl(type, name)); + } + } + + /** + * A static method definition (builder) + * + * @param the return type + * @param the parameter types, encoded as in {@link Emitter} + * @param em the emitter to be given once method definition moves into bytecode generation + * @param params the defined parameters so far + */ + record Def(Emitter em, List> params) { + + /** + * A method reference for defining a parameter + * + * @param the method definition + * @param the type of the parameter + * @param the return type + */ + public interface ParamFunction { + R apply(A0 arg0, T1 type, String name, Consumer> receiver); + } + + /** + * A method reference for finishing a static method definition + * + * @param the method definition + * @param the return type + */ + public interface DoneFunction { + R apply(A0 arg); + } + + /** + * A method reference for finishing an instance method definition + * + * @param the method definition + * @param the owning type + * @param the return type + */ + public interface ThisFunction { + R apply(A0 arg0, TRef type, Consumer>> receiver); + } + + /** + * Define a parameter for a static method + * + * @param the return type + * @param the parameter type + * @param the remaining parameters still requiring definition + * @param the parameters remaining and the one being defined + * @param mdef the method definition + * @param type the parameter type + * @param name the name + * @param receiver a consumer to accept the declared local variable handle + * @return the method definition + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static > Def param(Def mdef, T1 type, String name, + Consumer> receiver) { + mdef.params.add(new MthParam<>(type, name, receiver)); + return (Def) mdef; + } + + /** + * Define a parameter for an instance method + * + * @param the return type + * @param the owning type + * @param the parameter type + * @param the remaining parameters still requiring definition + * @param the parameters remaining and the one being defined + * @param mdef the method definition + * @param type the parameter type + * @param name the name + * @param receiver a consumer to accept the declared local variable handle + * @return the method definition + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static > ObjDef param(ObjDef mdef, T1 type, + String name, Consumer> receiver) { + mdef.params.add(new MthParam<>(type, name, receiver)); + return (ObjDef) mdef; + } + + /** + * Finish defining a static method and begin emitting bytecode + * + * @param the return type + * @param mdef the method definition + * @return the return request and emitter typed with an empty stack + */ + public static RetReqEm done(Def mdef) { + return new RetReqEm<>(new RetReq<>(), mdef.em); + } + + /** + * Finish defining an instance method and begin emitting bytecode + * + * @param the return type + * @param the owning type + * @param mdef the method definition + * @param type the owning type (for this {@code this}) parameter + * @param receiver a consumer to accept the declared {@code this} local handle + * @return the return request and emitter typed with an empty stack + */ + public static RetReqEm done(ObjDef mdef, + TRef type, Consumer>> receiver) { + mdef.params.add(new MthParam<>(type, "this", receiver)); + Scope scope = mdef.em.rootScope; + List> params = mdef.params.reversed(); + for (int i = 0; i < params.size(); i++) { + params.get(i).declare(scope); + } + return new RetReqEm<>(new RetReq<>(), mdef.em); + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param the parameter type + * @param func the static method reference that actually processes the parameter + * @param type the parameter type + * @param name the name + * @param receiver the receiver + * @return the return value from {@code func} + */ + public R param(ParamFunction, T1, R> func, + T1 type, String name, Consumer> receiver) { + return func.apply(this, type, name, receiver); + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param func the static method reference + * @return the return value from {@code func} + */ + public R param(DoneFunction, R> func) { + return func.apply(this); + } + } + + /** + * An instance method definition (builder) + * + * @param the return type + * @param the owning type + * @param the parameter types, encoded as in {@link Emitter} + * @param em the emitter to be given once method definition moves into bytecode generation + * @param params the defined parameters so far + */ + record ObjDef(Emitter em, List> params) { + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param the parameter type + * @param func the static method reference that actually processes the parameter + * @param type the parameter type + * @param name the name + * @param receiver the receiver + * @return the return value from {@code func} + */ + public R param( + ParamFunction, T1, R> func, T1 type, String name, + Consumer> receiver) { + return func.apply(this, type, name, receiver); + } + + /** + * A syntactic workaround for static method chaining + * + * @param the return type of {@code func} + * @param func the static method reference that actually processes the parameter + * @param type the {@code this} type + * @param receiver the receiver + * @return the return value from {@code func} + */ + public R param(ThisFunction, OT, R> func, TRef type, + Consumer>> receiver) { + return func.apply(this, type, receiver); + } + } + + /** + * A return request + *

    + * This is just a witness to the required return type of a method. Technically, there's nothing + * that prevents a user from passing a request meant for one method into, e.g., + * {@link Op#return_(Emitter, RetReq)} for bytecode emitted into another, but such should be + * unlikely to happen accidentally. + * + * @param the required return type + */ + record RetReq() {}; + + /** + * A tuple of return request and emitter with empty stack + * + * @param the required return type + */ + record RetReqEm(RetReq ret, Emitter em) {} +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Misc.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Misc.java new file mode 100644 index 0000000000..495bc07c8a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Misc.java @@ -0,0 +1,143 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import java.lang.System.Logger.Level; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; + +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Types.BNonVoid; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; + +/** + * Miscellaneous utilities + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public interface Misc { + + /** + * Fix the top of the stack, so it doesn't "extend" {@link Ent}, but just is {@link Ent}. + *

    + * This may be necessary when a code generating method is typed to pop then push something of + * the same type, but in some conditions actually just leaves the stack as is. + * + * @param the type at the top of the stack + * @param the tail of the stack + * @param the full stack + * @param em the emitter + * @return the same emitter + */ + static > + Emitter> cast1(Emitter em) { + return (Emitter) em; + } + + /** + * A handle to an (incomplete) {@code try-catch} block + * + * @param the type caught by the block + * @param the stack contents at the start and end of the {@code try} block + * @param end the label to place at the end of the {@code try} block + * @param handler the label to place at the handler, i.e., the start of the {@code catch} block. + * Note that the stack is the same as the block bounds, but with the exception type + * pushed. FIXME: Is that fully correct? Do we know what's actually underneath the + * exception? + * @param em the emitter at the start of the {@code try} block + */ + record TryCatchBlock(Lbl end, + Lbl>> handler, Emitter em) {} + + /** + * Start a try-catch block + *

    + * This places a label to mark the start of the {@code try} block. The user must provide labels + * for the end and the handler. Note that the stack contents at the handler must be the same as + * at the bounds, but with the exception type pushed. While this can check that the labels are + * correctly placed, it cannot check if placement is altogether forgotten. Ideally, the handler + * label is placed where code is otherwise unreachable, i.e., using + * {@code Lbl#placeDead(Emitter, Lbl)}. + * + * @param the type caught by the block + * @param the stack contents at the bounds of the {@code try} block + * @param em the emitter + * @param end the end label, often just {@link Lbl#create()}. + * @param handler the handler label, often just {@link Lbl#create()} + * @param type the exception type. If multiple types are caught, this must be the join of those + * types, and the user must emit code to distinguish each, possibly re-throwing if + * the join is larger than the union. + * @return a handle to the block. + */ + static TryCatchBlock tryCatch(Emitter em, + Lbl end, Lbl>> handler, TRef type) { + Lbl start = Lbl.create(); + em = em.emit(Lbl::place, start); + em.mv.visitTryCatchBlock(start.label(), end.label(), handler.label(), + type.internalName()); + return new TryCatchBlock<>(end, handler, em); + } + + /** + * Place a line number + * + * @param any live stack + * @param em the emitter + * @param number the (non zero) line number + * @return the emitter + */ + static Emitter lineNumber(Emitter em, int number) { + Label label = new Label(); + em.mv.visitLabel(label); + em.mv.visitLineNumber(number, label); + return em; + } + + /** + * Finish emitting bytecode + *

    + * This is where we invoke {@link MethodVisitor#visitMaxs(int, int)}. Frameworks that require + * bytecode generation can try to enforce this by requiring bytecode generation methods to + * return {@link Void}. Sure, a user can just return null, but this will at least remind them + * that they should call this method, as convention is to use a pattern like: + * + *

    +	 * return em
    +	 * 		.emit(Op::ldc__i, 0)
    +	 * 		.emit(Op::ireturn, retReq)
    +	 * 		.emit(Misc::finish);
    +	 * 
    + *

    + * A user of this pattern would be reminded were {@code finish} missing. Provided the generation + * method returns {@link Void}, this pattern should compile. + * + * @param em the emittter + * @return null + */ + static Void finish(Emitter em) { + em.rootScope.close(); + try { + em.mv.visitMaxs(0, 0); + } + catch (Exception e) { + Emitter.LOGGER.log(Level.WARNING, "Failed to compute Maxs", e); + } + em.mv.visitEnd(); + return null; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Op.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Op.java new file mode 100644 index 0000000000..33e0ce74f2 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Op.java @@ -0,0 +1,3185 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import org.objectweb.asm.*; + +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Lbl.LblEm; +import ghidra.pcode.emu.jit.gen.util.Methods.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +/** + * This interface is a namespace that defines all (well most) JVM bytecode operations. + *

    + * These also provide small examples of how to declare the type signatures for methods that generate + * portions of bytecode. Inevitably, those methods will have expectations of what is on the stack, + * and would like to express the overall effect on that stack in terms of the incoming stack. + * Conventionally, generation methods should accept the emitter (typed with the incoming stack) as + * its first parameter and return that emitter typed with the resulting stack. This allows those + * methods to be invoked using, e.g., {@link Emitter#emit(Function)}, and also sets them up to use + * the pattern: + * + *

    + * return em
    + * 		.emit(Op::ldc__i, 1)
    + * 		.emit(Op::iadd);
    + * 
    + *

    + * With this pattern, the Java type checker will ensure that the expected effect on the stack is in + * fact what the emitted code does. Once the pattern is understood, the type signature of each + * opcode method is trivially derived from Chapter 6 of the JVM specification. We do, however, have + * to treat each form separately. Method invocation opcodes require some additional support (see + * {@link Inv}), because they consume arguments of arbitrary number and types. + */ +@SuppressWarnings({ "unchecked", "rawtypes" }) +public interface Op { + + /** + * Emit an {@code aaload} instruction + * + * @param the element type + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static >, + N0 extends Ent> + Emitter>> aaload(Emitter em) { + em.mv.visitInsn(Opcodes.AASTORE); + return (Emitter) em; + } + + /** + * Emit an {@code aastore} instruction + * + * @param the element type + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index, + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static >, + N1 extends Ent, + N0 extends Ent>> + Emitter aastore(Emitter em) { + em.mv.visitInsn(Opcodes.AASTORE); + return (Emitter) em; + } + + /** + * Emit an {@code aconst_null} instruction + * + * @param the ascribed type of the {@code null} + * @param the tail of the stack (...) + * @param em the emitter + * @param type the ascribed type of the {@code null} + * @return the emitter with ..., {@code (T) null} + */ + static , + N extends Next> + Emitter> aconst_null(Emitter em, T type) { + em.mv.visitInsn(Opcodes.ACONST_NULL); + return (Emitter) em; + } + + /** + * Emit an {@code aload} instruction + * + * @param the type of the local + * @param the tail of the stack (...) + * @param em the emitter + * @param local the handle to the local + * @return the emitter with ..., value + */ + static , + N extends Next> + Emitter> aload(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.ALOAD, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code anewarray} instruction + * + * @param the element type + * @param the tail of the stack (...) + * @param ..., count + * @param em the emitter + * @param elemType the element type + * @return the emitter with ..., arrayref + */ + static > + Emitter>> anewarray(Emitter em, TRef elemType) { + em.mv.visitTypeInsn(Opcodes.ANEWARRAY, elemType.internalName()); + return (Emitter) em; + } + + /** + * Emit an {@code areturn} instruction + * + * @param the required return (ref) type + * @param the value (ref) type on the stack + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @param retReq some proof of this method's required return type + * @return the dead emitter + */ + static >> + Emitter areturn(Emitter em, RetReq> retReq) { + em.mv.visitInsn(Opcodes.ARETURN); + return (Emitter) em; + } + + /** + * Emit an {@code arraylength} instruction, when the array has primitive elements + * + * @param the element type + * @param the tail of the stack (...) + * @param ..., arrayref + * @param em the emitter + * @param elemType the element type + * @return the emitter with ..., length + */ + static , + N1 extends Next, + N0 extends Ent>> + Emitter> arraylength__prim(Emitter em, ET elemType) { + em.mv.visitInsn(Opcodes.ARRAYLENGTH); + return (Emitter) em; + } + + /** + * Emit an {@code arraylength} instruction, when the array has reference elements + * + * @param the element type + * @param the tail of the stack (...) + * @param ..., arrayref + * @param em the emitter + * @return the emitter with ..., length + */ + static >> + Emitter> arraylength__ref(Emitter em) { + em.mv.visitInsn(Opcodes.ARRAYLENGTH); + return (Emitter) em; + } + + /** + * Emit an {@code astore} instruction + * + * @param the local variable (ref) type + * @param the value (ref) type on the stack + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @param local the target local variable + * @return the emitter with ... + */ + static >> + Emitter astore(Emitter em, Local> local) { + em.mv.visitVarInsn(Opcodes.ASTORE, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code athrow} instruction + * + * @param the value (Throwable ref) type on the stack + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @return the dead emitter + */ + static , + N1 extends Next, + N0 extends Ent> + Emitter athrow(Emitter em) { + em.mv.visitInsn(Opcodes.ATHROW); + return (Emitter) em; + } + + /** + * Emit a {@code baload} instruction for a boolean array + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> baload__boolean(Emitter em) { + em.mv.visitInsn(Opcodes.BALOAD); + return (Emitter) em; + } + + /** + * Emit a {@code baload} instruction for a byte array + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> baload(Emitter em) { + em.mv.visitInsn(Opcodes.BALOAD); + return (Emitter) em; + } + + /** + * Emit a {@code bastore} instruction for a boolean array + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter bastore__boolean(Emitter em) { + em.mv.visitInsn(Opcodes.BASTORE); + return (Emitter) em; + } + + /** + * Emit a {@code bastore} instruction for a byte array + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter bastore(Emitter em) { + em.mv.visitInsn(Opcodes.BASTORE); + return (Emitter) em; + } + + /** + * Emit a {@code caload} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> caload(Emitter em) { + em.mv.visitInsn(Opcodes.CALOAD); + return (Emitter) em; + } + + /** + * Emit a {@code castore} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter castore(Emitter em) { + em.mv.visitInsn(Opcodes.CASTORE); + return (Emitter) em; + } + + /** + * Emit a {@code checkcast} instruction + * + * @param the inferred type of the value on the stack, i.e., the less-specific type + * @param the desired type, i.e., the more-specific type + * @param the reference type for the inferred type + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @param type the reference type for the desired type + * @return the emitter with ..., objectref + */ + static , + N1 extends Next, + N0 extends Ent> + Emitter>> checkcast(Emitter em, TRef type) { + em.mv.visitTypeInsn(Opcodes.CHECKCAST, type.internalName()); + return (Emitter) em; + } + + /** + * Emit a {@code d2f} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> d2f(Emitter em) { + em.mv.visitInsn(Opcodes.D2F); + return (Emitter) em; + } + + /** + * Emit a {@code d2i} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> d2i(Emitter em) { + em.mv.visitInsn(Opcodes.D2I); + return (Emitter) em; + } + + /** + * Emit a {@code d2l} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> d2l(Emitter em) { + em.mv.visitInsn(Opcodes.D2L); + return (Emitter) em; + } + + /** + * Emit a {@code dadd} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> dadd(Emitter em) { + em.mv.visitInsn(Opcodes.DADD); + return (Emitter) em; + } + + /** + * Emit a {@code daload} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> daload(Emitter em) { + em.mv.visitInsn(Opcodes.DALOAD); + return (Emitter) em; + } + + /** + * Emit a {@code dastore} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter dastore(Emitter em) { + em.mv.visitInsn(Opcodes.DASTORE); + return (Emitter) em; + } + + /** + * Emit a {@code dcmpg} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> dcmpg(Emitter em) { + em.mv.visitInsn(Opcodes.DCMPG); + return (Emitter) em; + } + + /** + * Emit a {@code dcmpl} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> dcmpl(Emitter em) { + em.mv.visitInsn(Opcodes.DCMPL); + return (Emitter) em; + } + + /** + * Emit a {@code ddiv} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ddiv(Emitter em) { + em.mv.visitInsn(Opcodes.DDIV); + return (Emitter) em; + } + + /** + * Emit a {@code dload} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @param local the handle to the local + * @return the emitter with ..., value + */ + static + Emitter> dload(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.DLOAD, local.index()); + return (Emitter) em; + } + + /** + * Emit a {@code dmul} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> dmul(Emitter em) { + em.mv.visitInsn(Opcodes.DMUL); + return (Emitter) em; + } + + /** + * Emit a {@code dneg} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> dneg(Emitter em) { + em.mv.visitInsn(Opcodes.DNEG); + return (Emitter) em; + } + + /** + * Emit a {@code drem} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> drem(Emitter em) { + em.mv.visitInsn(Opcodes.DREM); + return (Emitter) em; + } + + /** + * Emit a {@code dreturn} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param retReq some proof of this method's required return type + * @return the dead emitter + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter dreturn(Emitter em, RetReq retReq) { + em.mv.visitInsn(Opcodes.DRETURN); + return (Emitter) em; + } + + /** + * Emit a {@code dstore} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param local the target local variable + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter dstore(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.DSTORE, local.index()); + return (Emitter) em; + } + + /** + * Emit a {@code dsub} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> dsub(Emitter em) { + em.mv.visitInsn(Opcodes.DSUB); + return (Emitter) em; + } + + /** + * Emit a {@code dup} instruction + * + * @param the type of the value on the stack + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., value, value + */ + static > + Emitter> dup(Emitter em) { + em.mv.visitInsn(Opcodes.DUP); + return (Emitter) em; + } + + /** + * Emit a {@code dup_x1} instruction + * + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value2, value1 + */ + static , + N0 extends Ent> + Emitter, V2>, V1>> dup_x1(Emitter em) { + em.mv.visitInsn(Opcodes.DUP_X1); + return (Emitter) em; + } + + /** + * Emit a {@code dup_x2} instruction, inserting 3 values down (Form 1) + * + * @param the type of value3 on the stack + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value3 + * @param ..., value3, value2 + * @param ..., value3, value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value3, value2, value1 + */ + static , + N1 extends Ent, + N0 extends Ent> + Emitter, V3>, V2>, V1>> dup_x2__111(Emitter em) { + em.mv.visitInsn(Opcodes.DUP_X2); + return (Emitter) em; + } + + /** + * Emit a {@code dup_x2} instruction, inserting 2 values down (Form 2) + * + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value2, value1 + */ + static , + N0 extends Ent> + Emitter, V2>, V1>> dup_x2__21(Emitter em) { + em.mv.visitInsn(Opcodes.DUP_X2); + return (Emitter) em; + } + + /** + * Emit a {@code dup2} instruction, duplicating two operands (Form 1) + * + * @param the type of value2 on the stack + * @param the type of vlaue1 on the stack + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ..., value2, value1, value2, value1 + */ + static , + N0 extends Ent> + Emitter, V1>> dup2__11(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2); + return (Emitter) em; + } + + /** + * Emit a {@code dup2} instruction, duplicating one operand (Form 2) + * + * @param the type of the value on the stack + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., value, value + */ + static > + Emitter> dup2__2(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2); + return (Emitter) em; + } + + /** + * Emit a {@code dup2_x1} instruction, duplicating two operands, three values down (Form 1) + * + * @param the type of value3 on the stack + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value3 + * @param ..., value3, value2 + * @param ..., value3, value2, value1 + * @param em the emitter + * @return the emitter with ..., value2, value1, value3, value2, value1 + */ + static , + N1 extends Ent, + N0 extends Ent> + Emitter, V1>, V3>, V2>, V1>> dup2_x1__111(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2_X1); + return (Emitter) em; + } + + /** + * Emit a {@code dup2_x1} instruction, duplicating one operand, two values down (Form 2) + * + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value2, value1 + */ + static , + N0 extends Ent> + Emitter, V2>, V1>> dup2_x1__12(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2_X1); + return (Emitter) em; + } + + /** + * Emit a {@code dup2_x2} instruction, duplicating two operands, four values down (Form 1) + * + * @param the type of value4 on the stack + * @param the type of value3 on the stack + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value4 + * @param ..., value4, value3 + * @param ..., value4, value3, value2 + * @param ..., value4, value3, value2, value1 + * @param em the emitter + * @return the emitter with ..., value2, value1, value4, value3, value2, value1 + */ + static , + N2 extends Ent, + N1 extends Ent, + N0 extends Ent> + Emitter, V1>, V4>, V3>, V2>, V1>> + dup2_x2_1111(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2_X2); + return (Emitter) em; + } + + /** + * Emit a {@code dup2_x2} instruction, duplicating one operand, three values down (Form 2) + * + * @param the type of value3 on the stack + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value3 + * @param ..., value3, value2 + * @param ..., value3, value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value3, value2, value1 + */ + static , + N1 extends Ent, + N0 extends Ent> + Emitter, V3>, V2>, V1>> dup2_x2_112(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2_X2); + return (Emitter) em; + } + + /** + * Emit a {@code dup2_x2} instruction, duplicating two operands, three values down (Form 3) + * + * @param the type of value3 on the stack + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value3 + * @param ..., value3, value2 + * @param ..., value3, value2, value1 + * @param em the emitter + * @return the emitter with ..., value2, value1, value3, value2, value1 + */ + static , + N1 extends Ent, + N0 extends Ent> + Emitter, V1>, V3>, V2>, V1>> dup2_x2_211(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2_X2); + return (Emitter) em; + } + + /** + * Emit a {@code dup2_x2} instruction, duplicating one operand, two values down (Form 4) + * + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value2, value1 + */ + static , + N0 extends Ent> + Emitter, V2>, V1>> dup2_x2_22(Emitter em) { + em.mv.visitInsn(Opcodes.DUP2_X2); + return (Emitter) em; + } + + /** + * Emit an {@code f2d} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> f2d(Emitter em) { + em.mv.visitInsn(Opcodes.F2D); + return (Emitter) em; + } + + /** + * Emit an {@code f2i} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> f2i(Emitter em) { + em.mv.visitInsn(Opcodes.F2I); + return (Emitter) em; + } + + /** + * Emit an {@code f2l} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> f2l(Emitter em) { + em.mv.visitInsn(Opcodes.F2L); + return (Emitter) em; + } + + /** + * Emit an {@code fadd} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> fadd(Emitter em) { + em.mv.visitInsn(Opcodes.FADD); + return (Emitter) em; + } + + /** + * Emit an {@code faload} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> faload(Emitter em) { + em.mv.visitInsn(Opcodes.FALOAD); + return (Emitter) em; + } + + /** + * Emit an {@code fastore} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter fastore(Emitter em) { + em.mv.visitInsn(Opcodes.FASTORE); + return (Emitter) em; + } + + /** + * Emit an {@code fcmpg} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> fcmpg(Emitter em) { + em.mv.visitInsn(Opcodes.FCMPG); + return (Emitter) em; + } + + /** + * Emit an {@code fcmpl} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> fcmpl(Emitter em) { + em.mv.visitInsn(Opcodes.FCMPL); + return (Emitter) em; + } + + /** + * Emit an {@code fdiv} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> fdiv(Emitter em) { + em.mv.visitInsn(Opcodes.FDIV); + return (Emitter) em; + } + + /** + * Emit an {@code fload} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @param local the handle to the local + * @return the emitter with ..., value + */ + static + Emitter> fload(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.FLOAD, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code fmul} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> fmul(Emitter em) { + em.mv.visitInsn(Opcodes.FMUL); + return (Emitter) em; + } + + /** + * Emit an {@code fneg} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> fneg(Emitter em) { + em.mv.visitInsn(Opcodes.FNEG); + return (Emitter) em; + } + + /** + * Emit an {@code frem} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> frem(Emitter em) { + em.mv.visitInsn(Opcodes.FREM); + return (Emitter) em; + } + + /** + * Emit an {@code freturn} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param retReq some proof of this method's required return type + * @return the dead emitter + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter freturn(Emitter em, RetReq retReq) { + em.mv.visitInsn(Opcodes.FRETURN); + return (Emitter) em; + } + + /** + * Emit an {@code fstore} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param local the target local variable + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter fstore(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.FSTORE, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code fsub} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> fsub(Emitter em) { + em.mv.visitInsn(Opcodes.FSUB); + return (Emitter) em; + } + + /** + * Emit a {@code getfield} instruction + *

    + * LATER: Some sort of field handle? + * + * @param the owner type + * @param the type of the object on the stack owning the field + * @param the type of the field + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @param owner the owner type + * @param name the name of the field + * @param type the type of the field + * @return the emitter with ..., value + */ + static , + FT extends BNonVoid, + N1 extends Next, + N0 extends Ent> + Emitter> getfield(Emitter em, TRef owner, String name, FT type) { + em.mv.visitFieldInsn(Opcodes.GETFIELD, owner.type().getInternalName(), name, + type.type().getDescriptor()); + return (Emitter) em; + } + + /** + * Emit a {@code getstatic} instruction + *

    + * LATER: Some sort of field handle? + * + * @param the type of the field + * @param the tail of the stack (...) + * @param em the emitter + * @param owner the owner type + * @param name the name of the field + * @param type the type of the field + * @return the emitter with ..., value + */ + static + Emitter> getstatic(Emitter em, TRef owner, String name, FT type) { + em.mv.visitFieldInsn(Opcodes.GETSTATIC, owner.type().getInternalName(), name, + type.type().getDescriptor()); + return (Emitter) em; + } + + /** + * Emit a {@code goto} instruction to a new target label + * + * @param the tail of the stack (...) + * @param em the emitter + * @return the new target label and the dead emitter + */ + static + LblEm goto_(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.GOTO, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit a {@code goto} instruction to a given target label + * + * @param the tail of the stack (...) + * @param em the emitter + * @param target the target label + * @return the dead emitter + */ + static + Emitter goto_(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.GOTO, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code i2b} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> i2b(Emitter em) { + em.mv.visitInsn(Opcodes.I2B); + return (Emitter) em; + } + + /** + * Emit an {@code i2c} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> i2c(Emitter em) { + em.mv.visitInsn(Opcodes.I2C); + return (Emitter) em; + } + + /** + * Emit an {@code i2d} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> i2d(Emitter em) { + em.mv.visitInsn(Opcodes.I2D); + return (Emitter) em; + } + + /** + * Emit an {@code i2f} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> i2f(Emitter em) { + em.mv.visitInsn(Opcodes.I2F); + return (Emitter) em; + } + + /** + * Emit an {@code i2l} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> i2l(Emitter em) { + em.mv.visitInsn(Opcodes.I2L); + return (Emitter) em; + } + + /** + * Emit an {@code i2s} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> i2s(Emitter em) { + em.mv.visitInsn(Opcodes.I2S); + return (Emitter) em; + } + + /** + * Emit an {@code iadd} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> iadd(Emitter em) { + em.mv.visitInsn(Opcodes.IADD); + return (Emitter) em; + } + + /** + * Emit an {@code iaload} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> iaload(Emitter em) { + em.mv.visitInsn(Opcodes.IALOAD); + return (Emitter) em; + } + + /** + * Emit an {@code iand} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> iand(Emitter em) { + em.mv.visitInsn(Opcodes.IAND); + return (Emitter) em; + } + + /** + * Emit an {@code iastore} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter iastore(Emitter em) { + em.mv.visitInsn(Opcodes.IASTORE); + return (Emitter) em; + } + + /** + * Emit an {@code idiv} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> idiv(Emitter em) { + em.mv.visitInsn(Opcodes.IDIV); + return (Emitter) em; + } + + /** + * Emit an {@code if_acmpeq} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent>> + LblEm if_acmpeq(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ACMPEQ, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_acmpeq} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent>> + Emitter if_acmpeq(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ACMPEQ, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_acmpne} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent>> + LblEm if_acmpne(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ACMPNE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_acmpne} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent>> + Emitter if_acmpne(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ACMPNE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_icmpeq} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + LblEm if_icmpeq(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ICMPEQ, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_icmpeq} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter if_icmpeq(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ICMPEQ, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_icmpge} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + LblEm if_icmpge(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ICMPGE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_icmpge} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter if_icmpge(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ICMPGE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_icmpgt} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + LblEm if_icmpgt(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ICMPGT, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_icmpgt} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter if_icmpgt(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ICMPGT, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_icmple} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + LblEm if_icmple(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ICMPLE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_icmple} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter if_icmple(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ICMPLE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_icmplt} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + LblEm if_icmplt(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ICMPLT, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_icmplt} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter if_icmplt(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ICMPLT, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code if_icmpne} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + LblEm if_icmpne(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IF_ICMPNE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code if_icmpne} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter if_icmpne(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IF_ICMPNE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifeq} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + LblEm ifeq(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFEQ, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifeq} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter ifeq(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFEQ, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifge} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + LblEm ifge(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFGE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifge} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter ifge(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFGE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifgt} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + LblEm ifgt(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFGT, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifgt} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter ifgt(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFGT, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifle} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + LblEm ifle(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFLE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifle} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter ifle(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFLE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code iflt} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + LblEm iflt(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFLT, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code iflt} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter iflt(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFLT, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifne} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + LblEm ifne(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFNE, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifne} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter ifne(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFNE, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifnonnull} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent>> + LblEm ifnonnull(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFNONNULL, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifnonnull} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent>> + Emitter ifnonnull(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFNONNULL, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code ifnull} instruction to a new target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the new target label and the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent>> + LblEm ifnull(Emitter em) { + Lbl target = Lbl.create(); + em.mv.visitJumpInsn(Opcodes.IFNULL, target.label()); + return new LblEm<>(target, (Emitter) em); + } + + /** + * Emit an {@code ifnull} instruction to a given target label + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param target the target label + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent>> + Emitter ifnull(Emitter em, Lbl target) { + em.mv.visitJumpInsn(Opcodes.IFNULL, target.label()); + return (Emitter) em; + } + + /** + * Emit an {@code iinc} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @param local the target local to increment + * @param increment the constant value to increment by + * @return the emitter with ... + */ + static + Emitter iinc(Emitter em, Local local, int increment) { + em.mv.visitIincInsn(local.index(), increment); + return em; + } + + /** + * Emit an {@code iload} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @param local the handle to the local + * @return the emitter with ..., value + */ + static + Emitter> iload(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.ILOAD, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code imul} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> imul(Emitter em) { + em.mv.visitInsn(Opcodes.IMUL); + return (Emitter) em; + } + + /** + * Emit an {@code ineg} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> ineg(Emitter em) { + em.mv.visitInsn(Opcodes.INEG); + return (Emitter) em; + } + + /** + * Emit an {@code instanceof} instruction + * + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @param type the given type (T) + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent>> + Emitter> instanceof_(Emitter em, TRef type) { + em.mv.visitTypeInsn(Opcodes.INSTANCEOF, type.internalName()); + return (Emitter) em; + } + + /** + * Emit an {@code invokedynamic} instruction + *

    + * WARNING: This is probably not implemented correctly. The JVM spec does not provide an + * example, but the best we can tell, after all the call site resolution machinery, the net + * arguments actually consumed from the stack is determined by the given method descriptor. We + * also just let the ASM types {@link Type}, {@link Handle}, and {@link ConstantDynamic} leak + * from an API perspective. + * + * @param the JVM stack at the call site. Some may be popped as arguments + * @param the parameters expected by the method descriptor + * @param the return type from the method descriptor + * @param em the emitter + * @param name the name of the method + * @param desc the method descriptor + * @param bootstrapMethodHandle as in + * {@link MethodVisitor#visitInvokeDynamicInsn(String, String, Handle, Object...)} + * @param bootstrapMethodArguments as in + * {@link MethodVisitor#visitInvokeDynamicInsn(String, String, Handle, Object...)} + * @return an object to complete type checking of the arguments and, if applicable, the result + */ + static < + SN extends Next, + MN extends Next, + MR extends BType> + Inv invokedynamic__unsupported(Emitter em, String name, + MthDesc desc, Handle bootstrapMethodHandle, + Object... bootstrapMethodArguments) { + em.mv.visitInvokeDynamicInsn(name, desc.desc(), bootstrapMethodHandle, + bootstrapMethodArguments); + return new Inv<>(em); + } + + /** + * Emit an {@code invokeinterface} instruction + * + * @param the owner (interface) type + * @param the JVM stack at the call site. Some may be popped as arguments + * @param the parameters expected by the method descriptor + * @param the return type from the method descriptor + * @param em the emitter + * @param ownerType the owner (interface) type + * @param name the name of the method + * @param desc the method descriptor + * @return an object to complete type checking of the arguments and, if applicable, the result + */ + static + ObjInv + invokeinterface(Emitter em, TRef ownerType, String name, MthDesc desc) { + em.mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ownerType.internalName(), + name, + desc.desc(), true); + return new ObjInv<>(em); + } + + /** + * Emit an {@code invokespecial} instruction + * + * @param the owner (super) type + * @param the JVM stack at the call site. Some may be popped as arguments + * @param the parameters expected by the method descriptor + * @param the return type from the method descriptor + * @param em the emitter + * @param ownerType the owner (super) type + * @param name the name of the method + * @param desc the method descriptor + * @param isInterface true to indicate the owner type is an interface + * @return an object to complete type checking of the arguments and, if applicable, the result + */ + static + ObjInv invokespecial(Emitter em, TRef ownerType, String name, + MthDesc desc, boolean isInterface) { + em.mv.visitMethodInsn(Opcodes.INVOKESPECIAL, ownerType.internalName(), name, + desc.desc(), isInterface); + return new ObjInv<>(em); + } + + /** + * Emit an {@code invokestatic} instruction + * + * @param the JVM stack at the call site. Some may be popped as arguments + * @param the parameters expected by the method descriptor + * @param the return type from the method descriptor + * @param em the emitter + * @param ownerType the owner type + * @param name the name of the method + * @param desc the method descriptor + * @param isInterface true to indicate the owner type is an interface + * @return an object to complete type checking of the arguments and, if applicable, the result + */ + static < + SN extends Next, + MN extends Next, + MR extends BType> + Inv invokestatic(Emitter em, TRef ownerType, String name, + MthDesc desc, boolean isInterface) { + em.mv.visitMethodInsn(Opcodes.INVOKESTATIC, ownerType.internalName(), name, + desc.desc(), isInterface); + return new Inv<>(em); + } + + /** + * Emit an {@code invokevirtual} instruction + * + * @param the owner type + * @param the JVM stack at the call site. Some may be popped as arguments + * @param the parameters expected by the method descriptor + * @param the return type from the method descriptor + * @param em the emitter + * @param ownerType the owner type + * @param name the name of the method + * @param desc the method descriptor + * @param isInterface true to indicate the owner type is an interface + * @return an object to complete type checking of the arguments and, if applicable, the result + */ + static + ObjInv invokevirtual(Emitter em, TRef ownerType, String name, + MthDesc desc, boolean isInterface) { + em.mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ownerType.internalName(), name, + desc.desc(), isInterface); + return new ObjInv<>(em); + } + + /** + * Emit an {@code ior} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ior(Emitter em) { + em.mv.visitInsn(Opcodes.IOR); + return (Emitter) em; + } + + /** + * Emit an {@code irem} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> irem(Emitter em) { + em.mv.visitInsn(Opcodes.IREM); + return (Emitter) em; + } + + /** + * Emit an {@code ireturn} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param retReq some proof of this method's required return type + * @return the dead emitter + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter ireturn(Emitter em, RetReq retReq) { + em.mv.visitInsn(Opcodes.IRETURN); + return (Emitter) em; + } + + /** + * Emit an {@code ishl} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ishl(Emitter em) { + em.mv.visitInsn(Opcodes.ISHL); + return (Emitter) em; + } + + /** + * Emit an {@code ishr} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ishr(Emitter em) { + em.mv.visitInsn(Opcodes.ISHR); + return (Emitter) em; + } + + /** + * Emit an {@code istore} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param local the target local variable + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter istore(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.ISTORE, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code isub} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> isub(Emitter em) { + em.mv.visitInsn(Opcodes.ISUB); + return (Emitter) em; + } + + /** + * Emit an {@code iushr} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> iushr(Emitter em) { + em.mv.visitInsn(Opcodes.IUSHR); + return (Emitter) em; + } + + /** + * Emit an {@code ixor} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ixor(Emitter em) { + em.mv.visitInsn(Opcodes.IXOR); + return (Emitter) em; + } + + /** + * DO NOT emit an {@code jsr} instruction + *

    + * According to Oracle's documentation, this deprecated instruction was used in the + * implementation of {@code finally} blocks prior to Java SE 6. This method is here only to + * guide users searching for the {@code jsr} opcode toward the replacement: + * {@link Misc#tryCatch(Emitter, Lbl, Lbl, TRef)}. Syntactically, trying to use this method + * should result in all sorts of compilation errors, if not on the invocation itself, then on + * anything following it in the chain. At runtime, this always throws an + * {@link UnsupportedOperationException}. + * + * @param em the emitter + * @param target the target label + * @return never + */ + static Emitter jsr__deprecated(Emitter em, Lbl target) { + throw new UnsupportedOperationException(); + } + + /** + * Emit an {@code l2d} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> l2d(Emitter em) { + em.mv.visitInsn(Opcodes.L2D); + return (Emitter) em; + } + + /** + * Emit an {@code l2f} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> l2f(Emitter em) { + em.mv.visitInsn(Opcodes.L2F); + return (Emitter) em; + } + + /** + * Emit an {@code l2i} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> l2i(Emitter em) { + em.mv.visitInsn(Opcodes.L2I); + return (Emitter) em; + } + + /** + * Emit an {@code ladd} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ladd(Emitter em) { + em.mv.visitInsn(Opcodes.LADD); + return (Emitter) em; + } + + /** + * Emit an {@code laload} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> laload(Emitter em) { + em.mv.visitInsn(Opcodes.LALOAD); + return (Emitter) em; + } + + /** + * Emit an {@code land} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> land(Emitter em) { + em.mv.visitInsn(Opcodes.LAND); + return (Emitter) em; + } + + /** + * Emit an {@code lastore} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter lastore(Emitter em) { + em.mv.visitInsn(Opcodes.LASTORE); + return (Emitter) em; + } + + /** + * Emit an {@code lcmp} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lcmp(Emitter em) { + em.mv.visitInsn(Opcodes.LCMP); + return (Emitter) em; + } + + /** + * Emit an {@code ldc} instruction for an integer + *

    + * NOTE: The underlying ASM library may emit alternative instructions at its discretion. + * + * @param the tail of the stack (...) + * @param em the emitter + * @param value the value to push + * @return the emitter with ..., value + */ + static + Emitter> ldc__i(Emitter em, int value) { + em.mv.visitLdcInsn(value); + return (Emitter) em; + } + + /** + * Emit an {@code ldc} instruction for a long + *

    + * NOTE: The underlying ASM library may emit alternative instructions at its discretion. + * + * @param the tail of the stack (...) + * @param em the emitter + * @param value the value to push + * @return the emitter with ..., value + */ + static + Emitter> ldc__l(Emitter em, long value) { + em.mv.visitLdcInsn(value); + return (Emitter) em; + } + + /** + * Emit an {@code ldc} instruction for a float + *

    + * NOTE: The underlying ASM library may emit alternative instructions at its discretion. + * + * @param the tail of the stack (...) + * @param em the emitter + * @param value the value to push + * @return the emitter with ..., value + */ + static + Emitter> ldc__f(Emitter em, float value) { + em.mv.visitLdcInsn(value); + return (Emitter) em; + } + + /** + * Emit an {@code ldc} instruction for a double + *

    + * NOTE: The underlying ASM library may emit alternative instructions at its discretion. + * + * @param the tail of the stack (...) + * @param em the emitter + * @param value the value to push + * @return the emitter with ..., value + */ + static + Emitter> ldc__d(Emitter em, double value) { + em.mv.visitLdcInsn(value); + return (Emitter) em; + } + + /** + * Emit an {@code ldc} instruction for a reference + *

    + * NOTE: Only certain reference types are permitted. Some of the permitted types are those + * leaked (API-wise) from the underlying ASM library. The underlying ASM library may emit + * alternative instructions at its discretion. + * + * @param the tail of the stack (...) + * @param em the emitter + * @param value the value to push + * @return the emitter with ..., value + */ + static + Emitter>> ldc__a(Emitter em, T value) { + em.mv.visitLdcInsn(value); + return (Emitter) em; + } + + /** + * Emit an {@code ldiv} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> ldiv(Emitter em) { + em.mv.visitInsn(Opcodes.LDIV); + return (Emitter) em; + } + + /** + * Emit an {@code lload} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @param local the handle to the local + * @return the emitter with ..., value + */ + static + Emitter> lload(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.LLOAD, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code lmul} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lmul(Emitter em) { + em.mv.visitInsn(Opcodes.LMUL); + return (Emitter) em; + } + + /** + * Emit an {@code lneg} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter> lneg(Emitter em) { + em.mv.visitInsn(Opcodes.LNEG); + return (Emitter) em; + } + + /** + * Emit a {@code lookupswitch} instruction + * + * @param the tail of the stack (...) + * @param ..., key + * @param em the emitter + * @param dflt a target label for the default case. The stack at the label must be ... + * @param cases a map of integer case value to target label. The stack at each label must be ... + * @return the dead emitter + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter lookupswitch(Emitter em, Lbl dflt, Map> cases) { + em.mv.visitLookupSwitchInsn(dflt.label(), + cases.keySet().stream().mapToInt(k -> k).toArray(), + cases.values().stream().map(Lbl::label).toArray(Label[]::new)); + return (Emitter) em; + } + + /** + * Emit an {@code lor} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lor(Emitter em) { + em.mv.visitInsn(Opcodes.LOR); + return (Emitter) em; + } + + /** + * Emit an {@code lrem} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lrem(Emitter em) { + em.mv.visitInsn(Opcodes.LREM); + return (Emitter) em; + } + + /** + * Emit an {@code lreturn} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param retReq some proof of this method's required return type + * @return the dead emitter + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter lreturn(Emitter em, RetReq retReq) { + em.mv.visitInsn(Opcodes.LRETURN); + return (Emitter) em; + } + + /** + * Emit an {@code lshl} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lshl(Emitter em) { + em.mv.visitInsn(Opcodes.LSHL); + return (Emitter) em; + } + + /** + * Emit an {@code lshr} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lshr(Emitter em) { + em.mv.visitInsn(Opcodes.LSHR); + return (Emitter) em; + } + + /** + * Emit an {@code lstore} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param local the target local variable + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter lstore(Emitter em, Local local) { + em.mv.visitVarInsn(Opcodes.LSTORE, local.index()); + return (Emitter) em; + } + + /** + * Emit an {@code lsub} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lsub(Emitter em) { + em.mv.visitInsn(Opcodes.LSUB); + return (Emitter) em; + } + + /** + * Emit an {@code lushr} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lushr(Emitter em) { + em.mv.visitInsn(Opcodes.LUSHR); + return (Emitter) em; + } + + /** + * Emit an {@code lxor} instruction + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param ..., value1, value2 + * @param em the emitter + * @return the emitter with ..., result + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter> lxor(Emitter em) { + em.mv.visitInsn(Opcodes.LXOR); + return (Emitter) em; + } + + /** + * Emit a {@code monitorenter} instruction + * + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent>> + Emitter monitorenter(Emitter em) { + em.mv.visitInsn(Opcodes.MONITORENTER); + return (Emitter) em; + } + + /** + * Emit a {@code monitorexit} instruction + * + * @param the tail of the stack (...) + * @param ..., objectref + * @param em the emitter + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent>> + Emitter monitorexit(Emitter em) { + em.mv.visitInsn(Opcodes.MONITOREXIT); + return (Emitter) em; + } + + /** + * Emit a {@code multianewarray} instruction + *

    + * NOTE: This will emit the instruction, but derivation of the resulting stack contents is not + * implemented. The user must cast the emitter to the resulting type. LATER: If required, we may + * implement this for specific dimensions. Or, we might use a pattern similar to what we used + * for method invocation to allow us an arbitrary number of stack arguments. + * + * @param em the emitter + * @param type the type of the full multidimensional array (not just the element type) + * @param dimensions the number of dimensions to allocate + * @return the emitter with unknown stack + */ + static Emitter multianewarray__unsupported(Emitter em, TRef type, int dimensions) { + em.mv.visitMultiANewArrayInsn(type.internalName(), dimensions); + return em; + } + + /** + * Emit a {@code new} instruction + * + * @param the type of object + * @param the tail of the stack (...) + * @param em the emitter + * @param type the type of object + * @return the emitter with ..., objectref (uninitialized) + * @implNote We considered using a separate {@code URef} type to indicate an uninitialized + * reference; however, this would fail for the standard {@code new-dup-invokespecial} + * sequence, as the reference remaining on the stack would appear uninitialized when + * it is in fact initialized. + */ + static , + N extends Next> + Emitter> new_(Emitter em, T type) { + em.mv.visitTypeInsn(Opcodes.NEW, type.internalName()); + return (Emitter) em; + } + + /** + * Emit a {@code newarray} instruction + * + * @param the resulting array type + * @param the (primitive) element type + * @param the tail of the stack (...) + * @param ..., count + * @param em the emitter + * @param elemType the element type + * @return the emitter with ..., arrayref + */ + static , + N1 extends Next, + N0 extends Ent> + Emitter>> newarray(Emitter em, ET elemType) { + em.mv.visitIntInsn(Opcodes.NEWARRAY, elemType.t()); + return (Emitter) em; + } + + /** + * Emit a {@code nop} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @return the emitter with ... + */ + static + Emitter nop(Emitter em) { + em.mv.visitInsn(Opcodes.NOP); + return em; + } + + /** + * Emit a {@code pop} instruction + * + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter pop(Emitter em) { + em.mv.visitInsn(Opcodes.POP); + return (Emitter) em; + } + + /** + * Emit a {@code pop2} instruction to pop two operands (Form 1) + * + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ... + */ + static < + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter pop2__11(Emitter em) { + em.mv.visitInsn(Opcodes.POP2); + return (Emitter) em; + } + + /** + * Emit a {@code pop2} instruction to pop one operand (Form 2) + * + * @param the tail of the stack (...) + * @param ..., value1 + * @param em the emitter + * @return the emitter with ... + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter pop2__2(Emitter em) { + em.mv.visitInsn(Opcodes.POP2); + return (Emitter) em; + } + + /** + * Emit a {@code putfield} instruction + * + * @param the owner type + * @param the type of the object on the stack owning the field + * @param the type of the field + * @param the tail of the stack (...) + * @param ..., objectref + * @param ..., objectref, value + * @param em the emitter + * @param owner the owner type + * @param name the name of the field + * @param type the type of the field + * @return the emitter with ... + */ + static , FT extends BNonVoid, + N2 extends Next, + N1 extends Ent, + N0 extends Ent> + Emitter putfield(Emitter em, TRef owner, String name, + FT type) { + em.mv.visitFieldInsn(Opcodes.PUTFIELD, owner.internalName(), name, + type.type().getDescriptor()); + return (Emitter) em; + } + + /** + * Emit a {@code putstatic} instruction + * + * @param the type of the field + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param owner the owner type + * @param name the name of the field + * @param type the type of the field + * @return the emitter with ... + */ + static > + Emitter putstatic(Emitter em, TRef owner, String name, + FT type) { + em.mv.visitFieldInsn(Opcodes.PUTSTATIC, owner.internalName(), name, + type.type().getDescriptor()); + return (Emitter) em; + } + + /** + * DO NOT emit an {@code ret} instruction + *

    + * According to Oracle's documentation, this deprecated instruction was used in the + * implementation of {@code finally} blocks prior to Java SE 6. You may actually be searching + * for the {@link #return_(Emitter, RetReq)} method. This method is here only to guide users + * searching for the {@code ret} opcode toward the replacement: + * {@link Misc#tryCatch(Emitter, Lbl, Lbl, TRef)}. Syntactically, trying to use this method + * should result in all sorts of compilation errors, if not on the invocation itself, then on + * anything following it in the chain. At runtime, this always throws an + * {@link UnsupportedOperationException}. + * + * @param em the emitter + * @param local the local variable (NOTE: {@code returnAddress} is not a supported type) + * @return never + */ + static Emitter ret__deprecated(Emitter em, Local local) { + throw new UnsupportedOperationException(); + } + + /** + * Emit a {@code return} instruction + * + * @param the tail of the stack (...) + * @param em the emitter + * @param retReq some proof of this method's required return type + * @return the dead emitter + */ + static + Emitter return_(Emitter em, RetReq retReq) { + em.mv.visitInsn(Opcodes.RETURN); + return (Emitter) em; + } + + /** + * Emit an {@code saload} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param em the emitter + * @return the emitter with ..., value + */ + static < + N2 extends Next, + N1 extends Ent>, + N0 extends Ent> + Emitter> saload(Emitter em) { + em.mv.visitInsn(Opcodes.SALOAD); + return (Emitter) em; + } + + /** + * Emit an {@code sastore} instruction + * + * @param the tail of the stack (...) + * @param ..., arrayref + * @param ..., arrayref, index + * @param ..., arrayref, index, value + * @param em the emitter + * @return the emitter with ... + */ + static < + N3 extends Next, + N2 extends Ent>, + N1 extends Ent, + N0 extends Ent> + Emitter sastore(Emitter em) { + em.mv.visitInsn(Opcodes.SASTORE); + return (Emitter) em; + } + + /** + * Emit a {@code swap} instruction + * + * @param the type of value2 on the stack + * @param the type of value1 on the stack + * @param the tail of the stack (...) + * @param ..., value2 + * @param ..., value2, value1 + * @param em the emitter + * @return the emitter with ..., value1, value2 + */ + static , + N0 extends Ent> + Emitter, T2>> swap(Emitter em) { + em.mv.visitInsn(Opcodes.SWAP); + return (Emitter) em; + } + + /** + * Emit a {@code tableswitch} instruction + * + * @param the tail of the stack (...) + * @param ..., index + * @param em the emitter + * @param low the low index + * @param dflt a target label for the default case. The stack at the label must be ... + * @param cases a list of target labels. The stack at each label must be ... + * @return the dead emitter + */ + static < + N1 extends Next, + N0 extends Ent> + Emitter tableswitch(Emitter em, int low, Lbl dflt, List> cases) { + int high = low + cases.size() - 1; // inclusive + em.mv.visitTableSwitchInsn(low, high, dflt.label(), + cases.stream().map(Lbl::label).toArray(Label[]::new)); + return (Emitter) em; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/RootScope.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/RootScope.java new file mode 100644 index 0000000000..0b38ab20d1 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/RootScope.java @@ -0,0 +1,86 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.emu.jit.gen.util.Emitter.Bot; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BNonVoid; + +/** + * The implementation of the root scope for local variable declarations + * + * @param the stack at scope start and finish (not really enforced). This should be {@link Bot} + * for the actual root scope, but this class is extended by {@link ChildScope}. + */ +class RootScope implements Scope { + protected final Emitter em; + protected final Lbl start; + + protected int nextLocal; + protected Scope childScope; + protected boolean closed = false; + + protected final List> vars = new ArrayList<>(); + + @SuppressWarnings("unchecked") + RootScope(Emitter em, int nextLocal) { + this.em = (Emitter) em; + this.nextLocal = nextLocal; + + this.start = Lbl.place(this.em).lbl(); + } + + @Override + public SubScope sub() { + return new ChildScope<>(em, this); + } + + protected void declVars() { + var end = em.emit(Lbl::place).lbl(); + for (Local v : vars) { + em.emit(Local::decl, v, start, end); + } + } + + @Override + public Local decl(T type, String name) { + if (childScope != null) { + throw new IllegalStateException("There is a child scope active."); + } + int next = next(type); + Local local = Local.of(type, name, next); + vars.add(local); + return local; + } + + protected int next(BNonVoid type) { + int next = nextLocal; + nextLocal += type.slots(); + return next; + } + + @Override + public void close() { + if (closed) { + return; + } + declVars(); + closed = true; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Scope.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Scope.java new file mode 100644 index 0000000000..9e3045bb18 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Scope.java @@ -0,0 +1,59 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import ghidra.pcode.emu.jit.gen.util.Types.BNonVoid; + +/** + * A scope for local variable declarations, but not treated as a resource + */ +public interface Scope { + + /** + * Open a child scope of this scope, usually for temporary declarations/allocations + *

    + * The variables declared in this scope (see {@link #decl(BNonVoid, String)}) are reserved only + * with the scope of the {@code try-with-resources} block that ought to be used to managed this + * resource. Local variables meant to be in scope for the method's full scope should just be + * declared in the {@linkplain Emitter#rootScope() root scope}. + * + * @return the child scope + */ + SubScope sub(); + + /** + * Declare a local variable in this scope + *

    + * This assigns the local the next available index, being careful to increment the index + * according to the category of the given type. When this scope is closed, that index is reset + * to what is was at the start of this scope. + * + * @param the type of the variable + * @param type the type of the variable + * @param name the name of the variable + * @return the handle to the variable + */ + Local decl(T type, String name); + + /** + * Close this scope + *

    + * This resets the index to what it was at the start of this scope. In general, there is no need + * for the user to call this on the root scope. This is automatically done by + * {@link Misc#finish(Emitter)}. + */ + void close(); +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/SubScope.java similarity index 67% rename from Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.java rename to Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/SubScope.java index bfd398b686..60033daa09 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/SubScope.java @@ -13,15 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.jit.gen.op; - -import ghidra.pcode.emu.jit.op.JitBinOp; +package ghidra.pcode.emu.jit.gen.util; /** - * An extension for integer binary operators - * - * @param the class of p-code op node in the use-def graph + * A sub scope for local variable declarations */ -public interface IntBinOpGen extends BinOpGen { - // Intentionally empty +public interface SubScope extends Scope, AutoCloseable { + @Override + void close(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Types.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Types.java new file mode 100644 index 0000000000..dd10497e2b --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/util/Types.java @@ -0,0 +1,461 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import org.objectweb.asm.*; + +/** + * A namespace for types describing Java types + */ +public interface Types { + /** The {@code void} type */ + TVoid T_VOID = TVoid.INSTANCE; + /** The {@code boolean} type */ + TBool T_BOOL = TBool.INSTANCE; + /** The {@code byte} type */ + TByte T_BYTE = TByte.INSTANCE; + /** The {@code char} type */ + TChar T_CHAR = TChar.INSTANCE; + /** The {@code short} type */ + TShort T_SHORT = TShort.INSTANCE; + /** The {@code int} type */ + TInt T_INT = TInt.INSTANCE; + /** The {@code long} type */ + TLong T_LONG = TLong.INSTANCE; + /** The {@code float} type */ + TFloat T_FLOAT = TFloat.INSTANCE; + /** The {@code double} type */ + TDouble T_DOUBLE = TDouble.INSTANCE; + + /** The {@code boolean[]} type */ + TRef T_BOOL_ARR = refOf(boolean[].class); + /** The {@code byte[]} type */ + TRef T_BYTE_ARR = refOf(byte[].class); + /** The {@code char[]} type */ + TRef T_CHAR_ARR = refOf(char[].class); + /** The {@code short[]} type */ + TRef T_SHORT_ARR = refOf(short[].class); + /** The {@code int[]} type */ + TRef T_INT_ARR = refOf(int[].class); + /** The {@code long[]} type */ + TRef T_LONG_ARR = refOf(long[].class); + /** The {@code float[]} type */ + TRef T_FLOAT_ARR = refOf(float[].class); + /** The {@code double[]} type */ + TRef T_DOUBLE_ARR = refOf(double[].class); + + /** + * Create a type describing a reference of the given class (or interface) type + * + * @param the type of the Java class + * @param cls the class + * @return the type + */ + static TRef refOf(Class cls) { + return TRef.of(cls); + } + + /** + * Create a type describing an extension of the given class (or interface) type + *

    + * This is used when the type is itself dynamically generated, but it is at least known to + * extend a type defined by compiled Java source. This is best used with a type variable on the + * class in the Java source that generates the type. Unfortunately, that variable may bleed into + * any number of classes and methods which support that generation, esp., since this is almost + * always required to describe the type of {@code this}, and {@code this} is frequently accessed + * in generated code. Conventionally, the type variable is called {@code THIS}: + * + *

    +	 * class MyGenerator<THIS extends MyIf> {
    +	 * 	private final TRef<THIS> typeThis = refExtends(MyIf.class, generateDesc());
    +	 * }
    +	 * 
    + * + * @param the super type + * @param the type variable used to refer to the extension + * @param cls the class of the super type + * @param desc the internal name of the actual generated extension type + * @return the type + */ + static TRef refExtends(Class cls, String desc) { + return TRef.ofExtends(cls, desc); + } + + /** + * See {@link #refExtends(Class, String)} + * + * @param the super type + * @param the type variable used to refer to the extension + * @param st the super type + * @param desc the internal name of the actual generated extension type + * @return the type + */ + static TRef refExtends(TRef st, String desc) { + return TRef.ofExtends(st.cls, desc); + } + + /** + * Create a type describing a reflected extension of a given class (or interface) type + *

    + * This is used when the type is only known through reflection, but it is at least known to + * extend some other fixed type. This is best used with a type variable on the method that + * generates code wrt. the reflected class. + * + * @param the super type + * @param the type variable used to refer to the extension + * @param st the super type + * @param reflected the reflected class + * @return the type + */ + static TRef refExtends(TRef st, Class reflected) { + return TRef.ofExtends(st.cls, Type.getDescriptor(reflected)); + } + + /** + * Types that may be returned by a method in Java source + *

    + * This is essentially "all types including {@code void}" as far as Java is concerned. + */ + public interface SType { + /** + * Get the ASM type for this type + * + * @return the type + */ + Type type(); + + /** + * Get the Java class to describe this type + *

    + * For generated types, this may instead be a suitable super type. + * + * @return the class + */ + Class cls(); + } + + /** + * Types that may be ascribed to a variable in Java source + *

    + * This is essentially "all types except {@code void}" as far as Java is concerned. + */ + public interface SNonVoid extends SType { + } + + /** + * The primitive types that may be ascribed to a variable in Java source + *

    + * This is essentially "all non-reference types" as far as Java is concerned. + * + * @param the array type for which this primitive is the element type + */ + public interface SPrim extends SNonVoid { + /** + * The type id, as in {@link MethodVisitor#visitIntInsn(int, int)} for + * {@link Opcodes#NEWARRAY}, e.g., {@link Opcodes#T_INT}. + * + * @return the type id + */ + int t(); + } + + /** + * The types that may be ascribed to local variables in JVM bytecode, and {@code void} + *

    + * This includes {@code void}, all reference types, but only the primitive types {@code int}, + * {@code float}, {@code long}, and {@code double}. The other primitive types are stored in + * {@code int} local variables. + */ + public interface BType extends SType { + @Override + Type type(); + + /** + * Get the internal name of the type + * + * @return the internal name + */ + default String internalName() { + return type().getInternalName(); + } + } + + /** + * The types that may be ascribed to local variables in JVM bytecode + */ + public interface BNonVoid extends BType, SNonVoid { + /** + * {@return the number of slots (stack entries or local indices) taken by this type} + */ + int slots(); + } + + /** + * The primitive types that may be ascribed to local variables in JVM bytecode + *

    + * This includes only {@code int}, {@code float}, {@code long}, and {@code double}. + * + * @param the array type for which this primitive is the element type + */ + public interface BPrim extends BNonVoid, SPrim { + @Override + int t(); + } + + /** + * The {@code void} type + */ + public enum TVoid implements BType { + /** Singleton */ + INSTANCE; + + @Override + public Type type() { + return Type.VOID_TYPE; + } + + @Override + public Class cls() { + return void.class; + } + } + + /** + * Category 1 types as defined by the JVM specification + *

    + * This includes reference types, {@code int}, and {@code float}. + */ + public interface TCat1 extends BNonVoid { + @Override + default int slots() { + return 1; + } + } + + /** + * Reference types + * + * @param the type + * @param cls the class for the type. For generated types, this may be a super type. + * @param type the ASM type + */ + public record TRef(Class cls, Type type) implements TCat1 { + + static TRef of(Class cls) { + return new TRef<>(cls, Type.getType(cls)); + } + + static TRef ofExtends(Class cls, String desc) { + return new TRef(cls, Type.getType(desc)); + } + } + + /** + * The {@code boolean} type + */ + public enum TBool implements SPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_BOOLEAN; + } + + @Override + public Type type() { + return Type.BOOLEAN_TYPE; + } + + @Override + public Class cls() { + return boolean.class; + } + } + + /** + * The {@code byte} type + */ + public enum TByte implements SPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_BYTE; + } + + @Override + public Type type() { + return Type.BYTE_TYPE; + } + + @Override + public Class cls() { + return byte.class; + } + } + + /** + * The {@code char} type + */ + public enum TChar implements SPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_CHAR; + } + + @Override + public Type type() { + return Type.CHAR_TYPE; + } + + @Override + public Class cls() { + return char.class; + } + } + + /** + * The {@code short} type + */ + public enum TShort implements SPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_SHORT; + } + + @Override + public Type type() { + return Type.SHORT_TYPE; + } + + @Override + public Class cls() { + return short.class; + } + } + + /** + * The {@code int} type + */ + public enum TInt implements TCat1, BPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_INT; + } + + @Override + public Type type() { + return Type.INT_TYPE; + } + + @Override + public Class cls() { + return int.class; + } + } + + /** + * The {@code float} type + */ + public enum TFloat implements TCat1, BPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_FLOAT; + } + + @Override + public Type type() { + return Type.FLOAT_TYPE; + } + + @Override + public Class cls() { + return float.class; + } + } + + /** + * Category 2 types as defined by the JVM specification + *

    + * This includes {@code long} and {@code double}. + */ + public interface TCat2 extends BNonVoid { + @Override + default int slots() { + return 2; + } + } + + /** + * The {@code long} type + */ + public enum TLong implements TCat2, BPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_LONG; + } + + @Override + public Type type() { + return Type.LONG_TYPE; + } + + @Override + public Class cls() { + return long.class; + } + } + + /** + * The {@code double} type + */ + public enum TDouble implements TCat2, BPrim { + /** Singleton */ + INSTANCE; + + @Override + public int t() { + return Opcodes.T_DOUBLE; + } + + @Override + public Type type() { + return Type.DOUBLE_TYPE; + } + + @Override + public Class cls() { + return double.class; + } + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java index 4eb302017c..fc41a44f3e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ConstValGen.java @@ -15,14 +15,20 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; +import java.math.BigInteger; + import org.objectweb.asm.Opcodes; -import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType.*; -import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.var.JitConstVal; /** @@ -37,28 +43,81 @@ public enum ConstValGen implements ValGen { GEN; @Override - public void generateValInitCode(JitCodeGenerator gen, JitConstVal v, MethodVisitor iv) { + public Emitter genValInit(Emitter em, + Local> localThis, JitCodeGenerator gen, JitConstVal v) { + return em; } @Override - public JitType generateValReadCode(JitCodeGenerator gen, JitConstVal v, JitTypeBehavior typeReq, - Ext ext, MethodVisitor rv) { - JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); - switch (type) { - case IntJitType t -> rv.visitLdcInsn(v.value().intValue()); - case LongJitType t -> rv.visitLdcInsn(v.value().longValue()); - case FloatJitType t -> rv.visitLdcInsn(Float.intBitsToFloat(v.value().intValue())); - case DoubleJitType t -> rv.visitLdcInsn(Double.longBitsToDouble(v.value().longValue())); - case MpIntJitType t -> { - // Push most significant first, so least is at top of stack - int count = t.legsAlloc(); - for (int i = 0; i < count; i++) { - int leg = v.value().shiftRight(Integer.SIZE * (count - 1 - i)).intValue(); - rv.visitLdcInsn(leg); - } - } + public , JT extends SimpleJitType, + N extends Next> Emitter> genReadToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitConstVal v, JT type, + Ext ext) { + return switch (type) { + case IntJitType t -> em + .emit(Op::ldc__i, v.value().intValue()) + .emit(ValGen::castBack, type, t); + case LongJitType t -> em + .emit(Op::ldc__l, v.value().longValue()) + .emit(ValGen::castBack, type, t); + case FloatJitType t -> em + .emit(Op::ldc__f, Float.intBitsToFloat(v.value().intValue())) + .emit(ValGen::castBack, type, t); + case DoubleJitType t -> em + .emit(Op::ldc__d, Double.longBitsToDouble(v.value().longValue())) + .emit(ValGen::castBack, type, t); default -> throw new AssertionError(); + }; + } + + @Override + public OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, JitConstVal v, + MpIntJitType type, Ext ext, Scope scope) { + return new OpndEm<>(Opnd.constOf(type, v.value()), em); + } + + @Override + public Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, JitConstVal v, MpIntJitType type, int leg, + Ext ext) { + BigInteger value = v.value(); + int legVal = value.shiftRight(leg * Integer.SIZE).intValue(); + return em + .emit(Op::ldc__i, legVal); + } + + @Override + public Emitter>> + genReadToArray(Emitter em, Local> localThis, JitCodeGenerator gen, + JitConstVal v, MpIntJitType type, Ext ext, Scope scope, int slack) { + int legCount = type.legsAlloc(); + var ckArr = em + .emit(Op::ldc__i, legCount + slack) + .emit(Op::newarray, Types.T_INT); + BigInteger value = v.value(); + for (int i = 0; i < legCount; i++) { + int leg = value.intValue(); + if (leg != 0) { + ckArr = ckArr + .emit(Op::dup) + .emit(Op::ldc__i, i) + .emit(Op::ldc__i, leg) + .emit(Op::iastore); + } } - return type; + return ckArr; + } + + @Override + public Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, JitConstVal v) { + return em.emit(Op::ldc__i, v.value().equals(BigInteger.ZERO) ? 0 : 1); + } + + @Override + public ValGen subpiece(int byteShift, int maxByteSize) { + throw new AssertionError("Sleigh compiler generated subpiece of a constant?"); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java index 3e9724e682..e89f8cf07d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/DirectMemoryVarGen.java @@ -15,11 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.var.JitDirectMemoryVar; /** @@ -28,13 +33,35 @@ import ghidra.pcode.emu.jit.var.JitDirectMemoryVar; *

    * This prohibits generation of code to write the variable. */ -public enum DirectMemoryVarGen implements MemoryVarGen { - /** Singleton */ - GEN; +public interface DirectMemoryVarGen extends MemoryVarGen { @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitDirectMemoryVar v, JitType type, - Ext ext, MethodVisitor rv) { + default , JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitDirectMemoryVar v, + JT type, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + default Emitter genWriteFromOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, + JitDirectMemoryVar v, Opnd opnd, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + default >> + Emitter genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, JitDirectMemoryVar v, MpIntJitType type, Ext ext, + Scope scope) { + throw new AssertionError(); + } + + @Override + default Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, + JitDirectMemoryVar v) { throw new AssertionError(); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java index c9f841a575..b55181b1a5 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/FailValGen.java @@ -15,12 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.var.JitFailVal; /** @@ -31,12 +35,48 @@ public enum FailValGen implements ValGen { GEN; @Override - public void generateValInitCode(JitCodeGenerator gen, JitFailVal v, MethodVisitor iv) { + public Emitter genValInit(Emitter em, + Local> localThis, JitCodeGenerator gen, JitFailVal v) { + return em; } @Override - public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, JitTypeBehavior typeReq, - Ext ext, MethodVisitor rv) { + public , JT extends SimpleJitType, + N extends Next> Emitter> genReadToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitFailVal v, JT type, + Ext ext) { throw new AssertionError(); } + + @Override + public OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, JitFailVal v, + MpIntJitType type, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + public Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, JitFailVal v, MpIntJitType type, int leg, Ext ext) { + throw new AssertionError(); + } + + @Override + public Emitter>> + genReadToArray(Emitter em, Local> localThis, JitCodeGenerator gen, + JitFailVal v, MpIntJitType type, Ext ext, Scope scope, int slack) { + throw new AssertionError(); + } + + @Override + public Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, JitFailVal v) { + throw new AssertionError(); + } + + @Override + public ValGen subpiece(int byteShift, int maxByteSize) { + return this; + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java index 01d4dca5e8..cc1dcf81b4 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/InputVarGen.java @@ -15,11 +15,16 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.var.JitInputVar; /** @@ -28,13 +33,34 @@ import ghidra.pcode.emu.jit.var.JitInputVar; *

    * This prohibits generation of code to write the variable. */ -public enum InputVarGen implements LocalVarGen { - /** Singleton */ - GEN; +public interface InputVarGen extends LocalVarGen { @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, Ext ext, - MethodVisitor rv) { + default , JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitInputVar v, JT type, + Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + default Emitter genWriteFromOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, JitInputVar v, + Opnd opnd, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + default >> + Emitter genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, JitInputVar v, MpIntJitType type, Ext ext, + Scope scope) { + throw new AssertionError(); + } + + @Override + default Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, JitInputVar v) { throw new AssertionError(); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java index 394cb08ea4..8e4776a05a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalOutVarGen.java @@ -15,25 +15,44 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler; -import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.var.JitLocalOutVar; /** * The generator for a local variable that is defined within the passage. */ -public enum LocalOutVarGen implements LocalVarGen { - /** Singleton */ - GEN; +public interface LocalOutVarGen extends LocalVarGen { @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitLocalOutVar v, JitType type, - Ext ext, MethodVisitor rv) { - VarHandler handler = gen.getAllocationModel().getHandler(v); - handler.generateStoreCode(gen, type, ext, rv); + default , JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitLocalOutVar v, JT type, + Ext ext, Scope scope) { + return getHandler(gen, v).genStoreFromStack(em, gen, type, ext, scope); + } + + @Override + default Emitter genWriteFromOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, + JitLocalOutVar v, Opnd opnd, Ext ext, Scope scope) { + return getHandler(gen, v).genStoreFromOpnd(em, gen, opnd, ext, scope); + } + + @Override + default >> + Emitter genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, JitLocalOutVar v, MpIntJitType type, Ext ext, + Scope scope) { + return getHandler(gen, v).genStoreFromArray(em, gen, type, ext, scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java index a2f840ae79..c3929ec76c 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/LocalVarGen.java @@ -15,14 +15,19 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; +import ghidra.pcode.emu.jit.alloc.VarHandler; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.var.JitVarnodeVar; /** @@ -36,17 +41,57 @@ import ghidra.pcode.emu.jit.var.JitVarnodeVar; * @param the class of p-code variable node in the use-def graph */ public interface LocalVarGen extends VarGen { - @Override - default void generateValInitCode(JitCodeGenerator gen, V v, MethodVisitor iv) { - gen.getAllocationModel().getHandler(v).generateInitCode(gen, iv); + + /** + * Get the handler for a given p-code variable + *

    + * This is made to be overridden for the implementation of subpiece handlers. + * + * @param gen the code generator + * @param v the value + * @return the handler + */ + default VarHandler getHandler(JitCodeGenerator gen, V v) { + return gen.getAllocationModel().getHandler(v); } @Override - default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, - MethodVisitor rv) { - VarHandler handler = gen.getAllocationModel().getHandler(v); - JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); - handler.generateLoadCode(gen, type, ext, rv); - return type; + default Emitter genValInit(Emitter em, + Local> localThis, JitCodeGenerator gen, V v) { + return em; + } + + @Override + default , JT extends SimpleJitType, + N extends Next> Emitter> genReadToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, V v, JT type, Ext ext) { + return getHandler(gen, v).genLoadToStack(em, gen, type, ext); + } + + @Override + default OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, V v, + MpIntJitType type, Ext ext, Scope scope) { + return getHandler(gen, v).genLoadToOpnd(em, gen, type, ext, scope); + } + + @Override + default Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, V v, MpIntJitType type, int leg, Ext ext) { + return getHandler(gen, v).genLoadLegToStack(em, gen, type, leg, ext); + } + + @Override + default Emitter>> + genReadToArray(Emitter em, Local> localThis, JitCodeGenerator gen, + V v, MpIntJitType type, Ext ext, Scope scope, int slack) { + return getHandler(gen, v).genLoadToArray(em, gen, type, ext, scope, slack); + } + + @Override + default Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, V v) { + return getHandler(gen, v).genLoadToBool(em, gen); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java index 24eb73ce3d..4d9d65773e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryOutVarGen.java @@ -15,23 +15,48 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; - -import ghidra.pcode.emu.jit.analysis.JitType; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.access.AccessGen; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.var.JitMemoryOutVar; /** * The generator for a memory output variable. */ -public enum MemoryOutVarGen implements MemoryVarGen { - /** Singleton */ - GEN; +public interface MemoryOutVarGen extends MemoryVarGen { @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, Ext ext, - MethodVisitor rv) { - VarGen.generateValWriteCodeDirect(gen, v, type, rv); + default , JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitMemoryOutVar v, JT type, + Ext ext, Scope scope) { + return AccessGen.lookupSimple(gen.getAnalysisContext().getEndian(), type) + .genWriteFromStack(em, localThis, gen, getVarnode(gen, v)); + } + + @Override + default Emitter genWriteFromOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, + JitMemoryOutVar v, Opnd opnd, Ext ext, Scope scope) { + return AccessGen.lookupMp(gen.getAnalysisContext().getEndian()) + .genWriteFromOpnd(em, localThis, gen, opnd, getVarnode(gen, v)); + } + + @Override + default >> + Emitter genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, JitMemoryOutVar v, MpIntJitType type, Ext ext, + Scope scope) { + return AccessGen.lookupMp(gen.getAnalysisContext().getEndian()) + .genWriteFromArray(em, localThis, gen, getVarnode(gen, v), scope); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java index 8f441c5401..454ebf9a97 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MemoryVarGen.java @@ -15,15 +15,20 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.analysis.JitType.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; -import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; +import ghidra.pcode.emu.jit.gen.access.AccessGen; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.var.JitVarnodeVar; +import ghidra.program.model.pcode.Varnode; /** * The generator for memory variables. @@ -31,19 +36,87 @@ import ghidra.pcode.emu.jit.var.JitVarnodeVar; *

    * These variables affect the {@link JitBytesPcodeExecutorState state} immediately, i.e., they are * not birthed or retired as local JVM variables. The generator delegates to the appropriate - * {@link TypedAccessGen} for this variable's varnode and assigned type. + * {@link AccessGen} for this variable's varnode and assigned type. * * @param the class of p-code variable node in the use-def graph */ public interface MemoryVarGen extends VarGen { - @Override - default void generateValInitCode(JitCodeGenerator gen, V v, MethodVisitor iv) { - VarGen.generateValInitCode(gen, v.varnode()); + + /** + * Get the varnode actually accessed for the given p-code variable + *

    + * This is made to be overridden for the implementation of subpiece access. + * + * @param gen the code generator + * @param v the value + * @return the varnode + */ + default Varnode getVarnode(JitCodeGenerator gen, V v) { + return v.varnode(); } @Override - default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, - MethodVisitor rv) { - return VarGen.generateValReadCodeDirect(gen, v, typeReq, rv); + default Emitter genValInit(Emitter em, + Local> localThis, JitCodeGenerator gen, V v) { + return VarGen.genVarnodeInit(em, gen, getVarnode(gen, v)); + } + + @Override + default , JT extends SimpleJitType, + N extends Next> Emitter> genReadToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, V v, JT type, Ext ext) { + return VarGen.genReadValDirectToStack(em, localThis, gen, type, getVarnode(gen, v)); + } + + @Override + default OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, V v, + MpIntJitType type, Ext ext, Scope scope) { + return AccessGen.lookupMp(gen.getAnalysisContext().getEndian()) + .genReadToOpnd(em, localThis, gen, getVarnode(gen, v), type, ext, scope); + } + + @Override + default Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, V v, MpIntJitType type, int leg, Ext ext) { + Varnode vn = getVarnode(gen, v); + if (vn.getSize() <= leg * Integer.BYTES) { + return switch (ext) { + case ZERO -> em + .emit(Op::ldc__i, 0); + case SIGN -> { + Varnode msbVn = switch (gen.getAnalysisContext().getEndian()) { + case BIG -> new Varnode(vn.getAddress(), 1); + case LITTLE -> new Varnode(vn.getAddress().add(vn.getSize() - 1), 1); + }; + yield em + .emit(VarGen::genReadValDirectToStack, localThis, gen, IntJitType.I1, + msbVn) + .emit(Op::ldc__i, Integer.SIZE - Byte.SIZE) + .emit(Op::ishl) + .emit(Op::ldc__i, Integer.SIZE - 1) + .emit(Op::ishr); + } + }; + } + Varnode subVn = JitDataFlowArithmetic.subPieceVn(gen.getAnalysisContext().getEndian(), vn, + leg * Integer.BYTES, Integer.BYTES); + return VarGen.genReadValDirectToStack(em, localThis, gen, type.legTypesLE().get(leg), + subVn); + } + + @Override + default Emitter>> + genReadToArray(Emitter em, Local> localThis, JitCodeGenerator gen, + V v, MpIntJitType type, Ext ext, Scope scope, int slack) { + return AccessGen.lookupMp(gen.getAnalysisContext().getEndian()) + .genReadToArray(em, localThis, gen, getVarnode(gen, v), type, ext, scope, slack); + } + + @Override + default Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, V v) { + return AccessGen.genReadToBool(em, localThis, gen, getVarnode(gen, v)); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java index 26c608f7f6..539dc9f545 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/MissingVarGen.java @@ -16,16 +16,21 @@ package ghidra.pcode.emu.jit.gen.var; import static ghidra.pcode.emu.jit.gen.GenConsts.MDESC_ASSERTION_ERROR__$INIT; -import static ghidra.pcode.emu.jit.gen.GenConsts.NAME_ASSERTION_ERROR; -import static org.objectweb.asm.Opcodes.*; +import static ghidra.pcode.emu.jit.gen.GenConsts.T_ASSERTION_ERROR; -import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import ghidra.pcode.emu.jit.analysis.JitType; -import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.Methods.Inv; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.JitPhiOp; import ghidra.pcode.emu.jit.var.JitMissingVar; @@ -38,7 +43,7 @@ import ghidra.pcode.emu.jit.var.JitMissingVar; * up as an output, so we prohibit any attempt to generate code that writes to a missing variable. * However, we wait until run time to make that assertion about reads. In theory, it's possible the * generator will generate unreachable code that reads from a variable; however, that code is - * unreachable. First how does this happen? Second, what if it does? + * unreachable. First, how does this happen? Second, what if it does? * *

    * To answer the first question, we note that the passage decoder should never decode any statically @@ -57,31 +62,90 @@ public enum MissingVarGen implements VarGen { GEN; @Override - public void generateValInitCode(JitCodeGenerator gen, JitMissingVar v, MethodVisitor iv) { + public Emitter genValInit(Emitter em, + Local> localThis, JitCodeGenerator gen, JitMissingVar v) { + return em; + } + + private Emitter genThrow(Emitter em, JitMissingVar v) { + return em + .emit(Op::new_, T_ASSERTION_ERROR) + .emit(Op::dup) + .emit(Op::ldc__a, "Tried to read " + v) + .emit(Op::invokespecial, T_ASSERTION_ERROR, "", MDESC_ASSERTION_ERROR__$INIT, + false) + .step(Inv::takeRefArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::athrow); } @Override - public JitType generateValReadCode(JitCodeGenerator gen, JitMissingVar v, - JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) { - // [...] - rv.visitTypeInsn(NEW, NAME_ASSERTION_ERROR); - // [...,error:NEW] - rv.visitInsn(DUP); - // [...,error:NEW,error:NEW] - rv.visitLdcInsn("Tried to read " + v); - // [...,error:NEW,error:NEW,message] - rv.visitMethodInsn(INVOKESPECIAL, NAME_ASSERTION_ERROR, "", - MDESC_ASSERTION_ERROR__$INIT, false); - // [...,error] - rv.visitInsn(ATHROW); - // [...] - JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); - return type; + public , JT extends SimpleJitType, + N extends Next> Emitter> genReadToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitMissingVar v, JT type, + Ext ext) { + genThrow(em, v); + return null; } @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, Ext ext, - MethodVisitor rv) { + public OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, JitMissingVar v, + MpIntJitType type, Ext ext, Scope scope) { + genThrow(em, v); + return null; + } + + @Override + public Emitter> + genReadLegToStack(Emitter em, Local> localThis, + JitCodeGenerator gen, JitMissingVar v, MpIntJitType type, int leg, + Ext ext) { + genThrow(em, v); + return null; + } + + @Override + public Emitter>> + genReadToArray(Emitter em, Local> localThis, JitCodeGenerator gen, + JitMissingVar v, MpIntJitType type, Ext ext, Scope scope, int slack) { + genThrow(em, v); + return null; + } + + @Override + public , JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JitMissingVar v, JT type, + Ext ext, Scope scope) { throw new AssertionError(); } + + @Override + public Emitter genWriteFromOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, JitMissingVar v, + Opnd opnd, Ext ext, Scope scope) { + throw new AssertionError(); + } + + @Override + public >> + Emitter genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, JitMissingVar v, MpIntJitType type, Ext ext, + Scope scope) { + throw new AssertionError(); + } + + @Override + public Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, + JitMissingVar v) { + throw new AssertionError(); + } + + @Override + public ValGen subpiece(int byteShift, int maxByteSize) { + return this; + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubDirectMemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubDirectMemoryVarGen.java new file mode 100644 index 0000000000..8d4220f99c --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubDirectMemoryVarGen.java @@ -0,0 +1,27 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitDirectMemoryVar; + +/** + * The generator for a subpiece of a direct memory variable + * + * @param byteOffset the number of bytes to the right of the subpiece + * @param maxByteSize the size of the subpiece + */ +public record SubDirectMemoryVarGen(int byteOffset, int maxByteSize) + implements SubMemoryVarGen, DirectMemoryVarGen {} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubInputVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubInputVarGen.java new file mode 100644 index 0000000000..b0392bd786 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubInputVarGen.java @@ -0,0 +1,27 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitInputVar; + +/** + * The generator for a subpiece of a local variable that is input to the passage. + * + * @param byteOffset the number of bytes to the right of the subpiece + * @param maxByteSize the size of the subpiece + */ +public record SubInputVarGen(int byteOffset, int maxByteSize) + implements SubLocalVarGen, InputVarGen {} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubLocalOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubLocalOutVarGen.java new file mode 100644 index 0000000000..f48a547f0c --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubLocalOutVarGen.java @@ -0,0 +1,27 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitLocalOutVar; + +/** + * The generator for a subpiece of a local variable that is defined within the passage. + * + * @param byteOffset the number of bytes to the right of the subpiece + * @param maxByteSize the size of the subpiece + */ +public record SubLocalOutVarGen(int byteOffset, int maxByteSize) + implements SubLocalVarGen, LocalOutVarGen {} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubLocalVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubLocalVarGen.java new file mode 100644 index 0000000000..c4aa21277c --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubLocalVarGen.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.alloc.VarHandler; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.var.JitVarnodeVar; + +/** + * A generator for a subpiece of a local variable + * + * @param the class of p-code variable node in the use-def graph + */ +public interface SubLocalVarGen extends LocalVarGen { + + /** + * {@return} the number of bytes to the right of the subpiece + */ + int byteOffset(); + + /** + * {@return the size of the subpiece} + */ + int maxByteSize(); + + @Override + default VarHandler getHandler(JitCodeGenerator gen, V v) { + return LocalVarGen.super.getHandler(gen, v).subpiece(gen.getAnalysisContext().getEndian(), + byteOffset(), maxByteSize()); + } + + @Override + default ValGen subpiece(int byteOffset, int maxByteSize) { + throw new AssertionError("Who's subpiecing a subpiece?"); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubMemoryOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubMemoryOutVarGen.java new file mode 100644 index 0000000000..94091c397b --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubMemoryOutVarGen.java @@ -0,0 +1,27 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitMemoryOutVar; + +/** + * The generator for a subpiece of a memory output variable. + * + * @param byteOffset the number of bytes to the right of the subpiece + * @param maxByteSize the size of the subpiece + */ +public record SubMemoryOutVarGen(int byteOffset, int maxByteSize) + implements SubMemoryVarGen, MemoryOutVarGen {} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubMemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubMemoryVarGen.java new file mode 100644 index 0000000000..4d3f48d65c --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/SubMemoryVarGen.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.analysis.JitDataFlowArithmetic; +import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.var.JitVarnodeVar; +import ghidra.program.model.pcode.Varnode; + +/** + * A generator for a subpiece of a memory variable + * + * @param the class of p-code variable node in the use-def graph + */ +public interface SubMemoryVarGen extends MemoryVarGen { + + /** + * {@return} the number of bytes to the right of the subpiece + */ + int byteOffset(); + + /** + * {@return the size of the subpiece} + */ + int maxByteSize(); + + @Override + default Varnode getVarnode(JitCodeGenerator gen, V v) { + Varnode parent = MemoryVarGen.super.getVarnode(gen, v); + return JitDataFlowArithmetic.subPieceVn(gen.getAnalysisContext().getEndian(), parent, + byteOffset(), maxByteSize()); + } + + @Override + default ValGen subpiece(int byteOffset, int maxByteSize) { + throw new AssertionError("Who's subpiecing a subpiece?"); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java index 4c1ed9f6db..d4fd72d219 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/ValGen.java @@ -15,18 +15,22 @@ */ package ghidra.pcode.emu.jit.gen.var; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; - import ghidra.pcode.emu.jit.JitConfiguration; -import ghidra.pcode.emu.jit.analysis.*; +import ghidra.pcode.emu.jit.analysis.JitDataFlowModel; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.OpndEm; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.*; import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.var.*; +import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; /** @@ -49,7 +53,7 @@ import ghidra.program.model.pcode.Varnode; * {@link Varnode#isConstant() constant} * {@link JitConstVal} * {@link ConstValGen} - * {@link Opcodes#LDC ldc} + * {@link Op#ldc__i(Emitter, int) ldc} * * * {@link Varnode#isUnique() unique},
    @@ -60,12 +64,10 @@ import ghidra.program.model.pcode.Varnode; * {@link JitMissingVar} * {@link InputVarGen},
    * {@link LocalOutVarGen} - * See {@link SimpleJitType#opcodeLoad()}:
    - * {@link Opcodes#ILOAD iload}, {@link Opcodes#LLOAD lload}, {@link Opcodes#FLOAD fload}, - * {@link Opcodes#DLOAD dload} - * See {@link SimpleJitType#opcodeStore()}:
    - * {@link Opcodes#ISTORE istore}, {@link Opcodes#LSTORE lstore}, {@link Opcodes#FSTORE fstore}, - * {@link Opcodes#DSTORE dstore} + * {@link Op#iload(Emitter, Local) iload}, {@link Op#lload(Emitter, Local) lload}, + * {@link Op#fload(Emitter, Local) fload}, {@link Op#dload(Emitter, Local) dload} + * {@link Op#istore(Emitter, Local) istore}, {@link Op#lstore(Emitter, Local) lstore}, + * {@link Op#fstore(Emitter, Local) fstore}, {@link Op#dstore(Emitter, Local) dstore} * * * {@link Varnode#isAddress() memory} @@ -89,15 +91,10 @@ import ghidra.program.model.pcode.Varnode; * because they are shared by all threads. TODO: A {@link JitConfiguration} flag * that says "the machine is single threaded!" so we can optimize memory accesses in the * same manner we do registers and uniques. - * @implNote There are remnants of experiments and fragments in anticipation of multi-precision - * integer variables. These are not supported yet, but some of the components for mp-int - * support are used in degenerate form to support normal ints. Many of these components - * have "{@code Mp}" in the name. - * @implNote The memory variables are all generally handled as if ints, and then - * {@link TypeConversions type conversions} are applied if necessary to access them as - * floating point. + * @implNote The memory variables are all generally handled as if ints, and then {@link Opnd type + * conversions} are applied if necessary to access them as floating point. * @implNote {@link JitMissingVar} is a special case of {@code unique} and {@code register} variable - * where the definition could not be found. It is used as an intermediate result the + * where the definition could not be found. It is used as an intermediate result in the * {@link JitDataFlowModel}, but should be converted to a {@link JitOutVar} defined by a * {@link JitPhiOp} before it enters the use-def graph. * @implNote {@link JitIndirectMemoryVar} is a singleton dummy used in the {@link JitDataFlowModel}. @@ -125,29 +122,131 @@ public interface ValGen { }; } + @SuppressWarnings({ "unchecked", "rawtypes" }) + static , TT extends BPrim, N1 extends Next, N0 extends Ent> + Emitter> + castBack(Emitter em, SimpleJitType to, SimpleJitType from) { + return (Emitter) em; + } + /** - * Prepare any class-level items required to use this variable - * + * Emit code to prepare any class-level items required to use this variable *

    * For example, if this represents a direct memory variable, then this can prepare a reference * to the portion of the state involved, allowing it to access it readily. + *

    + * This should be used to emit code into the constructor. * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator * @param v the value - * @param iv the constructor visitor + * @return the emitter with ... */ - void generateValInitCode(JitCodeGenerator gen, V v, MethodVisitor iv); + Emitter genValInit(Emitter em, + Local> localThis, JitCodeGenerator gen, V v); /** - * Read the value onto the stack + * Emit code to read the value onto the stack * + * @param the type of the generated class + * @param the desired JVM type + * @param the desired p-code type + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator - * @param v the value to read - * @param typeReq the required type of the value - * @param ext the kind of extension to apply when adjusting from JVM size to varnode size - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - * @return the actual p-code type (which determines the JVM type) of the value on the stack + * @param v the value + * @param type the desired p-code type + * @param ext the kind of extension to apply + * @return the emitter with ..., result */ - JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, - MethodVisitor rv); + , JT extends SimpleJitType, + N extends Next> Emitter> genReadToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, V v, JT type, Ext ext); + + /** + * Emit code to read the value into local variables + *

    + * NOTE: In some cases, this may not emit any code at all. It may simple compose the operand + * from locals already allocated for a variable being "read." + * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} + * @param gen the code generator + * @param v the value + * @param type the desired p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary variables + * @return the operand and emitter with ... + */ + OpndEm genReadToOpnd( + Emitter em, Local> localThis, JitCodeGenerator gen, V v, + MpIntJitType type, Ext ext, Scope scope); + + /** + * Emit code to read a leg of the value onto the stack + * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} + * @param gen the code generator + * @param v the value + * @param type the desired p-code type + * @param leg the leg index, 0 being the least significant + * @param ext the kind of extension to apply + * @return the emitter with ..., result + */ + Emitter> genReadLegToStack( + Emitter em, Local> localThis, JitCodeGenerator gen, V v, + MpIntJitType type, int leg, Ext ext); + + /** + * Emit code to read the value into an array + * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} + * @param gen the code generator + * @param v the value + * @param type the desired p-code type + * @param ext the kind of extension to apply + * @param scope a scope for generated temporary variables + * @param slack the number of extra (more significant) elements to allocate in the array + * @return the operand and emitter with ..., arrayref + */ + Emitter>> genReadToArray( + Emitter em, Local> localThis, JitCodeGenerator gen, V v, + MpIntJitType type, Ext ext, Scope scope, int slack); + + /** + * Emit code to read the value onto the stack as a boolean + * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} + * @param gen the code generator + * @param v the value + * @return the emitter with ..., result + */ + Emitter> genReadToBool( + Emitter em, Local> localThis, JitCodeGenerator gen, V v); + + /** + * Create a generator for a {@link PcodeOp#SUBPIECE} of a value. + * + * @param byteOffset the number of least-significant bytes to remove + * @param maxByteSize the maximum size of the resulting variable. In general, a subpiece should + * never exceed the size of the parent varnode, but if it does, this will truncate + * that excess. + * @return the resulting subpiece generator + */ + ValGen subpiece(int byteOffset, int maxByteSize); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java index a5be31c50f..3ec404c149 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/VarGen.java @@ -20,18 +20,24 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.BLOCK_SIZE; import java.util.LinkedHashSet; import java.util.Set; -import org.objectweb.asm.MethodVisitor; - import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; -import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmLocal; +import ghidra.pcode.emu.jit.alloc.JvmLocal; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; +import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.SimpleJitType; +import ghidra.pcode.emu.jit.analysis.JitVarScopeModel; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.access.AccessGen; import ghidra.pcode.emu.jit.gen.op.CBranchOpGen; import ghidra.pcode.emu.jit.gen.op.CallOtherOpGen; +import ghidra.pcode.emu.jit.gen.opnd.Opnd; +import ghidra.pcode.emu.jit.gen.opnd.Opnd.Ext; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; -import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; +import ghidra.pcode.emu.jit.gen.util.*; +import ghidra.pcode.emu.jit.gen.util.Emitter.Ent; +import ghidra.pcode.emu.jit.gen.util.Emitter.Next; +import ghidra.pcode.emu.jit.gen.util.Types.BPrim; +import ghidra.pcode.emu.jit.gen.util.Types.TRef; import ghidra.pcode.emu.jit.var.*; import ghidra.program.model.address.Address; import ghidra.program.model.pcode.Varnode; @@ -60,11 +66,11 @@ public interface VarGen extends ValGen { static VarGen lookup(V v) { return (VarGen) switch (v) { case JitIndirectMemoryVar imv -> throw new AssertionError(); - case JitDirectMemoryVar dmv -> DirectMemoryVarGen.GEN; - case JitInputVar iv -> InputVarGen.GEN; + case JitDirectMemoryVar dmv -> WholeDirectMemoryVarGen.GEN; + case JitInputVar iv -> WholeInputVarGen.GEN; case JitMissingVar mv -> MissingVarGen.GEN; - case JitMemoryOutVar mov -> MemoryOutVarGen.GEN; - case JitLocalOutVar lov -> LocalOutVarGen.GEN; + case JitMemoryOutVar mov -> WholeMemoryOutVarGen.GEN; + case JitLocalOutVar lov -> WholeLocalOutVarGen.GEN; default -> throw new AssertionError(); }; } @@ -83,142 +89,157 @@ public interface VarGen extends ValGen { * we have the generator iterate the variables in address order and invoke this method, where we * make the request first. * + * @param the tail of the stack (...) + * @param em the emitter * @param gen the code generator * @param vn the varnode + * @return the emitter with ... */ - static void generateValInitCode(JitCodeGenerator gen, Varnode vn) { + static Emitter genVarnodeInit(Emitter em, JitCodeGenerator gen, + Varnode vn) { long start = vn.getOffset(); long endIncl = start + vn.getSize() - 1; long startBlock = start / BLOCK_SIZE * BLOCK_SIZE; long endBlockIncl = endIncl / BLOCK_SIZE * BLOCK_SIZE; - // Use != to allow wrap-around. + // Use != instead of < to allow wrap-around. for (long block = startBlock; block != endBlockIncl + BLOCK_SIZE; block += BLOCK_SIZE) { gen.requestFieldForArrDirect(vn.getAddress().getNewAddress(block)); } + + return em; } /** * Emit bytecode that loads the given varnode with the given p-code type from the - * {@link JitBytesPcodeExecutorState state} onto the JVM stack. + * {@link JitBytesPcodeExecutorState state} onto the stack. * *

    * This is used for direct memory accesses and for register/unique scope transitions. The JVM - * type of the stack variable is determined by the {@code type} argument. + * type of the operand is determined by the {@code type} argument. * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator * @param type the p-code type of the variable * @param vn the varnode to read from the state - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the emitter with ..., result */ - static void generateValReadCodeDirect(JitCodeGenerator gen, JitType type, Varnode vn, - MethodVisitor rv) { - TypedAccessGen.lookupReader(gen.getAnalysisContext().getEndian(), type) - .generateCode(gen, vn, rv); - } - - /** - * Emit bytecode that loads the given use-def variable from the - * {@link JitBytesPcodeExecutorState state} onto the JVM stack. - * - *

    - * The actual type is determined by resolving the {@code typeReq} argument against the given - * variable. Since the variable is being loaded directly from the state, which is just raw - * bytes/bits, we ignore the "assigned" type and convert directly the type required by the - * operand. - * - * @param gen the code generator - * @param v the use-def variable node - * @param typeReq the type (behavior) required by the operand. - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method - * @return the resulting p-code type (which also describes the JVM type) of the value on the JVM - * stack - */ - static JitType generateValReadCodeDirect(JitCodeGenerator gen, JitVarnodeVar v, - JitTypeBehavior typeReq, MethodVisitor rv) { - JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); - generateValReadCodeDirect(gen, type, v.varnode(), rv); - return type; + static , JT extends SimpleJitType, + N extends Next> Emitter> genReadValDirectToStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JT type, Varnode vn) { + return AccessGen.lookupSimple(gen.getAnalysisContext().getEndian(), type) + .genReadToStack(em, localThis, gen, vn); } /** * Emit bytecode that writes the given varnode with the given p-code type in the - * {@link JitBytesPcodeExecutorState state} from the JVM stack. + * {@link JitBytesPcodeExecutorState state} from a stack operand. * *

    * This is used for direct memory accesses and for register/unique scope transitions. The * expected JVM type of the stack variable is described by the {@code type} argument. * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator - * @param type the p-code type of the variable + * @param type the type of the operand on the stack * @param vn the varnode to write in the state - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the emitter with ... */ - static void generateValWriteCodeDirect(JitCodeGenerator gen, JitType type, Varnode vn, - MethodVisitor rv) { - TypedAccessGen.lookupWriter(gen.getAnalysisContext().getEndian(), type) - .generateCode(gen, vn, rv); + static , JT extends SimpleJitType, + N1 extends Next, + N0 extends Ent> Emitter genWriteValDirectFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JT type, Varnode vn) { + return AccessGen.lookupSimple(gen.getAnalysisContext().getEndian(), type) + .genWriteFromStack(em, localThis, gen, vn); } /** * Emit bytecode that writes the given use-def variable in the {@link JitBytesPcodeExecutorState - * state} from the JVM stack. + * state} from a stack operand. * *

    * The expected type is given by the {@code type} argument. Since the variable is being written * directly into the state, which is just raw bytes/bits, we ignore the "assigned" type and * convert using the given type instead. * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator + * @param type the type of the operand on the stack * @param v the use-def variable node - * @param type the p-code type of the value on the stack, as required by the operand - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the emitter with ... */ - static void generateValWriteCodeDirect(JitCodeGenerator gen, JitVarnodeVar v, - JitType type, MethodVisitor rv) { - generateValWriteCodeDirect(gen, type, v.varnode(), rv); + static , JT extends SimpleJitType, + N1 extends Next, + N0 extends Ent> Emitter genWriteValDirectFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, JT type, JitVarnodeVar v) { + return genWriteValDirectFromStack(em, localThis, gen, type, v.varnode()); } /** * For block transitions: emit bytecode that births (loads) variables from the * {@link JitBytesPcodeExecutorState state} into their allocated JVM locals. * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator * @param toBirth the set of varnodes to load - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the emitter with ... */ - static void generateBirthCode(JitCodeGenerator gen, Set toBirth, MethodVisitor rv) { + static Emitter genBirth(Emitter em, + Local> localThis, JitCodeGenerator gen, Set toBirth) { for (Varnode vn : toBirth) { - for (JvmLocal local : gen.getAllocationModel().localsForVn(vn)) { - local.generateBirthCode(gen, rv); + for (JvmLocal local : gen.getAllocationModel().localsForVn(vn)) { + em = local.genBirthCode(em, localThis, gen); } } + return em; } /** * For block transitions: emit bytecode the retires (writes) variables into the * {@link JitBytesPcodeExecutorState state} from their allocated JVM locals. * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator * @param toRetire the set of varnodes to write - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @return the emitter with ... */ - static void generateRetireCode(JitCodeGenerator gen, Set toRetire, MethodVisitor rv) { + static Emitter genRetire(Emitter em, + Local> localThis, JitCodeGenerator gen, Set toRetire) { for (Varnode vn : toRetire) { - for (JvmLocal local : gen.getAllocationModel().localsForVn(vn)) { - local.generateRetireCode(gen, rv); + for (JvmLocal local : gen.getAllocationModel().localsForVn(vn)) { + em = local.genRetireCode(em, localThis, gen); } } + return em; } /** * A means to emit bytecode on transitions between {@link JitBlock blocks} * + * @param the type of the generated class + * @param localThis a handle to {@code this} * @param gen the code generator * @param toRetire the varnodes to retire on the transition * @param toBirth the varnodes to birth on the transition */ - public record BlockTransition(JitCodeGenerator gen, Set toRetire, - Set toBirth) { + public record BlockTransition(Local> localThis, + JitCodeGenerator gen, Set toRetire, Set toBirth) { /** * Construct a "nop" or blank transition. * @@ -226,10 +247,11 @@ public interface VarGen extends ValGen { * The transition is mutable, so it's common to create one in this fashion and then populate * it. * + * @param localThis a handle to {@code this} * @param gen the code generator */ - public BlockTransition(JitCodeGenerator gen) { - this(gen, new LinkedHashSet<>(), new LinkedHashSet<>()); + public BlockTransition(Local> localThis, JitCodeGenerator gen) { + this(localThis, gen, new LinkedHashSet<>(), new LinkedHashSet<>()); } /** @@ -248,11 +270,14 @@ public interface VarGen extends ValGen { /** * Emit bytecode for the transition * - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @param the tail of the stack (...) + * @param em the emitter + * @return the emitter with ... */ - public void generate(MethodVisitor rv) { - generateRetireCode(gen, toRetire, rv); - generateBirthCode(gen, toBirth, rv); + public Emitter genFwd(Emitter em) { + return em + .emit(VarGen::genRetire, localThis, gen, toRetire) + .emit(VarGen::genBirth, localThis, gen, toBirth); } /** @@ -261,15 +286,17 @@ public interface VarGen extends ValGen { *

    * Sometimes "transitions" are used around hazards, notably {@link CallOtherOpGen}. This * method is used after the hazard to restore the live variables in scope. - * ({@link #generate(MethodVisitor)} is used before the hazard.) Variables that were retired - * and re-birthed here. There should not have been any variables birthed going into the - * hazard. + * ({@link #genFwd(Emitter)} is used before the hazard.) Variables that were retired and + * re-birthed here. There should not have been any variables birthed going into the hazard. * - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @param the tail of the stack (...) + * @param em the emitter + * @return the emitter with ... */ - public void generateInv(MethodVisitor rv) { - generateRetireCode(gen, toBirth, rv); - generateBirthCode(gen, toRetire, rv); + public Emitter genInv(Emitter em) { + return em + .emit(VarGen::genRetire, localThis, gen, toBirth) + .emit(VarGen::genBirth, localThis, gen, toRetire); } } @@ -280,17 +307,19 @@ public interface VarGen extends ValGen { * Either block may be {@code null} to indicate entering or leaving the passage. Additionally, * the {@code to} block should be {@code null} when generating transitions around a hazard. * + * @param the type of the generated class + * @param localThis a handle to {@code this} * @param gen the code generator * @param from the block control flow is leaving (whether by branch or fall through) * @param to the block control flow is entering * @return the means of generating bytecode at the transition */ - static BlockTransition computeBlockTransition(JitCodeGenerator gen, JitBlock from, - JitBlock to) { + static BlockTransition computeBlockTransition( + Local> localThis, JitCodeGenerator gen, JitBlock from, JitBlock to) { JitVarScopeModel scopeModel = gen.getVariableScopeModel(); Set liveFrom = from == null ? Set.of() : scopeModel.getLiveVars(from); Set liveTo = to == null ? Set.of() : scopeModel.getLiveVars(to); - BlockTransition result = new BlockTransition(gen); + BlockTransition result = new BlockTransition<>(localThis, gen); result.toRetire.addAll(liveFrom); result.toRetire.removeAll(liveTo); @@ -302,14 +331,61 @@ public interface VarGen extends ValGen { } /** - * Write a value from the stack into the given variable + * Write a value from a stack operand into the given variable * + * @param the type of the generated class + * @param the JVM type of the stack operand + * @param the p-code type of the stack operand + * @param the tail of the stack (...) + * @param ..., value + * @param em the emitter + * @param localThis a handle to {@code this} * @param gen the code generator * @param v the variable to write - * @param type the p-code type (which also determines the expected JVM type) of the value on the - * stack + * @param type the p-code type of the stack operand * @param ext the kind of extension to apply when adjusting from varnode size to JVM size - * @param rv the visitor for the {@link JitCompiledPassage#run(int) run} method + * @param scope a scope for temporaries + * @return the emitter with ... */ - void generateVarWriteCode(JitCodeGenerator gen, V v, JitType type, Ext ext, MethodVisitor rv); + , JT extends SimpleJitType, + N1 extends Next, N0 extends Ent> Emitter genWriteFromStack(Emitter em, + Local> localThis, JitCodeGenerator gen, V v, JT type, Ext ext, + Scope scope); + + /** + * Write a value from a local operand into the given variable + * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param em the emitter + * @param localThis a handle to {@code this} + * @param gen the code generator + * @param v the variable to write + * @param opnd the source operand + * @param ext the kind of extension to apply when adjusting from varnode size to JVM size + * @param scope a scope for temporaries + * @return the emitter with ... + */ + Emitter genWriteFromOpnd(Emitter em, + Local> localThis, JitCodeGenerator gen, V v, Opnd opnd, + Ext ext, Scope scope); + + /** + * Write a value from an array operand into the given variable + * + * @param the type of the generated class + * @param the tail of the stack (...) + * @param ..., arrayref + * @param em the emitter + * @param localThis a handle to {@code this} + * @param gen the code generator + * @param v the variable to write + * @param type the p-code type of the array operand + * @param ext the kind of extension to apply when adjusting from varnode size to JVM size + * @param scope a scope for temporaries + * @return the emitter with ... + */ + >> Emitter + genWriteFromArray(Emitter em, Local> localThis, + JitCodeGenerator gen, V v, MpIntJitType type, Ext ext, Scope scope); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeDirectMemoryVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeDirectMemoryVarGen.java new file mode 100644 index 0000000000..9665a0918a --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeDirectMemoryVarGen.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitDirectMemoryVar; + +/** + * The generator for a (whole) direct memory variable. + */ +public enum WholeDirectMemoryVarGen implements DirectMemoryVarGen { + /** Singleton */ + GEN; + + @Override + public ValGen subpiece(int byteOffset, int maxByteSize) { + return new SubDirectMemoryVarGen(byteOffset, maxByteSize); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeInputVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeInputVarGen.java new file mode 100644 index 0000000000..69607f7a1f --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeInputVarGen.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitInputVar; + +/** + * The generator for a (whole) local variable that is input to the passage. + */ +public enum WholeInputVarGen implements InputVarGen { + /** Singleton */ + GEN; + + @Override + public ValGen subpiece(int byteOffset, int maxByteSize) { + return new SubInputVarGen(byteOffset, maxByteSize); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeLocalOutVarGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeLocalOutVarGen.java new file mode 100644 index 0000000000..0e0c809305 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeLocalOutVarGen.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.var; + +import ghidra.pcode.emu.jit.var.JitLocalOutVar; + +/** + * The generator for a (whole) local variable that is defined within the passage. + */ +public enum WholeLocalOutVarGen implements LocalOutVarGen { + /** Singleton */ + GEN; + + @Override + public ValGen subpiece(int byteOffset, int maxByteSize) { + return new SubLocalOutVarGen(byteOffset, maxByteSize); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeMemoryOutVarGen.java similarity index 62% rename from Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java rename to Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeMemoryOutVarGen.java index 3e80cf42c6..8f2a9a4a0e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/var/WholeMemoryOutVarGen.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.jit.gen.op; +package ghidra.pcode.emu.jit.gen.var; -import ghidra.pcode.emu.jit.op.JitFloatBinOp; +import ghidra.pcode.emu.jit.var.JitMemoryOutVar; /** - * An extension for floating-point binary operators - * - * @param the class of p-code op node in the use-def graph + * The generator for a (whole) memory output variable. */ -public interface FloatBinOpGen extends BinOpGen { +public enum WholeMemoryOutVarGen implements MemoryOutVarGen { + /** Singleton */ + GEN; + @Override - default boolean isSigned() { - return false; + public ValGen subpiece(int byteOffset, int maxByteSize) { + return new SubMemoryOutVarGen(byteOffset, maxByteSize); } } diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/gen/util/EmitterTest.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/gen/util/EmitterTest.java new file mode 100644 index 0000000000..8eefea4188 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/gen/util/EmitterTest.java @@ -0,0 +1,239 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen.util; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.PrintStream; +import java.lang.invoke.*; +import java.lang.invoke.MethodHandles.Lookup; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javax.tools.*; +import javax.tools.Diagnostic.Kind; +import javax.tools.JavaCompiler.CompilationTask; + +import org.junit.Test; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; + +import generic.Unique; +import ghidra.pcode.emu.jit.gen.util.Emitter.*; +import ghidra.pcode.emu.jit.gen.util.EmitterTest.Generated; +import ghidra.pcode.emu.jit.gen.util.Methods.*; +import ghidra.pcode.emu.jit.gen.util.Types.*; + +public class EmitterTest { + static final TRef T_GENERATED = Types.refOf(Generated.class); + static final TRef T_OBJECT = Types.refOf(Object.class); + static final TRef T_PRINT_STREAM = Types.refOf(PrintStream.class); + static final TRef T_STRING = Types.refOf(String.class); + static final TRef T_SYSTEM = Types.refOf(System.class); + + public interface Generated { + void run(); + } + + static final MthDesc MDESC_CONS = MthDesc.returns(Types.T_VOID).build(); + static final MthDesc MDESC_RUN = MthDesc.returns(Types.T_VOID).build(); + + static final MthDesc>> MDESC_PRINTLN = + MthDesc.returns(Types.T_VOID).param(T_STRING).build(); + + final TRef typeThis = + Types.refExtends(Generated.class, "Lghidra/pcode/emu/jit/gen/util/TestGenerated;"); + + public interface RunGenerator { + Emitter gen(Emitter em, Local> localThis, RetReq ret); + } + + public void generateAndRun(RunGenerator gen) throws Throwable { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + cw.visit(Opcodes.V21, Opcodes.ACC_PUBLIC, typeThis.internalName(), null, + T_OBJECT.internalName(), new String[] { T_GENERATED.internalName() }); + + var paramsInit = new Object() { + Local> this_; + }; + var retInit = Emitter.start(typeThis, cw, Opcodes.ACC_PUBLIC, "", MDESC_CONS) + .param(Def::done, typeThis, l -> paramsInit.this_ = l); + retInit.em() + .emit(Op::aload, paramsInit.this_) + .emit(Op::invokespecial, T_OBJECT, "", MDESC_CONS, false) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::return_, retInit.ret()) + .emit(Misc::finish); + + var paramsRun = new Object() { + Local> this_; + }; + var retRun = Emitter.start(typeThis, cw, Opcodes.ACC_PUBLIC, "run", MDESC_RUN) + .param(Def::done, typeThis, l -> paramsRun.this_ = l); + retRun.em() + .emit(gen::gen, paramsRun.this_, retRun.ret()) + .emit(Misc::finish); + + cw.visitEnd(); + byte[] classfile = cw.toByteArray(); + + Lookup lookup = MethodHandles.lookup(); + Lookup defLookup = lookup.defineHiddenClass(classfile, true); + @SuppressWarnings("unchecked") + Class cls = (Class) defLookup.lookupClass(); + MethodHandle constructor = + defLookup.findConstructor(cls, MethodType.methodType(void.class)); + Generated hw = (Generated) constructor.invoke(); + hw.run(); + } + + @Test + public void testHelloWorld() throws Throwable { + generateAndRun((em, localThis, ret) -> em + .emit(Op::getstatic, T_SYSTEM, "out", T_PRINT_STREAM) + .emit(Op::ldc__a, "Hello, World") + .emit(Op::invokevirtual, T_PRINT_STREAM, "println", MDESC_PRINTLN, false) + .step(Inv::takeArg) + .step(Inv::takeObjRef) + .step(Inv::retVoid) + .emit(Op::return_, ret)); + } + + @Test + public void testArrayLengthPrim() throws Throwable { + generateAndRun((em, localThis, ret) -> em + .emit(Op::ldc__i, 6) + .emit(Op::newarray, Types.T_INT) + .emit(Op::arraylength__prim, Types.T_INT) + .emit(Op::pop) + .emit(Op::return_, ret)); + } + + @Test + public void testArrayLengthRef() throws Throwable { + generateAndRun((em, localThis, ret) -> em + .emit(Op::ldc__i, 6) + .emit(Op::anewarray, Types.refOf(String.class)) + .emit(Op::arraylength__ref) + .emit(Op::pop) + .emit(Op::return_, ret)); + } + + @Test + public void testArrayLengthWrong() throws Throwable { + runExpectingCompilationError(""" + generateAndRun((em, localThis, ret) -> /*s*/em + .emit(Op::ldc__i, 6) + .emit(Op::arraylength__prim, Types.T_INT)/*e*/ + .emit(Op::pop) + .emit(Op::return_, ret)); + """, List.of(Types.class, Op.class)); + } + + @Test + public void testAstore() throws Throwable { + generateAndRun((em, localThis, ret) -> { + Local> test = em.rootScope().decl(Types.refOf(Object.class), "test"); + return em + .emit(Op::aload, localThis) + .emit(Op::astore, test) + .emit(Op::return_, ret); + }); + } + + // @Test // Because it won't actually run. Just a syntax check. + public void syntaxTestAreturn() throws Throwable { + RetReq> ret = null; + generateAndRun((em, localThis, ignore) -> em + .emit(Op::new_, Types.refOf(String.class)) + .emit(Op::areturn, ret)); + } + + protected static class JavaSourceFromString extends SimpleJavaFileObject { + private final String code; + + protected JavaSourceFromString(String name, String code) { + super(URI.create("string:///" + name), Kind.SOURCE); + this.code = code; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return code; + } + }; + + protected String genImports(Iterable> imports) { + StringBuilder sb = new StringBuilder(); + for (Class imp : imports) { + sb.append("import "); + sb.append(imp.getCanonicalName()); + sb.append(";\n"); + } + return sb.toString(); + } + + protected void runExpectingCompilationError(String source, Iterable> imports) + throws Throwable { + + List> importsPlus = new ArrayList<>(); + importsPlus.add(this.getClass()); + importsPlus.add(Generated.class); + imports.forEach(importsPlus::add); + + String fullSource = """ + package test.source; + %s + public class TestSource extends %s { + public void testMethod() throws Throwable { + %s + } + } + """.formatted(genImports(importsPlus), + this.getClass().getSimpleName(), source); + + int expectedStart = fullSource.indexOf("/*s*/"); + if (expectedStart == -1) { + throw new AssertionError("Invalid test case. Missing start marker /*s*/"); + } + int expectedEnd = fullSource.indexOf("/*e*/"); + if (expectedEnd == -1) { + throw new AssertionError("Invalid test case. Missing end marker /*e*/"); + } + if (fullSource.indexOf("/*s*/", expectedStart + 1) != -1) { + throw new AssertionError("Invalid test case. Duplicate start marker /*s*/"); + } + if (fullSource.indexOf("/*e*/", expectedEnd + 1) != -1) { + throw new AssertionError("Invalid test case. Duplicate end marker /*e*/"); + } + + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + JavaCompiler javac = ToolProvider.getSystemJavaCompiler(); + CompilationTask task = javac.getTask(null, null, diagnostics, List.of( + "-cp", System.getProperty("java.class.path")), null, + List.of(new JavaSourceFromString("test/source/TestSource.java", fullSource))); + task.call(); + Diagnostic oneError = Unique.assertOne( + diagnostics.getDiagnostics().stream().filter(d -> d.getKind() == Kind.ERROR)); + assertEquals("Error position mismatch", + fullSource.substring(expectedStart + "/*s*/".length(), expectedEnd), + fullSource.substring((int) oneError.getStartPosition(), + (int) oneError.getEndPosition())); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/Utils.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/Utils.java index 9c782bed1a..cf3e491e7f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/Utils.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcode/utils/Utils.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * 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. @@ -16,11 +15,11 @@ */ package ghidra.pcode.utils; +import java.math.BigInteger; + import ghidra.util.BigEndianDataConverter; import ghidra.util.LittleEndianDataConverter; -import java.math.BigInteger; - public class Utils { public static final String endl = System.getProperty("line.separator"); @@ -52,8 +51,13 @@ public class Utils { return BigInteger.ONE.shiftLeft(size * 8).subtract(BigInteger.ONE); } - public static boolean signbit_negative(long val, int size) { // Return true if signbit is set - // (negative) + /** + * {@return true if signbit is set (negative)} + * + * @param val the raw value as a long + * @param size the actual size (in bytes) of the value + */ + public static boolean signbit_negative(long val, int size) { long mask = 0x80; mask <<= 8 * (size - 1); return ((val & mask) != 0); @@ -63,9 +67,7 @@ public class Utils { return ((~in) & calc_mask(size)); } - public static long sign_extend(long in, int sizein, int sizeout) - - { + public static long sign_extend(long in, int sizein, int sizeout) { int signbit; long mask; @@ -84,9 +86,7 @@ public class Utils { } // this used to void and changed the parameter val - can't do it in java - public static long zzz_sign_extend(long val, int bit) - - { // Sign extend -val- above -bit- + public static long zzz_sign_extend(long val, int bit) { // Sign extend -val- above -bit- long mask = 0; mask = (~mask) << bit; if (((val >>> bit) & 1) != 0) { @@ -108,7 +108,17 @@ public class Utils { return val; } - // this used to void and changed the parameter val - can't do it in java + // this used to be void and changed the parameter val - can't do it in java + /** + * Swap the least-significant bytes of the given value + *

    + * The size must be less than 8, as that is the largest possible size of the given value. If + * size is larger than 8, there may be undefined behavior, e.g., bytes getting truncated. + * + * @param val the value whose bytes to swap + * @param size the number of least-signifcant bytes to swap + * @return the value with the swapped bytes + */ public static long byte_swap(long val, int size) { // Swap the least sig -size- bytes in val long res = 0; while (size > 0) { @@ -143,12 +153,10 @@ public class Utils { public static byte[] longToBytes(long val, int size, boolean bigEndian) { long value = val; - if (!bigEndian) { - value = byte_swap(value, size); - } byte[] bytes = new byte[size]; for (int i = 0; i < size; i++) { - bytes[size - i - 1] = (byte) value; + int index = bigEndian ? size - i - 1 : i; + bytes[index] = (byte) value; value = value >> 8; } return bytes; @@ -168,5 +176,4 @@ public class Utils { } return LittleEndianDataConverter.INSTANCE.getBytes(val, size); } - } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java index b371774838..5e3f9db875 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/pcode/Varnode.java @@ -140,7 +140,7 @@ public class Varnode { if (spaceID != addr.getAddressSpace().getSpaceID()) { return false; } - if (isConstant() || isUnique() || isHash()) { + if (isConstant() || isHash()) { // this is not really a valid use case return offset == addr.getOffset(); } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/analysis/JitAllocationModelTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/analysis/JitAllocationModelTest.java index 1ef4ad4288..6149c02f16 100644 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/analysis/JitAllocationModelTest.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/analysis/JitAllocationModelTest.java @@ -27,7 +27,7 @@ import generic.Unique; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguageHelper; import ghidra.pcode.emu.jit.AbstractJitTest; -import ghidra.pcode.emu.jit.analysis.JitAllocationModel.MultiLocalVarHandler; +import ghidra.pcode.emu.jit.alloc.AlignedMpIntHandler; import ghidra.pcode.emu.jit.analysis.JitType.DoubleJitType; import ghidra.pcode.emu.jit.var.JitVar; import ghidra.pcode.emu.jit.var.JitVarnodeVar; @@ -66,7 +66,7 @@ public class JitAllocationModelTest extends AbstractJitTest { JitVarnodeVar tempVar = Unique.assertOne(varnodeVars(dfm) .filter(v -> v.varnode().isUnique())); - if (!(am.getHandler(tempVar) instanceof MultiLocalVarHandler handler)) { + if (!(am.getHandler(tempVar) instanceof AlignedMpIntHandler handler)) { throw new AssertionFailedError(); } @@ -74,7 +74,7 @@ public class JitAllocationModelTest extends AbstractJitTest { * TODO: Might like to assert more details, but this mp-int aspect of the JIT-based emulator * is still a work in progress. */ - assertEquals(8, handler.parts().size()); + assertEquals(8, handler.legs().size()); } @Test diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java new file mode 100644 index 0000000000..80cf726aea --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractJitCodeGeneratorTest.java @@ -0,0 +1,419 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.*; +import java.lang.invoke.MethodHandles; +import java.math.BigInteger; +import java.nio.file.Files; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.objectweb.asm.*; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.TraceClassVisitor; + +import generic.Unique; +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.jit.*; +import ghidra.pcode.emu.jit.JitPassage.AddrCtx; +import ghidra.pcode.emu.jit.analysis.*; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPointPrototype; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; +import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.*; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.util.DefaultLanguageService; +import ghidra.util.NumericUtilities; +import ghidra.util.SystemUtilities; + +@SuppressWarnings("javadoc") +public abstract class AbstractJitCodeGeneratorTest extends AbstractJitTest { + + // NOTE: Limit logged output in nightly/batch test mode + protected static final boolean DEBUG_ENABLED = !SystemUtilities.isInTestingBatchMode(); + protected static final PrintWriter DEBUG_WRITER = + DEBUG_ENABLED ? new PrintWriter(System.out) : null; + + protected static final long LONG_CONST = 0xdeadbeefcafebabeL; + + public static void dumpProgram(PcodeProgram program) { + if (!DEBUG_ENABLED) { + return; + } + System.out.println(program); + } + + public static void dumpClass(byte[] classbytes) throws Exception { + if (!DEBUG_ENABLED) { + return; + } + File tmp = Files.createTempFile("gen", ".class").toFile(); + try (FileOutputStream out = new FileOutputStream(tmp)) { + out.write(classbytes); + } + new ProcessBuilder("javap", "-c", "-l", tmp.getPath()).inheritIO().start().waitFor(); + } + + record Translation(PcodeProgram program, MethodNode init, MethodNode run, JitPcodeThread thread, + TestUseropLibrary library, JitBytesPcodeExecutorState state, + JitCompiledPassageClass passageCls, JitCompiledPassage passage) { + + public void runErr(Class excType, String message) { + try { + passage.run(0); + } + catch (Throwable e) { + if (!excType.isInstance(e)) { + fail("Expected error of type " + excType.getSimpleName() + ", but was " + e); + } + assertEquals(message, e.getMessage()); + return; + } + fail("Expected error of type " + excType.getSimpleName() + ", but there was none."); + } + + public void runLowlevelErr(String message) { + runErr(LowlevelError.class, message); + } + + public void runDecodeErr(long pc) { + runErr(DecodePcodeExecutionException.class, + "Unknown disassembly error (PC=%08x)".formatted(pc)); + } + + public void runFallthrough() { + assertEquals(0xdeadbeef, runClean()); + } + + public void runFallthrough32() { + assertEquals(0xdeadbeef, (int) runClean()); + } + + public long runClean() { + passage.run(0); + return thread.getCounter().getOffset(); + } + + public long getLongRegVal(Register reg) { + byte[] raw = state.getVar(reg, Reason.INSPECT); + return thread.getArithmetic().toLong(raw, Purpose.INSPECT); + } + + public RegisterValue getRegVal(Register reg) { + byte[] raw = state.getVar(reg, Reason.INSPECT); + return thread.getArithmetic().toRegisterValue(reg, raw, Purpose.INSPECT); + } + + public long getLongRegVal(String name) { + Register reg = thread.getLanguage().getRegister(name); + return getLongRegVal(reg); + } + + public long getLongVnVal(Varnode vn) { + byte[] raw = state.getVar(vn, Reason.INSPECT); + return thread.getArithmetic().toLong(raw, Purpose.INSPECT); + } + + public long getLongMemVal(long offset, int size) { + AddressSpace space = thread.getLanguage().getDefaultSpace(); + byte[] raw = state.getVar(space, offset, size, false, Reason.INSPECT); + return thread.getArithmetic().toLong(raw, Purpose.INSPECT); + } + + public void setLongRegVal(Register reg, long value) { + byte[] raw = thread.getArithmetic().fromConst(value, reg.getNumBytes()); + state.setVar(reg, raw); + } + + public void setLongRegVal(String name, long value) { + Register reg = thread.getLanguage().getRegister(name); + setLongRegVal(reg, value); + } + + public void setLongVnVal(Varnode vn, long value) { + byte[] raw = thread.getArithmetic().fromConst(value, vn.getSize()); + state.setVar(vn, raw); + } + + public void setLongMemVal(long offset, long value, int size) { + byte[] raw = thread.getArithmetic().fromConst(value, size); + AddressSpace space = thread.getLanguage().getDefaultSpace(); + state.setVar(space, offset, size, false, raw); + } + + public Entry entryPrototype(Address addr, RegisterValue ctx, + int blockId) { + return Map.entry(new AddrCtx(ctx, addr), new EntryPointPrototype(passageCls, blockId)); + } + } + + public Translation translateProgram(PcodeProgram program, JitPcodeThread thread) + throws Exception { + + dumpProgram(program); + + JitAnalysisContext context = makeContext(program, thread); + JitControlFlowModel cfm = new JitControlFlowModel(context); + JitDataFlowModel dfm = new JitDataFlowModel(context, cfm); + JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm); + JitTypeModel tm = new JitTypeModel(dfm); + JitAllocationModel am = new JitAllocationModel(context, dfm, vsm, tm); + JitOpUseModel oum = new JitOpUseModel(context, cfm, dfm, vsm); + + JitCodeGenerator gen = + new JitCodeGenerator<>(MethodHandles.lookup(), context, cfm, dfm, vsm, tm, am, oum); + + byte[] classbytes = gen.generate(); + + dumpClass(classbytes); + + ClassNode cn = new ClassNode(Opcodes.ASM9); + ClassReader cr = new ClassReader(classbytes); + ClassVisitor cv = DEBUG_ENABLED ? new TraceClassVisitor(cn, DEBUG_WRITER) : cn; + cr.accept(cv, 0); + + // Have the JVM validate this thing + JitBytesPcodeExecutorState state = thread.getState(); + JitCompiledPassageClass passageCls = + JitCompiledPassageClass.load(MethodHandles.lookup(), classbytes); + JitCompiledPassage passage = passageCls.createInstance(thread); + + assertEquals(Set.of( + "", "", "run", "thread"), + cn.methods.stream().map(m -> m.name).collect(Collectors.toSet())); + + MethodNode initMethod = + Unique.assertOne(cn.methods.stream().filter(m -> "".equals(m.name))); + MethodNode runMethod = + Unique.assertOne(cn.methods.stream().filter(m -> "run".equals(m.name))); + return new Translation(program, initMethod, runMethod, thread, + (TestUseropLibrary) thread.getMachine().getUseropLibrary(), state, passageCls, passage); + } + + public static class TestUseropLibrary extends AnnotatedPcodeUseropLibrary { + boolean gotJavaUseropCall = false; + boolean gotFuncUseropCall = false; + boolean gotSleighUseropCall = false; + + @PcodeUserop + public long java_userop(long a, long b) { + gotJavaUseropCall = true; + return 2 * a + b; + } + + @PcodeUserop(functional = true) + public long func_userop(long a, long b) { + gotFuncUseropCall = true; + return 2 * a + b; + } + + @PcodeUserop(functional = true) + public void func_mpUserop(@OpOutput int[] out, int[] a, int[] b) { + gotFuncUseropCall = true; + + if (out == null) { + return; + } + + out[0] = b[0]; + out[1] = a[0]; + for (int i = 0; i < 8; i++) { + out[0] |= out[0] << 4; + out[1] |= out[1] << 4; + } + } + + @PcodeUserop(canInline = true) + public void sleigh_userop(@OpExecutor PcodeExecutor executor, + @OpLibrary PcodeUseropLibrary library, + @OpOutput Varnode out, Varnode a, Varnode b) { + gotSleighUseropCall = true; + PcodeProgram opProg = SleighProgramCompiler.compileUserop(executor.getLanguage(), + "sleigh_userop", List.of("__result", "a", "b"), """ + __result = 2*a + b; + """, library, List.of(out, a, b)); + executor.execute(opProg, library); + } + + @PcodeUserop(functional = true) + public int tap_int(int a) { + System.err.println("tap: %x".formatted(a)); + return a; + } + } + + public static class TestJitPcodeEmulator extends JitPcodeEmulator { + public TestJitPcodeEmulator(Language language) { + super(language, new JitConfiguration(), MethodHandles.lookup()); + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return new TestUseropLibrary(); + } + } + + public static class TestPlainPcodeEmulator extends PcodeEmulator { + public TestPlainPcodeEmulator(Language language) { + super(language); + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return new TestUseropLibrary(); + } + } + + record Eval(String expr, BigInteger value) {} + + static Eval ev(String name, BigInteger value) { + return new Eval(name, value); + } + + static Eval ev(String name, String value) { + BigInteger bi = NumericUtilities.decodeBigInteger(value); + return ev(name, bi); + } + + static Eval ev(String name, double value) { + BigInteger bi = BigInteger.valueOf(Double.doubleToRawLongBits(value)); + return new Eval(name, bi); + } + + static Eval ev(String name, float value) { + BigInteger bi = BigInteger.valueOf(Integer.toUnsignedLong(Float.floatToRawIntBits(value))); + return new Eval(name, bi); + } + + record Case(String name, String init, List evals) {} + + static final int nNaNf = Float.floatToRawIntBits(Float.NaN) | Integer.MIN_VALUE; + static final long nNaNd = Double.doubleToRawLongBits(Double.NaN) | Long.MIN_VALUE; + static final BigInteger nNaN_F = BigInteger.valueOf(nNaNf); + static final BigInteger nNaN_D = BigInteger.valueOf(nNaNd); + + protected abstract LanguageID getLanguageID(); + + protected void runEquivalenceTest(Translation tr, List cases) { + PcodeEmulator plainEmu = new TestPlainPcodeEmulator(tr.program.getLanguage()); + PcodeThread plainThread = plainEmu.newThread(); + + for (Case c : cases) { + if (!c.init.isBlank()) { + plainThread.getExecutor().executeSleigh(c.init); + tr.thread.getExecutor().executeSleigh(c.init); + } + + plainThread.getExecutor().execute(tr.program, plainThread.getUseropLibrary()); + assertEquals("Mismatch of PC.", plainThread.getCounter().getOffset(), tr.runClean()); + + for (Eval e : c.evals) { + PcodeExpression expr = + SleighProgramCompiler.compileExpression(tr.program.getLanguage(), e.expr); + BigInteger plnResult = plainThread.getArithmetic() + .toBigInteger(expr.evaluate(plainThread.getExecutor()), Purpose.INSPECT); + BigInteger jitResult = tr.thread.getArithmetic() + .toBigInteger(expr.evaluate(tr.thread.getExecutor()), Purpose.INSPECT); + + BigInteger expResult = + new RegisterValue(tr.program.getLanguage().getRegister(e.expr), e.value) + .getUnsignedValue(); + + assertEquals( + "WRONG ASSERTION For case '%s': Mismatch of '%s'.".formatted(c.name, e.expr), + expResult.toString(16), plnResult.toString(16)); + assertEquals("For case '%s': Mismatch of '%s'.".formatted(c.name, e.expr), + expResult.toString(16), jitResult.toString(16)); + } + } + } + + public Translation translateSleigh(LanguageID langId, String source) throws Exception { + SleighLanguage language = (SleighLanguage) DefaultLanguageService.getLanguageService() + .getLanguage(langId); + List lines = new ArrayList<>(Arrays.asList(source.split("\n"))); + if (!lines.getLast().startsWith("goto ")) { + // Cannot end with fall-through + // TODO: how to specify positive? + lines.add("goto 0xdeadbeef;"); + source = lines.stream().collect(Collectors.joining("\n")); + } + JitPcodeEmulator emu = new TestJitPcodeEmulator(language); + JitPcodeThread thread = emu.newThread(); + PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", source, + thread.getUseropLibrary()); + return translateProgram(program, thread); + } + + public AssemblyBuffer createBuffer(LanguageID languageID, long entry) throws Exception { + Language language = DefaultLanguageService.getLanguageService().getLanguage(languageID); + Address addr = language.getDefaultSpace().getAddress(entry); + Assembler asm = Assemblers.getAssembler(language); + return new AssemblyBuffer(asm, addr); + } + + public Translation translateBuffer(AssemblyBuffer buf, Address entry, Map injects) + throws Exception { + Language language = buf.getAssembler().getLanguage(); + + JitPcodeEmulator emu = new TestJitPcodeEmulator(language); + AddressSpace space = language.getDefaultSpace(); + for (Map.Entry ent : injects.entrySet()) { + emu.inject(space.getAddress(ent.getKey()), ent.getValue()); + } + JitPcodeThread thread = emu.newThread(); + byte[] bytes = buf.getBytes(); + emu.getSharedState().setVar(buf.getEntry(), bytes.length, false, bytes); + + thread.setCounter(entry); + thread.overrideContextWithDefault(); + JitPassage passage = decodePassage(thread); + return translateProgram(passage, thread); + } + + public Translation translateLang(LanguageID languageID, long offset, String source, + Map injects) + throws Exception { + AssemblyBuffer buf = createBuffer(languageID, offset); + for (String line : source.split("\n")) { + if (line.isBlank()) { + } + else if (line.startsWith(".emit ")) { + buf.emit(NumericUtilities + .convertStringToBytes(line.substring(".emit ".length()).replace(" ", ""))); + } + else { + buf.assemble(line); + } + } + return translateBuffer(buf, buf.getEntry(), injects); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java new file mode 100644 index 0000000000..bddc24526a --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/AbstractToyJitCodeGeneratorTest.java @@ -0,0 +1,3651 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen; + +import static ghidra.lifecycle.Unfinished.TODO; +import static org.junit.Assert.*; + +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.Ignore; +import org.junit.Test; +import org.objectweb.asm.tree.MethodInsnNode; + +import ghidra.pcode.exec.InterruptPcodeExecutionException; +import ghidra.pcode.exec.SleighLinkException; +import ghidra.pcode.floatformat.FloatFormat; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.pcode.Varnode; + +public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGeneratorTest { + + protected static final LanguageID ID_TOYBE64 = new LanguageID("Toy:BE:64:default"); + protected static final LanguageID ID_TOYLE64 = new LanguageID("Toy:LE:64:default"); + protected static final LanguageID ID_TOYBE32 = new LanguageID("Toy:BE:32:default"); + + protected abstract Endian getEndian(); + + @Override + protected LanguageID getLanguageID() { + return switch (getEndian()) { + case BIG -> ID_TOYBE64; + case LITTLE -> ID_TOYLE64; + }; + } + + public Translation translateToy(long offset, String source) throws Exception { + return translateLang(getLanguageID(), offset, source, Map.of()); + } + + @Test + public void testSimpleInt() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp:4 = 0x1234; + """); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(0x1234, tr.getLongVnVal(temp)); + } + + @Test + public void testByteInIntLoad() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp:1 = r1l(2) + 0x34; + r0 = zext(temp); + """); + runEquivalenceTest(tr, List.of( + new Case("only", "r1l = 0x11223344;", List.of(ev("r0", "0x56"))))); + // NOTE: Would be nice to assert about positioning and masking + } + + @Test + public void testByteInIntStore() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + r0l = 0xffffffff; + r0l[16,8] = 0x12; + """); + runEquivalenceTest(tr, List.of( + new Case("only", "", List.of(ev("r0", "0xff12ffff"))))); + } + + @Test + public void testByteInLongLoad() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp:1 = r1(3) + 0x34; + r0 = zext(temp); + """); + runEquivalenceTest(tr, List.of( + new Case("only", "r1l = 0x1122334455667788;", List.of(ev("r0", "0x89"))))); + // NOTE: Would be nice to assert about positioning and masking + } + + @Test + public void testByteInLongStore() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + r0 = 0xffffffffffffffff; + r0[24,8] = 0x12; + """); + runEquivalenceTest(tr, List.of( + new Case("only", "", List.of(ev("r0", "0xffffffff12ffffff"))))); + } + + /** + * NOTE: There's no case where it should generate code reading a byte out of a float local. + * Instead, the coalescing should cause the local to be an int. It'll get converted to float + * when a float op requires it. Still, we should get the semantics we expect. That said, there's + * no no need (I think) to test "storing" into a float (nor into a double). + * + * @throws Exception because + */ + @Test + public void testByteInFloatLoad() throws Exception { + int f2Dot5 = Float.floatToRawIntBits(2.5f); + int f7Dot5 = Float.floatToRawIntBits(7.5f); + Translation tr = translateSleigh(getLanguageID(), """ + r1l = r2l f+ r3l; + r1l = sqrt(r1l); + temp:1 = r1l(2); + r0 = zext(temp); + """); + runEquivalenceTest(tr, List.of( + new Case("only", "r2l = 0x%x; r3l = 0x%x;".formatted(f2Dot5, f7Dot5), + List.of(ev("r0", "0x4a"))))); + } + + @Test + public void testByteInDoubleLoad() throws Exception { + long d2Dot5 = Double.doubleToRawLongBits(2.5); + long d7Dot5 = Double.doubleToRawLongBits(7.5); + Translation tr = translateSleigh(getLanguageID(), """ + r1 = r2 f+ r3; + r1 = sqrt(r1); + temp:1 = r1(2); + r0 = zext(temp); + """); + runEquivalenceTest(tr, List.of( + new Case("only", "r2 = 0x%x; r3 = 0x%x;".formatted(d2Dot5, d7Dot5), + List.of(ev("r0", "0xda"))))); + } + + @Test + public void testToyOneBlockHasFallthroughExit() throws Exception { + Translation tr = translateToy(0x00400000, """ + imm r0, #0x123 + """); + tr.runDecodeErr(0x00400002); + assertEquals(0x123, tr.getLongRegVal("r0")); + } + + @Test + public void testSimpleLong() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp:8 = 0x1234; + """); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(0x1234, tr.getLongVnVal(temp)); + } + + @Test + public void testSimpleFloat() throws Exception { + int fDot5 = Float.floatToRawIntBits(0.5f); + int fDot75 = Float.floatToRawIntBits(0.75f); + Translation tr = translateSleigh(getLanguageID(), """ + temp:4 = 0x%x f+ 0x%x; + """.formatted(fDot5, fDot75)); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(1.25f, Float.intBitsToFloat((int) tr.getLongVnVal(temp)), 0); + } + + @Test + public void testSimpleDouble() throws Exception { + long dDot5 = Double.doubleToRawLongBits(0.5); + long dDot75 = Double.doubleToRawLongBits(0.75); + Translation tr = translateSleigh(getLanguageID(), """ + temp:8 = 0x%x f+ 0x%x; + """.formatted(dDot5, dDot75)); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(1.25f, Double.longBitsToDouble(tr.getLongVnVal(temp)), 0); + } + + @Test + public void testReadMemMappedRegBE() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + * 0:8 = 0x%x:8; + temp:8 = mmr0; + """.formatted(LONG_CONST)); + Varnode temp = tr.program().getCode().get(1).getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongVnVal(temp)); + } + + @Test + public void testReadMemDirectWithPartsSpanningBlock() throws Exception { + long offset = GenConsts.BLOCK_SIZE - 2; + Translation tr = translateSleigh(getLanguageID(), """ + temp:8 = * 0x%x:8; + """.formatted(offset)); + tr.setLongMemVal(offset, LONG_CONST, 8); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongVnVal(temp)); + } + + @Test + @Ignore("Undefined") + public void testReadMemDirectWithSpanWrapSpace() throws Exception { + long offset = -2; + Translation tr = translateSleigh(getLanguageID(), """ + temp:8 = * 0x%x:8; + """.formatted(offset)); + tr.setLongMemVal(offset, LONG_CONST, 8); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp.isUnique()); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongVnVal(temp)); + } + + @Test + public void testWriteMemDirectWithPartsSpanningBlock() throws Exception { + long offset = GenConsts.BLOCK_SIZE - 2; + Translation tr = translateSleigh(getLanguageID(), """ + local temp:8; + * 0x%x:8 = temp; + """.formatted(offset)); + Varnode temp = tr.program().getCode().getFirst().getInput(2); + assertTrue(temp.isUnique()); + tr.setLongVnVal(temp, LONG_CONST); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); + } + + @Test + @Ignore("Undefined") + public void testWriteMemDirectWithSpanWrapSpace() throws Exception { + long offset = -2; + Translation tr = translateSleigh(getLanguageID(), """ + local temp:8; + * 0x%x:8 = temp; + """.formatted(offset)); + Varnode temp = tr.program().getCode().getFirst().getInput(2); + assertTrue(temp.isUnique()); + tr.setLongVnVal(temp, LONG_CONST); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); + } + + @Test + public void testReadMemIndirect() throws Exception { + long offset = GenConsts.BLOCK_SIZE - 2; + Translation tr = translateSleigh(getLanguageID(), """ + local temp:8; + local addr:8; + temp = * addr; + """); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + Varnode addr = tr.program().getCode().getFirst().getInput(1); + assertTrue(temp.isUnique()); + assertTrue(addr.isUnique()); + tr.setLongMemVal(offset, LONG_CONST, 8); + tr.setLongVnVal(addr, offset); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongVnVal(temp)); + } + + @Test + public void testWriteMemIndirect() throws Exception { + long offset = GenConsts.BLOCK_SIZE - 2; + Translation tr = translateSleigh(getLanguageID(), """ + local temp:8; + local addr:8; + * addr = temp; + """); + Varnode temp = tr.program().getCode().getFirst().getInput(2); + Varnode addr = tr.program().getCode().getFirst().getInput(1); + assertTrue(temp.isUnique()); + assertTrue(addr.isUnique()); + tr.setLongVnVal(temp, LONG_CONST); + tr.setLongVnVal(addr, offset); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); + } + + @Test + public void testAddressSize32() throws Exception { + long offset = GenConsts.BLOCK_SIZE - 2; + Translation tr = translateSleigh(ID_TOYBE32, """ + local temp:8; + local addr:4; + * addr = temp; + """); + Varnode temp = tr.program().getCode().getFirst().getInput(2); + Varnode addr = tr.program().getCode().getFirst().getInput(1); + assertTrue(temp.isUnique()); + assertTrue(addr.isUnique()); + tr.setLongVnVal(temp, LONG_CONST); + tr.setLongVnVal(addr, offset); + tr.runFallthrough32(); + assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); + } + + @Test + public void testVariablesAreRetiredBranchInd() throws Exception { + /** + * Considering detection of inter-passage indirect branching, I think this will complicate + * things way too much. All of the control-flow analysis must consider indirect flows, and + * then, the dataflow could be affected by that, so there's a circular dependency. With some + * care, that can be done, though I'm not sure it's always guaranteed to converge. Another + * possibility is to retire all the variables, but then, there has to be a special branch + * target that knows to birth the appropriate ones before entering the real block. + */ + Translation tr = translateSleigh(getLanguageID(), """ + local jump:8; + temp:8 = 0x%x; + goto [jump]; + """.formatted(LONG_CONST)); + Varnode temp = tr.program().getCode().getFirst().getOutput(); + Varnode jump = tr.program().getCode().get(1).getInput(0); + assertTrue(temp.isUnique()); + assertTrue(jump.isUnique()); + tr.setLongVnVal(jump, 0x1234); + assertEquals(0x1234, tr.runClean()); + assertEquals(LONG_CONST, tr.getLongVnVal(temp)); + } + + /** + * Test reading from a variable in an unreachable block. + * + *

    + * Yes, this test is valid, because, even though no slaspec should produce this, they could, but + * more to the point, a user injection could. Note that the underlying classfile writer may + * analyze and remove the unreachable code. Doesn't matter. What matters is that it doesn't + * crash, and that it produces correct results. + * + * @throws Exception because + */ + @Test + public void testWithMissingVariable() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + local temp:8; + local temp2:8; + temp2 = 1; + goto 0xdeadbeef; + temp2 = temp; + """); + Varnode temp2 = tr.program().getCode().getFirst().getOutput(); + assertTrue(temp2.isUnique()); + tr.runFallthrough(); + assertEquals(1, tr.getLongVnVal(temp2)); + } + + @Test + public void testMpIntOffcutLoadBE() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + local temp:16; + temp[0,64] = r1; + temp[64,64] = r2; + temp2:14 = temp[8,112]; + r0 = zext(temp2); + """), + List.of( + new Case("only", """ + r1 = 0x1122334455667788; + r2 = 0x99aabbccddeeff00; + """, + List.of( + ev("r0", "0x0011223344556677"))))); + } + + @Test + public void testLongOffcutLoadBE() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + local temp:16; + temp[0,64] = r1; + temp[64,64] = r2; + r0 = temp[24,64]; + """), + List.of( + new Case("only", """ + r1 = 0x1122334455667788; + r2 = 0x99aabbccddeeff00; + """, + List.of( + ev("r0", "0xeeff001122334455"))))); + } + + @Test + public void testLongOffcutStore() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + local temp:16; + temp[0,64] = r0; + temp[64,64] = r1; + temp[24,64] = 0xdeadbeefcafebabe; + r0 = temp[0,64]; + r1 = temp[64,64]; + """), + List.of( + new Case("only", """ + r0 = 0x1122334455667788; + r1 = 0x99aabbccddeeff00; + """, + List.of( + ev("r0", "0xefcafebabe667788"), + ev("r1", "0x99aabbccdddeadbe"))))); + } + + @Test + public void testCallOtherSleighDef() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + r0 = sleigh_userop(6:8, 2:8); + """); + assertTrue(tr.library().gotSleighUseropCall); + tr.library().gotSleighUseropCall = false; + tr.runFallthrough(); + assertFalse(tr.library().gotSleighUseropCall); + assertEquals(14, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherJavaDef() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + r0 = java_userop(6:8, 2:8); + """); + assertFalse(tr.library().gotJavaUseropCall); + tr.runFallthrough(); + assertTrue(tr.library().gotJavaUseropCall); + assertEquals(14, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherJavaDefNoOut() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + java_userop(6:8, 2:8); + """); + assertFalse(tr.library().gotJavaUseropCall); + tr.runFallthrough(); + assertTrue(tr.library().gotJavaUseropCall); + assertEquals(0, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherFuncJavaDef() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + r0 = func_userop(6:8, 2:8); + """); + assertFalse(tr.library().gotFuncUseropCall); + tr.runFallthrough(); + assertTrue(tr.library().gotFuncUseropCall); + assertEquals(14, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherFuncJavaDefNoOut() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + func_userop(6:8, 2:8); + """); + assertFalse(tr.library().gotFuncUseropCall); + tr.runFallthrough(); + assertTrue(tr.library().gotFuncUseropCall); + assertEquals(0, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherFuncJavaDefMpInt() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp1:9 = zext(6:8); + temp2:9 = zext(2:8); + temp0:9 = func_mpUserop(temp1, temp2); + r0 = temp0(0); + """); + assertFalse(tr.library().gotFuncUseropCall); + tr.runFallthrough(); + assertTrue(tr.library().gotFuncUseropCall); + assertEquals(0x6666666622222222L, tr.getLongRegVal("r0")); + } + + @Test + public void testCallOtherFuncJavaDefNoOutMpInt() throws Exception { + Translation tr = translateSleigh(getLanguageID(), """ + temp1:9 = zext(6:8); + temp2:9 = zext(2:8); + func_mpUserop(temp1, temp2); + """); + assertFalse(tr.library().gotFuncUseropCall); + tr.runFallthrough(); + assertTrue(tr.library().gotFuncUseropCall); + assertEquals(0, tr.getLongRegVal("r0")); + } + + /** + * Test that the emulator doesn't throw until the userop is actually encountered at run time. + * + *

    + * NOTE: The userop must be defined by the language, but left undefined by the library. + * Otherwise, we cannot even compile the sample Sleigh. + * + *

    + * NOTE: Must also use actual instructions, because the test passage constructor will fail fast + * on the undefined userop. + * + * @throws Exception because + */ + @Test + public void testCallOtherUndef() throws Exception { + Translation tr = translateToy(0x00400000, """ + user_one r0 + """); + tr.runErr(SleighLinkException.class, "Sleigh userop 'pcodeop_one' is not in the library"); + assertEquals(0x00400000, tr.thread().getCounter().getOffset()); + } + + /** + * I need to find an example of this: + * + *

    +	 *  *[register] OFFSET = ... ?
    +	 * 
    + * + *

    + * Honestly, if this actually occurs frequently, we could be in trouble. We would need either: + * 1) To re-write all the offending semantic blocks, or 2) Work out a way to re-write them + * during JIT compilation. People tell me this is done by some vector instructions, which makes + * me thing re-writing would be possible, since they should all fold to constants. If we're + * lucky, the planned constant folding would just take care of these; however, I'd still want to + * allocate them as locals, not just direct array access. For now, I'm just going to feign + * ignorance. If it becomes a problem, then we can treat all register accesses like memory + * within the entire passage containing one of these "indirect-register-access" ops. + * + * @throws Exception because + */ + @Test + @Ignore("No examples, yet") + public void testComputedOffsetsInRegisterSpace() throws Exception { + TODO(); + } + + @Test + public void testBranchOpGenInternal() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = 0xbeef; + goto ; + r0 = 0xdead; + + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0xbeef"))))); + } + + @Test + public void testBranchOpGenExternal() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = 0xbeef; + goto 0xdeadbeef; + r0 = 0xdead; + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0xbeef"))))); + } + + @Test + public void testCBranchOpGenInternalIntPredicate() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = 0xbeef; + if (r1!=0) goto ; + r0 = 0xdead; + + """), + List.of( + new Case("take", "r1=1;", List.of( + ev("r0", "0xbeef"))), + new Case("fall", "r1=0;", List.of( + ev("r0", "0xdead"))))); + } + + @Test + public void testCBranchOpGenExternalLongPredicate() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = 0xbeef; + if (r1) goto 0xdeadbeef; + r0 = 0xdead; + """), + List.of( + new Case("take", "r1=1;", List.of( + ev("r0", "0xbeef"))), + new Case("fall", "r1=0;", List.of( + ev("r0", "0xdead"))))); + } + + @Test + public void testCBranchOpGenExternalMpIntPredicate() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = 0xbeef; + temp:9 = zext(r1); + if (temp) goto 0xdeadbeef; + r0 = 0xdead; + """), + List.of( + new Case("sm_take", "r1 = 1;", List.of( + ev("r0", "0xbeef"))), + new Case("sm_fall", "r1 = 0;", List.of( + ev("r0", "0xdead"))), + new Case("lg_take", "r1 = 0x8000000000000000;", List.of( + ev("r0", "0xbeef"))))); + } + + @Test + public void testBoolNegateOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = !r1; + r6l = !r7l; + """), + List.of( + new Case("f", """ + r1 = 0; + r7l = 0; + """, + List.of( + ev("r0", "1"), + ev("r6", "1"))), + new Case("t", """ + r1 = 1; + r7l = 1; + """, + List.of( + ev("r0", "0"), + ev("r6", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolNegateMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp:9 = zext(r1); + temp = !temp; + r0 = temp(1); + """), + List.of( + new Case("f", """ + r1 = 0; + """, + List.of( + ev("r0", "0"))))); + } + + @Test + public void testBoolAndOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 && r2; + r3 = r4 && r5l; + r6l = r7l && r8; + r9l = r10l && r11l; + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; + r4 =0; r5l =0; + r7l =0; r8 =0; + r10l=0; r11l=0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + r4 =0; r5l =1; + r7l =0; r8 =1; + r10l=0; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), + new Case("tf", """ + r1 =1; r2 =0; + r4 =1; r5l =0; + r7l =1; r8 =0; + r10l=1; r11l=0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), + new Case("tt", """ + r1 =1; r2 =1; + r4 =1; r5l =1; + r7l =1; r8 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolAndMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 && temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("ff", """ + r1 = 0; r2 = 0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("ft", """ + r1 =0; r2 = 1; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("tf", """ + r1 = 1; r2 = 0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("tt", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolOrOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 || r2; + r3 = r4 || r5l; + r6l = r7l || r8; + r9l = r10l || r11l; + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; + r4 =0; r5l =0; + r7l =0; r8 =0; + r10l=0; r11l=0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + r4 =0; r5l =1; + r7l =0; r8 =1; + r10l=0; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), + new Case("tf", """ + r1 =1; r2 =0; + r4 =1; r5l =0; + r7l =1; r8 =0; + r10l=1; r11l=0; + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), + new Case("tt", """ + r1 =1; r2 =1; + r4 =1; r5l =1; + r7l =1; r8 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolOrMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 || temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tf", """ + r1 =1; r2 =0; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tt", """ + r1 =1; r2 =1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolXorOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 ^^ r2; + r3 = r4 ^^ r5l; + r6l = r7l ^^ r8; + r9l = r10l ^^ r11l; + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; + r4 =0; r5l =0; + r7l =0; r8 =0; + r10l=0; r11l=0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + r4 =0; r5l =1; + r7l =0; r8 =1; + r10l=0; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), + new Case("tf", """ + r1 =1; r2 =0; + r4 =1; r5l =0; + r7l =1; r8 =0; + r10l=1; r11l=0; + """, + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), + new Case("tt", """ + r1 =1; r2 =1; + r4 =1; r5l =1; + r7l =1; r8 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testBoolXorMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 ^^ temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("ff", """ + r1 =0; r2 =0; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("ft", """ + r1 =0; r2 =1; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tf", """ + r1 =1; r2 =0; + """, + List.of( + ev("r0", "1"), + ev("r3", "0"))), + new Case("tt", """ + r1 =1; r2 =1; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))))); + // NOTE: Not testing cases with other bits set + } + + @Test + public void testFloatAbsOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + long dn0dot5 = Double.doubleToLongBits(-0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + int fn0dot5 = Float.floatToIntBits(-0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = abs(r1); + r6l = abs(r7l); + """), + List.of( + new Case("p", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))), + new Case("n", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))))); + } + + /** + * Note that the test case for sqrt(n) where n is negative could be brittle, because of + * undefined behavior in the IEEE 754 spec. My JVM will result in "negative NaN." It used to be + * this test failed, but I've made an adjustment to {@link FloatFormat} to ensure it keeps + * whatever sign bit was returned by {@link Math#sqrt(double)}. It shouldn't matter to the + * emulation target one way or another (in theory), but I do want to ensure the two emulators + * behave the same. It seems easier to me to have {@link FloatFormat} keep the sign bit than to + * have the JIT compile in code that checks for and fixes "negative NaN." Less run-time cost, + * too. + * + * @throws Exception because + */ + @Test + public void testFloatSqrtOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + long dn0dot5 = Double.doubleToLongBits(-0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + int fn0dot5 = Float.floatToIntBits(-0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = sqrt(r1); + r6l = sqrt(r7l); + """), + List.of( + new Case("p", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", Math.sqrt(0.5)), + ev("r6", (float) Math.sqrt(0.5)))), + new Case("n", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", nNaN_D), + ev("r6l", nNaN_F))))); + } + + @Test + public void testFloatCeilOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + long dn0dot5 = Double.doubleToLongBits(-0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + int fn0dot5 = Float.floatToIntBits(-0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = ceil(r1); + r6l = ceil(r7l); + """), + List.of( + new Case("p", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), + new Case("n", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", -0.0d), + ev("r6", -0.0f))))); + } + + @Test + public void testFloatFloorOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + long dn0dot5 = Double.doubleToLongBits(-0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + int fn0dot5 = Float.floatToIntBits(-0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = floor(r1); + r6l = floor(r7l); + """), + List.of( + new Case("p", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), + new Case("n", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))))); + } + + @Test + public void testFloatRoundOpGen() throws Exception { + long d0dot25 = Double.doubleToLongBits(0.25); + long dn0dot25 = Double.doubleToLongBits(-0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + int fn0dot25 = Float.floatToIntBits(-0.25f); + long d0dot5 = Double.doubleToLongBits(0.5); + long dn0dot5 = Double.doubleToLongBits(-0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + int fn0dot5 = Float.floatToIntBits(-0.5f); + long d0dot75 = Double.doubleToLongBits(0.75); + long dn0dot75 = Double.doubleToLongBits(-0.75); + int f0dot75 = Float.floatToIntBits(0.75f); + int fn0dot75 = Float.floatToIntBits(-0.75f); + long d1dot0 = Double.doubleToLongBits(1.0); + long dn1dot0 = Double.doubleToLongBits(-1.0); + int f1dot0 = Float.floatToIntBits(1.0f); + int fn1dot0 = Float.floatToIntBits(-1.0f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = round(r1); + r6l = round(r7l); + """), + List.of( + new Case("+0.25", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot25, f0dot25), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), + new Case("-0.25", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot25, fn0dot25), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), + new Case("+0.5", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), + new Case("-0.5", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", 0.0d), + ev("r6", 0.0f))), + new Case("+0.75", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot75, f0dot75), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), + new Case("-0.75", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn0dot75, fn0dot75), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))), + new Case("+1.0", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d1dot0, f1dot0), + List.of( + ev("r0", 1.0d), + ev("r6", 1.0f))), + new Case("-1.0", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(dn1dot0, fn1dot0), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))))); + } + + @Test + public void testFloat2FloatOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = float2float(r1l); + r6l = float2float(r7); + """), + List.of( + new Case("only", """ + r1l =0x%x; + r7 =0x%x; + """.formatted(f0dot5, d0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))))); + } + + @Test + public void testFloatInt2FloatOpGen() throws Exception { + /** + * The size swap is not necessary, but test anyway. + */ + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = int2float(r1l); + r6l = int2float(r7); + """), + List.of( + new Case("only", """ + r1l =1; + r7 =2; + """, + List.of( + ev("r0", 1.0d), + ev("r6", 2.0f))))); + } + + @Test + public void testFloatTruncOpGen() throws Exception { + long d1dot0 = Double.doubleToLongBits(1.0); + long d0dot5 = Double.doubleToLongBits(0.5); + long dn0dot5 = Double.doubleToLongBits(-0.5); + int f1dot0 = Float.floatToIntBits(1.0f); + int f0dot5 = Float.floatToIntBits(0.5f); + int fn0dot5 = Float.floatToIntBits(-0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = trunc(r1); + r3 = trunc(r4l); + r6l = trunc(r7); + r9l = trunc(r10l); + """), + List.of( + new Case("+1.0", """ + r1 =0x%x; + r4l =0x%x; + r7 =0x%x; + r10l=0x%x; + """.formatted(d1dot0, f1dot0, d1dot0, f1dot0), + List.of( + ev("r0", "1"), + ev("r3", "1"), + ev("r6", "1"), + ev("r9", "1"))), + new Case("+0.5", """ + r1 =0x%x; + r4l =0x%x; + r7 =0x%x; + r10l=0x%x; + """.formatted(d0dot5, f0dot5, d0dot5, f0dot5), + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))), + new Case("-0.5", """ + r1 =0x%x; + r4l =0x%x; + r7 =0x%x; + r10l=0x%x; + """.formatted(dn0dot5, dn0dot5, dn0dot5, fn0dot5), + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))))); + } + + @Test + public void testFloatNaNOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long dNaN = Double.doubleToRawLongBits(Double.NaN); + int fNaN = Float.floatToRawIntBits(Float.NaN); + /** + * The size swap is not necessary, but test anyway. + */ + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = nan(r1l); + r6l = nan(r7); + """), + List.of( + new Case("num", """ + r1l =0x%x; + r7 =0x%x; + """.formatted(f0dot5, d0dot5), + List.of( + ev("r0", "0"), + ev("r6", "0"))), + new Case("nan", """ + r1l =0x%x; + r7 =0x%x; + """.formatted(fNaN, dNaN), + List.of( + ev("r0", "1"), + ev("r6", "1"))))); + } + + @Test + public void testFloatNegOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = f-r1; + r6l = f-r7l; + """), + List.of( + new Case("num", """ + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", -0.5d), + ev("r6l", -0.5f))))); + } + + @Test + public void testFloatAddOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f+ r2; + r9l = r10l f+ r11l; + """), + List.of( + new Case("only", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", 0.75d), + ev("r9", 0.75f))))); + } + + @Test + public void testFloatSubOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f- r2; + r9l = r10l f- r11l; + """), + List.of( + new Case("only", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", 0.25d), + ev("r9", 0.25f))))); + } + + @Test + public void testFloatMultOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f* r2; + r9l = r10l f* r11l; + """), + List.of( + new Case("only", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", 0.125d), + ev("r9", 0.125f))))); + } + + @Test + public void testFloatDivOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f/ r2; + r9l = r10l f/ r11l; + """), + List.of( + new Case("only", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", 2.0d), + ev("r9", 2.0f))))); + } + + @Test + public void testFloatEqualOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f== r2; + r9l = r10l f== r11l; + """), + List.of( + new Case("lt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("eq", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("gt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testFloatNotEqualOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f!= r2; + r9l = r10l f!= r11l; + """), + List.of( + new Case("lt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("eq", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("gt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testFloatLessEqualOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f<= r2; + r9l = r10l f<= r11l; + """), + List.of( + new Case("lt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("eq", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("gt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testFloatLessOpGen() throws Exception { + long d0dot5 = Double.doubleToLongBits(0.5); + int f0dot5 = Float.floatToIntBits(0.5f); + long d0dot25 = Double.doubleToLongBits(0.25); + int f0dot25 = Float.floatToIntBits(0.25f); + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 f< r2; + r9l = r10l f< r11l; + """), + List.of( + new Case("lt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("eq", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("gt", """ + r1 =0x%x; r2 =0x%x; + r10l=0x%x; r11l=0x%x; + """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testInt2CompOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = -r1; + r6l = -r7l; + """), + List.of( + new Case("pos", """ + r1 =4; + r7l =4; + """, + List.of( + ev("r0", "-4"), + ev("r6l", "-4"))), + new Case("neg", """ + r1 =-4; + r7l =-4; + """, + List.of( + ev("r0", "4"), + ev("r6l", "4"))))); + } + + @Test + public void testInt2CompMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp0:9 = -temp1; + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1 = 4; + """, + List.of( + ev("r0", "-4"), + ev("r2", "-1"))), + new Case("neg", """ + r1 =-4; + """, + List.of( + ev("r0", "4"), + ev("r2", "0"))))); + } + + @Test + public void testIntNegateOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = ~r1; + r6l = ~r7l; + """), + List.of( + new Case("pos", """ + r1 =4; + r7l =4; + """, + List.of( + ev("r0", "-5"), + ev("r6l", "-5"))), + new Case("neg", """ + r1 =-4; + r7l =-4; + """, + List.of( + ev("r0", "3"), + ev("r6l", "3"))))); + } + + @Test + public void testIntNegateMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp0:9 = ~temp1; + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1 = 4; + """, + List.of( + ev("r0", "-5"), + ev("r2", "-1"))), + new Case("neg", """ + r1 = -4; + """, + List.of( + ev("r0", "3"), + ev("r2", "0"))))); + } + + @Test + public void testIntSExtOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = sext(r1l); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "4"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "-4"))))); + } + + @Test + public void testIntSExtMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:9 = sext(r1l); + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "4"), + ev("r2", "0"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "-4"), + ev("r2", "-1"))))); + } + + @Test + public void testIntZExtOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = zext(r1l); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "4"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "0xfffffffc"))))); + } + + @Test + public void testIntZExtMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:9 = zext(r1l); + r0 = temp0(0); + r2 = temp0(1); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "4"), + ev("r2", "0"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "0xfffffffc"), + ev("r2", "0xffffff"))))); + } + + @Test + public void testLzCountOpGen() throws Exception { + // Test size change, even though not necessary here + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = lzcount(r1l); + + temp:3 = r3(0); + r2 = lzcount(temp); + """), + List.of( + new Case("pos", """ + r1l =4; + r3 =4; + """, + List.of( + ev("r0", "29"), + ev("r2", "21"))), + new Case("neg", """ + r1l =-4; + r3 =-4; + """, + List.of( + ev("r0", "0"), + ev("r2", "0"))))); + } + + @Test + public void testLzCountMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1s:9 = sext(r1); + temp1z:9 = zext(r1); + r0 = lzcount(temp1s); + r2 = lzcount(temp1z); + """), + List.of( + new Case("pos", """ + r1 =4; + """, + List.of( + ev("r0", "69"), + ev("r2", "69"))), + new Case("neg", """ + r1 =-4; + """, + List.of( + ev("r0", "0"), + ev("r2", "8"))))); + } + + @Test + public void testPopCountOpGen() throws Exception { + // Test size change, even though not necessary here + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = popcount(r1l); + """), + List.of( + new Case("pos", """ + r1l =4; + """, + List.of( + ev("r0", "1"))), + new Case("neg", """ + r1l =-4; + """, + List.of( + ev("r0", "30"))))); + } + + @Test + public void testPopCountMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1s:9 = sext(r1); + temp1z:9 = zext(r1); + r0 = popcount(temp1s); + r2 = popcount(temp1z); + """), + List.of( + new Case("pos", """ + r1 =4; + """, + List.of( + ev("r0", "1"), + ev("r2", "1"))), + new Case("neg", """ + r1 =-4; + """, + List.of( + ev("r0", "70"), + ev("r2", "62"))))); + } + + @Test + public void testSubPieceOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0l = r1(3); + r3 = r4l(3); + """), + List.of( + new Case("only", """ + r1 =0x%x; + r4l=0x12345678; + """.formatted(LONG_CONST), + List.of( + ev("r0l", "0xadbeefca"), + ev("r3", "0x12"))))); + } + + @Test + public void testSubPieceMpIntConst9_0() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:9 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst9_1() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:9 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst10_0() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:10 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst10_1() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:10 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst10_2() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:10 = 0x1122334455667788; + r0 = temp0(2); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x112233445566"))))); + } + + @Test + public void testSubPieceMpIntConst11_0() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:11 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst11_1() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:11 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst11_2() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:11 = 0x1122334455667788; + r0 = temp0(2); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x112233445566"))))); + } + + @Test + public void testSubPieceMpIntConst11_3() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:11 = 0x1122334455667788; + r0 = temp0(3); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455"))))); + } + + @Test + public void testSubPieceMpIntConst12_0() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:12 = 0x1122334455667788; + r0 = temp0(0); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455667788"))))); + } + + @Test + public void testSubPieceMpIntConst12_1() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:12 = 0x1122334455667788; + r0 = temp0(1); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344556677"))))); + } + + @Test + public void testSubPieceMpIntConst12_2() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:12 = 0x1122334455667788; + r0 = temp0(2); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x112233445566"))))); + } + + @Test + public void testSubPieceMpIntConst12_3() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:12 = 0x1122334455667788; + r0 = temp0(3); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x1122334455"))))); + } + + @Test + public void testSubPieceMpIntConst12_4() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:12 = 0x1122334455667788; + r0 = temp0(4); + """), + List.of( + new Case("only", "", List.of( + ev("r0", "0x11223344"))))); + } + + @Test + public void testIntAddOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 + r2; + r9l = r10l + r11l; + """), + List.of( + new Case("only", """ + r1 =2; r2 =2; + r10l=2; r11l=2; + """, + List.of( + ev("r0", "4"), + ev("r9", "4"))))); + } + + protected void runTestIntAddMpIntOpGen(Endian endian) throws Exception { + /** + * NOTE: We copy temp1 and temp2 back into r1 and r2 and assert their values, to ensure the + * input operands remain unmodified. + */ + runEquivalenceTest(translateSleigh(endian.isBigEndian() ? getLanguageID() : ID_TOYLE64, """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 + temp2; + r0 = temp0(0); + r1 = temp1(0); + r2 = temp2(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "4"), ev("r1", "2"), ev("r2", "2"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x087654323456789a"), + ev("r1", "0x8111111122222222"), + ev("r2", "0x8765432112345678"))))); + } + + @Test + public void testIntAddMpIntOpGenBE() throws Exception { + runTestIntAddMpIntOpGen(Endian.BIG); + } + + @Test + public void testIntAddMpIntOpGenLE() throws Exception { + runTestIntAddMpIntOpGen(Endian.LITTLE); + } + + @Test + public void testIntSubOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 - r2; + r9l = r10l - r11l; + """), + List.of( + new Case("only", """ + r1 =2; r2 =2; + r10l=2; r11l=2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSubMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 - temp2; + r0 = temp0(0); + r3 = temp0(1); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0xf9abcdf00fedcbaa"), + ev("r3", "0xfff9abcdf00fedcb"))))); + } + + @Test + public void testIntMultOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 * r2; + r9l = r10l * r11l; + """), + List.of( + new Case("only", """ + r1 =2; r2 =2; + r10l=2; r11l=2; + """, + List.of( + ev("r0", "4"), + ev("r9", "4"))))); + } + + @Test + public void testIntMultMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp0:16 = zext(r1) * zext(r2); + r0 = temp0[0,64]; + r3 = temp0[64,64]; + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 7; + """, + List.of( + ev("r0", "14"), + ev("r3", "0"))), + new Case("large", """ + r1 = 0xffeeddccbbaa9988; r2 = 0x8877665544332211; + """, + List.of( + ev("r0", "0x30fdc971d4d04208"), + ev("r3", "0x886e442c48bba72d"))))); + } + + @Test + public void testIntDivOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 / r2; + r9l = r10l / r11l; + """), + List.of( + new Case("pp", """ + r1 =5; r2 =2; + r10l=5; r11l=2; + """, + List.of( + ev("r0", "2"), + ev("r9", "2"))), + new Case("pn", """ + r1 =5; r2 =-2; + r10l=5; r11l=-2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("np", """ + r1 =-5; r2 =2; + r10l=-5; r11l=2; + """, + List.of( + ev("r0", "0x7ffffffffffffffd"), + ev("r9", "0x7ffffffd"))), + new Case("nn", """ + r1 =-5; r2 =-2; + r10l=-5; r11l=-2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntDivOpGenWith3ByteOperand() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp:3 = r1 + r2; + r0 = temp / r0; + """), + List.of( + new Case("only", """ + r1 = 0xdead; + r2 = 0xbeef; + r0 = 4; + """, + List.of( + ev("r0", "0x6767"))))); + } + + @Test + public void testIntDivMpIntOpGenNonUniform() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + r0l = temp1 / r2; + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x2ee95b10"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x00000000"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x0e658826"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x000000ff"))))); + } + + @Test + public void testIntDivMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local quotient = temp1 / temp2; + r0l = quotient(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x2ee95b10"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x00000000"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0l", "0x0e658826"))), + // NOTE: Result differs from NonUniform, because r2 is also sext()ed + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0l", "0x00000000"))))); + } + + @Test + public void testIntSDivOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 s/ r2; + r9l = r10l s/ r11l; + """), + List.of( + new Case("pp", """ + r1 =5; r2 =2; + r10l=5; r11l=2; + """, + List.of( + ev("r0", "2"), + ev("r9l", "2"))), + new Case("pn", """ + r1 =5; r2 =-2; + r10l=5; r11l=-2; + """, + List.of( + ev("r0", "-2"), + ev("r9l", "-2"))), + new Case("np", """ + r1 =-5; r2 =2; + r10l=-5; r11l=2; + """, + List.of( + ev("r0", "-2"), + ev("r9l", "-2"))), + new Case("nn", """ + r1 =-5; r2 =-2; + r10l=-5; r11l=-2; + """, + List.of( + ev("r0", "2"), + ev("r9l", "2"))))); + } + + @Test + public void testIntSDivMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local quotient = temp1 s/ temp2; + r0l = quotient(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x2ee95b10"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0xd116a4f0"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0xd116a4f0"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0x2ee95b10"))))); + } + + @Test + public void testIntRemOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 % r2; + r9l = r10l % r11l; + """), + List.of( + new Case("pp", """ + r1 =5; r2 =2; + r10l=5; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), + new Case("pn", """ + r1 =5; r2 =-2; + r10l=5; r11l=-2; + """, + List.of( + ev("r0", "5"), + ev("r9l", "5"))), + new Case("np", """ + r1 =-5; r2 =2; + r10l=-5; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), + new Case("nn", """ + r1 =-5; r2 =-2; + r10l=-5; r11l=-2; + """, + List.of( + ev("r0", "-5"), + ev("r9l", "-5"))))); + } + + @Test + public void testIntRemMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local remainder = temp1 % temp2; + r0l = remainder(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x0c49"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0xefcdab89"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x00bf"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0x10325477"))))); + } + + @Test + public void testIntSRemOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 s% r2; + r9l = r10l s% r11l; + """), + List.of( + new Case("pp", """ + r1 =5; r2 =2; + r10l=5; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), + new Case("pn", """ + r1 =5; r2 =-2; + r10l=5; r11l=-2; + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), + new Case("np", """ + r1 =-5; r2 =2; + r10l=-5; r11l=2; + """, + List.of( + ev("r0", "-1"), + ev("r9l", "-1"))), + new Case("nn", """ + r1 =-5; r2 =-2; + r10l=-5; r11l=-2; + """, + List.of( + ev("r0", "-1"), + ev("r9l", "-1"))))); + } + + @Test + public void testIntSRemMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + local quotient = temp1 s% temp2; + r0l = quotient(0); + """), + List.of( + new Case("pp", """ + r1 = 0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0x0c49"))), + new Case("pn", """ + r1 = 0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0x0c49"))), + new Case("np", """ + r1 = -0x67452301efcdab89; + r2 = 0x1234; + """, + List.of( + ev("r0", "0xfffff3b7"))), + new Case("nn", """ + r1 = -0x67452301efcdab89; + r2 = -0x1234; + """, + List.of( + ev("r0", "0xfffff3b7"))))); + } + + @Test + public void testIntAndOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 & r2; + r9l = r10l & r11l; + """), + List.of( + new Case("only", """ + r1 =0x3; r2 =0x5; + r10l=0x3; r11l=0x5; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntAndMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 & temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "2"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x8101010102200220"))))); + } + + @Test + public void testIntOrOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 | r2; + r9l = r10l | r11l; + """), + List.of( + new Case("only", """ + r1 =0x3; r2 =0x5; + r10l=0x3; r11l=0x5; + """, + List.of( + ev("r0", "7"), + ev("r9", "7"))))); + } + + @Test + public void testIntOrMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 | temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "2"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x877553313236767a"))))); + } + + @Test + public void testIntXorOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 ^ r2; + r9l = r10l ^ r11l; + """), + List.of( + new Case("only", """ + r1 =0x3; r2 =0x5; + r10l=0x3; r11l=0x5; + """, + List.of( + ev("r0", "6"), + ev("r9", "6"))))); + } + + @Test + public void testIntXorMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1); + temp2:9 = zext(r2); + temp0:9 = temp1 ^ temp2; + r0 = temp0(0); + """), + List.of( + new Case("small", """ + r1 = 2; r2 = 2; + """, + List.of( + ev("r0", "0"))), + new Case("large", """ + r1 = 0x8111111122222222; r2 = 0x8765432112345678; + """, + List.of( + ev("r0", "0x67452303016745a"))))); + } + + @Test + public void testIntEqualOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 == r2; + r9l = r10l == r11l; + """), + List.of( + new Case("lt", """ + r1 =1; r2 =2; + r10l=1; r11l=2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("slt", """ + r1 =-1; r2 =2; + r10l=-1; r11l=2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("eq", """ + r1 =1; r2 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("gt", """ + r1 =2; r2 =1; + r10l=2; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("sgt", """ + r1 =2; r2 =-1; + r10l=2; r11l=-1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 == temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "0"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "0"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "0"))))); + } + + @Test + public void testIntNotEqualOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 != r2; + r9l = r10l != r11l; + """), + List.of( + new Case("lt", """ + r1 =1; r2 =2; + r10l=1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("slt", """ + r1 =-1; r2 =2; + r10l=-1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("eq", """ + r1 =1; r2 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("gt", """ + r1 =2; r2 =1; + r10l=2; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("sgt", """ + r1 =2; r2 =-1; + r10l=2; r11l=-1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntNotEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 != temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "1"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "1"))))); + } + + @Test + public void testIntLessEqualOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 <= r2; + r9l = r10l <= r11l; + """), + List.of( + new Case("lt", """ + r1 =1; r2 =2; + r10l=1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("slt", """ + r1 =-1; r2 =2; + r10l=-1; r11l=2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("eq", """ + r1 =1; r2 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("gt", """ + r1 =2; r2 =1; + r10l=2; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("sgt", """ + r1 =2; r2 =-1; + r10l=2; r11l=-1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntLessEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 <= temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "0"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "1"))))); + } + + @Test + public void testIntSLessEqualOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 s<= r2; + r9l = r10l s<= r11l; + """), + List.of( + new Case("lt", """ + r1 =1; r2 =2; + r10l=1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("slt", """ + r1 =-1; r2 =2; + r10l=-1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("eq", """ + r1 =1; r2 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("gt", """ + r1 =2; r2 =1; + r10l=2; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("sgt", """ + r1 =2; r2 =-1; + r10l=2; r11l=-1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSLessEqualMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 s<= temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "1"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "1"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "0"))))); + } + + @Test + public void testIntLessOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 < r2; + r9l = r10l < r11l; + """), + List.of( + new Case("lt", """ + r1 =1; r2 =2; + r10l=1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("slt", """ + r1 =-1; r2 =2; + r10l=-1; r11l=2; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("eq", """ + r1 =1; r2 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("gt", """ + r1 =2; r2 =1; + r10l=2; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("sgt", """ + r1 =2; r2 =-1; + r10l=2; r11l=-1; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntLessMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 < temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "0"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "1"))))); + } + + @Test + public void testIntSLessOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 s< r2; + r9l = r10l s< r11l; + """), + List.of( + new Case("lt", """ + r1 =1; r2 =2; + r10l=1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("slt", """ + r1 =-1; r2 =2; + r10l=-1; r11l=2; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("eq", """ + r1 =1; r2 =1; + r10l=1; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("gt", """ + r1 =2; r2 =1; + r10l=2; r11l=1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("sgt", """ + r1 =2; r2 =-1; + r10l=2; r11l=-1; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSLessMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = sext(r2); + r0 = temp1 s< temp2; + """), + List.of( + new Case("lt", """ + r1 = 1; r2 = 2; + """, + List.of( + ev("r0", "1"))), + new Case("slt", """ + r1 = -1; r2 = 0x7fffffffffffffff; + """, + List.of( + ev("r0", "1"))), + new Case("eq", """ + r1 = 1; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("gt", """ + r1 = 2; r2 = 1; + """, + List.of( + ev("r0", "0"))), + new Case("sgt", """ + r1 = 2; r2 = -1; + """, + List.of( + ev("r0", "0"))))); + } + + @Test + public void testIntCarryOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = carry(r1, r2); + r9l = carry(r10l, r11l); + """), + List.of( + new Case("f", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("t", """ + r1 =0x8000000000000000; r2 =0x8000000000000000; + r10l=0x80000000; r11l=0x80000000; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntCarryMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1) << 8; + temp2:9 = zext(r2) << 8; + r0 = carry(temp1, temp2); + """), + List.of( + new Case("f", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"))), + new Case("t", """ + r1 =0x8000000000000000; r2 =0x8000000000000000; + r10l=0x80000000; r11l=0x80000000; + """, + List.of( + ev("r0", "1"))))); + } + + @Test + public void testIntSCarryOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = scarry(r1, r2); + r9l = scarry(r10l, r11l); + """), + List.of( + new Case("f", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), + new Case("t", """ + r1 =0x4000000000000000; r2 =0x4000000000000000; + r10l=0x40000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))))); + } + + @Test + public void testIntSCarryMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1) << 8; + temp2:9 = zext(r2) << 8; + r0 = scarry(temp1, temp2); + """), + List.of( + new Case("f", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"))), + new Case("t", """ + r1 =0x4000000000000000; r2 =0x4000000000000000; + r10l=0x40000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"))))); + } + + @Test + public void testIntSBorrowOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = sborrow(r1, r2); + r9l = sborrow(r10l, r11l); + """), + List.of( + new Case("t", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), + new Case("f", """ + r1 =0xc000000000000000; r2 =0x4000000000000000; + r10l=0xc0000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))))); + } + + @Test + public void testIntSBorrowMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = zext(r1) << 8; + temp2:9 = zext(r2) << 8; + r0 = sborrow(temp1, temp2); + """), + List.of( + new Case("t", """ + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "1"))), + new Case("f", """ + r1 =0xc000000000000000; r2 =0x4000000000000000; + r10l=0xc0000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"))))); + } + + @Test + public void testIntLeftOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 << r2; + r3 = r4 << r5l; + r6l = r7l << r8; + r9l = r10l << r11l; + """), + List.of( + new Case("posLposR", """ + r1 =100; r2 =4; + r4 =100; r5l =4; + r7l =100; r8 =4; + r10l=100; r11l=4; + """, + List.of( + ev("r0", "0x640"), + ev("r3", "0x640"), + ev("r6l", "0x640"), + ev("r9l", "0x640"))), + new Case("posLbigR", """ + r1 =100; r2 =0x100000004; + r4 =100; r5l =0x100000004; + r7l =100; r8 =0x100000004; + r10l=100; r11l=0x100000004; + """, + List.of( + ev("r0", "0"), + ev("r3", "0x640"), + ev("r6l", "0"), + ev("r9l", "0x640"))), + new Case("posLnegR", """ + r1 =100; r2 =-4; + r4 =100; r5l =-4; + r7l =100; r8 =-4; + r10l=100; r11l=-4; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))), + new Case("negLposR", """ + r1 =-100; r2 =4; + r4 =-100; r5l =4; + r7l =-100; r8 =4; + r10l=-100; r11l=4; + """, + List.of( + ev("r0", "-0x640"), + ev("r3", "-0x640"), + ev("r6l", "-0x640"), + ev("r9l", "-0x640"))), + new Case("negLnegR", """ + r1 =-100; r2 =-4; + r4 =-100; r5l =-4; + r7l =-100; r8 =-4; + r10l=-100; r11l=-4; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))))); + } + + @Test + public void testIntLeftMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = (zext(r2) << 64) + r3; + temp0:9 = temp1 << temp2; + r0 = temp0(0); + r4 = temp0(1); + """), + List.of( + new Case("posLposR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xedcba98765432100"), + ev("r4", "0x07edcba987654321"))), + new Case("posLmedR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x6543210000000000"), + ev("r4", "0x8765432100000000"))), + new Case("posLbigR", """ + r1 = 0x7edcba9876543210; + r2 = 0x40; + r3 = 4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("posLnegR", """ + r1 = 0x7edcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("negLposR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xedcba98765432100"), + ev("r4", "0xffedcba987654321"))), + new Case("negLnegR", """ + r1 = 0xfedcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))))); + } + + @Test + public void testIntRightOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 >> r2; + r3 = r4 >> r5l; + r6l = r7l >> r8; + r9l = r10l >> r11l; + """), + List.of( + new Case("posLposR", """ + r1 =100; r2 =4; + r4 =100; r5l =4; + r7l =100; r8 =4; + r10l=100; r11l=4; + """, + List.of( + ev("r0", "6"), + ev("r3", "6"), + ev("r6l", "6"), + ev("r9l", "6"))), + new Case("posLbigR", """ + r1 =100; r2 =0x100000004; + r4 =100; r5l =0x100000004; + r7l =100; r8 =0x100000004; + r10l=100; r11l=0x100000004; + """, + List.of( + ev("r0", "0"), + ev("r3", "6"), + ev("r6l", "0"), + ev("r9l", "6"))), + new Case("posLnegR", """ + r1 =100; r2 =-4; + r4 =100; r5l =-4; + r7l =100; r8 =-4; + r10l=100; r11l=-4; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))), + new Case("negLposR", """ + r1 =-100; r2 =4; + r4 =-100; r5l =4; + r7l =-100; r8 =4; + r10l=-100; r11l=4; + """, + List.of( + ev("r0", "0x0ffffffffffffff9"), + ev("r3", "0x0ffffffffffffff9"), + ev("r6l", "0x0ffffff9"), + ev("r9l", "0x0ffffff9"))), + new Case("negLnegR", """ + r1 =-100; r2 =-4; + r4 =-100; r5l =-4; + r7l =-100; r8 =-4; + r10l=-100; r11l=-4; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))))); + } + + @Test + public void testIntRightMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = (zext(r2) << 64) + r3; + temp0:9 = temp1 >> temp2; + r0 = temp0(0); + r4 = temp0(1); + """), + List.of( + new Case("posLposR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0x07edcba987654321"), + ev("r4", "0x0007edcba9876543"))), + new Case("posLmedR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x0000000007edcba9"), + ev("r4", "0x000000000007edcb"))), + new Case("posLbigR", """ + r1 = 0x7edcba9876543210; + r2 = 0x40; + r3 = 4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("posLnegR", """ + r1 = 0x7edcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("negLposR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xffedcba987654321"), + ev("r4", "0x0fffedcba9876543"))), + new Case("negLmedR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x0000000fffedcba9"), + ev("r4", "0x000000000fffedcb"))), + new Case("negLnegR", """ + r1 = 0xfedcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))))); + } + + @Test + public void testIntSRightOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + r0 = r1 s>> r2; + r3 = r4 s>> r5l; + r6l = r7l s>> r8; + r9l = r10l s>> r11l; + """), + List.of( + new Case("posLposR", """ + r1 =100; r2 =4; + r4 =100; r5l =4; + r7l =100; r8 =4; + r10l=100; r11l=4; + """, + List.of( + ev("r0", "6"), + ev("r3", "6"), + ev("r6l", "6"), + ev("r9l", "6"))), + new Case("posLbigR", """ + r1 =100; r2 =0x100000004; + r4 =100; r5l =0x100000004; + r7l =100; r8 =0x100000004; + r10l=100; r11l=0x100000004; + """, + List.of( + ev("r0", "0"), + ev("r3", "6"), + ev("r6l", "0"), + ev("r9l", "6"))), + new Case("posLnegR", """ + r1 =100; r2 =-4; + r4 =100; r5l =-4; + r7l =100; r8 =-4; + r10l=100; r11l=-4; + """, + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6l", "0"), + ev("r9l", "0"))), + new Case("negLposR", """ + r1 =-100; r2 =4; + r4 =-100; r5l =4; + r7l =-100; r8 =4; + r10l=-100; r11l=4; + """, + List.of( + ev("r0", "-7"), + ev("r3", "-7"), + ev("r6l", "-7"), + ev("r9l", "-7"))), + new Case("negLnegR", """ + r1 =-100; r2 =-4; + r4 =-100; r5l =-4; + r7l =-100; r8 =-4; + r10l=-100; r11l=-4; + """, + List.of( + ev("r0", "-1"), + ev("r3", "-1"), + ev("r6l", "-1"), + ev("r9l", "-1"))))); + } + + @Test + public void testIntSRight3IntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:3 = r1(0); + temp0:3 = temp1 s>> r2; + r0 = zext(temp0); + """), + List.of( + new Case("posLposR", """ + r1 = 0xfedcba; + r2 = 4; + """, + List.of( + ev("r0", "0xffedcb"))))); + } + + @Test + public void testIntSRightMpIntOpGen() throws Exception { + runEquivalenceTest(translateSleigh(getLanguageID(), """ + temp1:9 = sext(r1); + temp2:9 = (zext(r2) << 64) + r3; + temp0:9 = temp1 s>> temp2; + r0 = temp0(0); + r4 = temp0(1); + """), + List.of( + new Case("posLposR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0x07edcba987654321"), + ev("r4", "0x0007edcba9876543"))), + new Case("posLmedR", """ + r1 = 0x7edcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0x0000000007edcba9"), + ev("r4", "0x000000000007edcb"))), + new Case("posLbigR", """ + r1 = 0x7edcba9876543210; + r2 = 0x40; + r3 = 4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("posLnegR", """ + r1 = 0x7edcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "0"), + ev("r4", "0"))), + new Case("negLposR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 4; + """, + List.of( + ev("r0", "0xffedcba987654321"), + ev("r4", "0xffffedcba9876543"))), + new Case("negLlegR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 32; + """, + List.of( + ev("r0", "0xfffffffffedcba98"), + ev("r4", "0xfffffffffffedcba"))), + new Case("negLmedR", """ + r1 = 0xfedcba9876543210; + r2 = 0; + r3 = 36; + """, + List.of( + ev("r0", "0xffffffffffedcba9"), + ev("r4", "0xffffffffffffedcb"))), + new Case("negLnegR", """ + r1 = 0xfedcba9876543210; + r2 = -1; + r3 = -4; + """, + List.of( + ev("r0", "-1"), + ev("r4", "-1"))))); + } + + @Test + public void testFloatAsOffset() throws Exception { + int fDot5 = Float.floatToRawIntBits(0.5f); + int f1Dot0 = Float.floatToRawIntBits(1.0f); + Translation tr = translateSleigh(getLanguageID(), """ + temp:4 = 0x%x f+ 0x%x; + temp2:8 = *temp; + """.formatted(fDot5, fDot5)); + Varnode temp2 = tr.program().getCode().get(1).getOutput(); + assertTrue(temp2.isUnique()); + tr.setLongMemVal(f1Dot0, LONG_CONST, 8); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongVnVal(temp2)); + } + + @Test + public void testDoubleAsOffset() throws Exception { + long dDot5 = Double.doubleToRawLongBits(0.5); + long d1Dot0 = Double.doubleToRawLongBits(1.0); + Translation tr = translateSleigh(getLanguageID(), """ + temp:8 = 0x%x f+ 0x%x; + temp2:8 = *temp; + """.formatted(dDot5, dDot5)); + Varnode temp2 = tr.program().getCode().get(1).getOutput(); + assertTrue(temp2.isUnique()); + tr.setLongMemVal(d1Dot0, LONG_CONST, 8); + tr.runFallthrough(); + assertEquals(LONG_CONST, tr.getLongVnVal(temp2)); + } + + /** + * This is more diagnostics, but at the least, I should document that it doesn't work as + * expected, or perhaps just turn it completely off. + */ + @Test + @Ignore("TODO") + public void testUninitializedVsInitializedReads() { + TODO(); + } + + @Test + public void testDelaySlot() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + brds r0 + imm r0, #123 + """, Map.of()); + tr.setLongRegVal("r0", 0x1234); + assertEquals(0x1234, tr.runClean()); + assertEquals(123, tr.getLongRegVal("r0")); + } + + @Test + public void testEmuInjectionCallEmuSwi() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + imm r0,#123 + add r0,#7 + """, + Map.ofEntries( + Map.entry(0x00400002L, "emu_swi();"))); + + tr.runErr(InterruptPcodeExecutionException.class, "Execution hit breakpoint"); + + /** + * Two reasons we don't reach the add: 1) It's overridden, and there's no deferral to the + * decoded instruction. 2) Even if there were, we got interrupted before it executed. + */ + assertEquals(123, tr.getLongRegVal("r0")); + } + + @Test + public void testEmuInjectionCallEmuExecDecoded() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + imm r0,#123 + add r0,#7 + """, Map.ofEntries( + Map.entry(0x00400002L, """ + r1 = sleigh_userop(r0, 4:8); + emu_exec_decoded(); + """))); + + tr.runDecodeErr(0x00400004); + assertEquals(123 + 7, tr.getLongRegVal("r0")); + assertEquals(123 * 2 + 4, tr.getLongRegVal("r1")); + } + + @Test + public void testEmuInjectionCallEmuSkipDecoded() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + imm r0,#123 + add r0,#7 + """, Map.ofEntries( + Map.entry(0x00400002L, """ + r1 = sleigh_userop(r0, 4:8); + emu_skip_decoded(); + """))); + + tr.runDecodeErr(0x00400004); + assertEquals(123, tr.getLongRegVal("r0")); + assertEquals(123 * 2 + 4, tr.getLongRegVal("r1")); + } + + @Test + public void testFlagOpsRemoved() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + add r0,#6 + add r0,#7 + """, Map.of()); + + tr.runDecodeErr(0x00400004); + assertEquals(13, tr.getLongRegVal("r0")); + + long countSCarrys = Stream.of(tr.run().instructions.toArray()).filter(i -> { + if (!(i instanceof MethodInsnNode mi)) { + return false; + } + return "sCarryLongRaw".equals(mi.name); + }).count(); + assertEquals(1, countSCarrys); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ArmJitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ArmJitCodeGeneratorTest.java new file mode 100644 index 0000000000..c8b8b3d077 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ArmJitCodeGeneratorTest.java @@ -0,0 +1,147 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen; + +import static org.junit.Assert.assertEquals; + +import java.math.BigInteger; +import java.util.Map; + +import org.junit.Test; + +import ghidra.app.plugin.assembler.AssemblyBuffer; +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; + +public class ArmJitCodeGeneratorTest extends AbstractJitCodeGeneratorTest { + protected static final LanguageID ID_ARMv8LE = new LanguageID("ARM:LE:32:v8"); + + @Override + protected LanguageID getLanguageID() { + return ID_ARMv8LE; + } + + @Test + public void testArmThumbFunc() throws Exception { + AssemblyBuffer asm = createBuffer(getLanguageID(), 0x00400000); + + Language language = asm.getAssembler().getLanguage(); + Register regCtx = language.getContextBaseRegister(); + Register regT = language.getRegister("T"); + RegisterValue rvDefault = new RegisterValue(regCtx, + asm.getAssembler().getContextAt(asm.getNext()).toBigInteger(regCtx.getNumBytes())); + RegisterValue rvArm = rvDefault.assign(regT, BigInteger.ZERO); + RegisterValue rvThumb = rvDefault.assign(regT, BigInteger.ONE); + + AssemblyPatternBlock ctxThumb = AssemblyPatternBlock.fromRegisterValue(rvThumb); + + asm.assemble("mov r1, #456"); + Address addrBlx = asm.getNext(); + asm.assemble("blx 0x0"); + Address addrRet = asm.getNext(); // The address where we expect to return + asm.assemble("bx lr"); // Follows CALL, so principally, must be here, but not decoded + Address addrThumb = asm.getNext(); + asm.assemble("add r0, r1", ctxThumb); + asm.assemble("bx lr", ctxThumb); + + asm.assemble(addrBlx, "blx 0x%s".formatted(addrThumb)); + + Translation tr = translateBuffer(asm, asm.getEntry(), Map.of()); + + assertEquals(Map.ofEntries( + tr.entryPrototype(asm.getEntry(), rvArm, 0), + tr.entryPrototype(addrThumb, rvThumb, 1)), + tr.passageCls().getBlockEntries()); + + /** + * The blx will be a direct branch, so that will get executed in the bytecode. However, the + * bx lr (from THUMB) will be an indirect jump, causing a passage exit, so we should expect + * the return value to be the address immediately after the blx. Of course, that's not all + * that convincing.... So, we'll assert that r0 was set, too. + */ + assertEquals(addrRet.getOffset(), tr.runClean()); + assertEquals(456, tr.getLongRegVal("r0")); + } + + @Test + public void testExitAsThumb() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + blx 0x00500000 + """, Map.of()); + Language language = tr.state().getLanguage(); + Register regCtx = language.getContextBaseRegister(); + Register regT = language.getRegister("T"); + + tr.runDecodeErr(0x00500000); + RegisterValue actualCtx = tr.getRegVal(regCtx); + RegisterValue expectedCtx = actualCtx.assign(regT, BigInteger.ONE); + assertEquals(expectedCtx, actualCtx); + } + + @Test + public void testCtxHazardousFallthrough() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + mov r0,#6 + mov r1,#7 + """, Map.ofEntries( + Map.entry(0x00400000L, """ + setISAMode(1:1); + emu_exec_decoded(); + """))); + + tr.runClean(); + assertEquals(6, tr.getLongRegVal("r0")); + // Should not execute second instruction, because of injected ctx change + assertEquals(0, tr.getLongRegVal("r1")); + } + + @Test + public void testCtxMaybeHazardousFallthrough() throws Exception { + /** + * For this test to produce the "MAYBE" case, the multiple paths have to be + * internal to an instruction (or inject). All that logic is only applied on an + * instruction-by-instruction basis. + */ + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + mov r0,#6 + mov r1,#7 + """, Map.ofEntries( + Map.entry(0x00400000L, """ + if (!ZR) goto ; + ISAModeSwitch = 1; + setISAMode(ISAModeSwitch); + + emu_exec_decoded(); + """))); + + tr.setLongRegVal("r1", 0); // Reset + tr.setLongRegVal("ZR", 0); + // Since ctx wasn't touched at runtime, we fall out of program + tr.runDecodeErr(0x00400008); + assertEquals(6, tr.getLongRegVal("r0")); + assertEquals(7, tr.getLongRegVal("r1")); + assertEquals(0, tr.getLongRegVal("ISAModeSwitch")); + + tr.setLongRegVal("r1", 0); // Reset + tr.setLongRegVal("ZR", 1); + // Hazard causes exit before 2nd instruction + tr.runClean(); + assertEquals(6, tr.getLongRegVal("r0")); + assertEquals(0, tr.getLongRegVal("r1")); + assertEquals(1, tr.getLongRegVal("ISAModeSwitch")); + } +} diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java deleted file mode 100644 index 1493f745db..0000000000 --- a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/JitCodeGeneratorTest.java +++ /dev/null @@ -1,3056 +0,0 @@ -/* ### - * IP: GHIDRA - * - * 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 ghidra.pcode.emu.jit.gen; - -import static ghidra.lifecycle.Unfinished.*; -import static org.junit.Assert.*; - -import java.io.*; -import java.lang.invoke.MethodHandles; -import java.math.BigInteger; -import java.nio.file.Files; -import java.util.*; -import java.util.Map.Entry; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.Ignore; -import org.junit.Test; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.*; -import org.objectweb.asm.util.TraceClassVisitor; - -import generic.Unique; -import ghidra.app.plugin.assembler.*; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.emu.PcodeEmulator; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.emu.jit.*; -import ghidra.pcode.emu.jit.JitPassage.AddrCtx; -import ghidra.pcode.emu.jit.analysis.*; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.EntryPointPrototype; -import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; -import ghidra.pcode.error.LowlevelError; -import ghidra.pcode.exec.*; -import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; -import ghidra.pcode.floatformat.FloatFormat; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.*; -import ghidra.program.model.pcode.Varnode; -import ghidra.program.util.DefaultLanguageService; -import ghidra.util.NumericUtilities; - -@SuppressWarnings("javadoc") -public class JitCodeGeneratorTest extends AbstractJitTest { - private static final LanguageID ID_TOYBE64 = new LanguageID("Toy:BE:64:default"); - private static final LanguageID ID_TOYLE64 = new LanguageID("Toy:LE:64:default"); - private static final LanguageID ID_TOYBE32 = new LanguageID("Toy:BE:32:default"); - private static final LanguageID ID_ARMv8LE = new LanguageID("ARM:LE:32:v8"); - private static final LanguageID ID_X8664 = new LanguageID("x86:LE:64:default"); - - private static final long LONG_CONST = 0xdeadbeefcafebabeL; - - // NOTE: Limit logged output in nightly/batch test mode - private static boolean DEBUG_ENABLED = false; // !SystemUtilities.isInTestingBatchMode(); - - private PrintWriter debugWriter = DEBUG_ENABLED ? new PrintWriter(System.out) : null; - - public static void dumpProgram(PcodeProgram program) { - if (!DEBUG_ENABLED) { - return; - } - System.out.println(program); - } - - public static void dumpClass(byte[] classbytes) throws Exception { - if (!DEBUG_ENABLED) { - return; - } - File tmp = Files.createTempFile("gen", ".class").toFile(); - try (FileOutputStream out = new FileOutputStream(tmp)) { - out.write(classbytes); - } - new ProcessBuilder("javap", "-c", "-l", tmp.getPath()).inheritIO().start().waitFor(); - } - - record Translation(PcodeProgram program, MethodNode init, MethodNode run, JitPcodeThread thread, - TestUseropLibrary library, JitBytesPcodeExecutorState state, - JitCompiledPassageClass passageCls, JitCompiledPassage passage) { - - public void runErr(Class excType, String message) { - try { - passage.run(0); - } - catch (Throwable e) { - if (!excType.isInstance(e)) { - fail("Expected error of type " + excType.getSimpleName() + ", but was " + e); - } - assertEquals(message, e.getMessage()); - return; - } - fail("Expected error of type " + excType.getSimpleName() + ", but there was none."); - } - - public void runLowlevelErr(String message) { - runErr(LowlevelError.class, message); - } - - public void runDecodeErr(long pc) { - runErr(DecodePcodeExecutionException.class, - "Unknown disassembly error (PC=%08x)".formatted(pc)); - } - - public void runFallthrough() { - assertEquals(0xdeadbeef, runClean()); - } - - public void runFallthrough32() { - assertEquals(0xdeadbeef, (int) runClean()); - } - - public long runClean() { - passage.run(0); - return thread.getCounter().getOffset(); - } - - public long getLongRegVal(Register reg) { - byte[] raw = state.getVar(reg, Reason.INSPECT); - return thread.getArithmetic().toLong(raw, Purpose.INSPECT); - } - - public RegisterValue getRegVal(Register reg) { - byte[] raw = state.getVar(reg, Reason.INSPECT); - return thread.getArithmetic().toRegisterValue(reg, raw, Purpose.INSPECT); - } - - public long getLongRegVal(String name) { - Register reg = thread.getLanguage().getRegister(name); - return getLongRegVal(reg); - } - - public long getLongVnVal(Varnode vn) { - byte[] raw = state.getVar(vn, Reason.INSPECT); - return thread.getArithmetic().toLong(raw, Purpose.INSPECT); - } - - public long getLongMemVal(long offset, int size) { - AddressSpace space = thread.getLanguage().getDefaultSpace(); - byte[] raw = state.getVar(space, offset, size, false, Reason.INSPECT); - return thread.getArithmetic().toLong(raw, Purpose.INSPECT); - } - - public void setLongRegVal(Register reg, long value) { - byte[] raw = thread.getArithmetic().fromConst(value, reg.getNumBytes()); - state.setVar(reg, raw); - } - - public void setLongRegVal(String name, long value) { - Register reg = thread.getLanguage().getRegister(name); - setLongRegVal(reg, value); - } - - public void setLongVnVal(Varnode vn, long value) { - byte[] raw = thread.getArithmetic().fromConst(value, vn.getSize()); - state.setVar(vn, raw); - } - - public void setLongMemVal(long offset, long value, int size) { - byte[] raw = thread.getArithmetic().fromConst(value, size); - AddressSpace space = thread.getLanguage().getDefaultSpace(); - state.setVar(space, offset, size, false, raw); - } - - public Entry entryPrototype(Address addr, RegisterValue ctx, - int blockId) { - return Map.entry(new AddrCtx(ctx, addr), new EntryPointPrototype(passageCls, blockId)); - } - } - - public Translation translateProgram(PcodeProgram program, JitPcodeThread thread) - throws Exception { - - dumpProgram(program); - - JitAnalysisContext context = makeContext(program, thread); - JitControlFlowModel cfm = new JitControlFlowModel(context); - JitDataFlowModel dfm = new JitDataFlowModel(context, cfm); - JitVarScopeModel vsm = new JitVarScopeModel(cfm, dfm); - JitTypeModel tm = new JitTypeModel(dfm); - JitAllocationModel am = new JitAllocationModel(context, dfm, vsm, tm); - JitOpUseModel oum = new JitOpUseModel(context, cfm, dfm, vsm); - - JitCodeGenerator gen = - new JitCodeGenerator(MethodHandles.lookup(), context, cfm, dfm, vsm, tm, am, oum); - - byte[] classbytes = gen.generate(); - - dumpClass(classbytes); - - ClassNode cn = new ClassNode(Opcodes.ASM9); - ClassReader cr = new ClassReader(classbytes); - cr.accept(new TraceClassVisitor(cn, debugWriter), 0); - - // Have the JVM validate this thing - JitBytesPcodeExecutorState state = thread.getState(); - JitCompiledPassageClass passageCls = - JitCompiledPassageClass.load(MethodHandles.lookup(), classbytes); - JitCompiledPassage passage = passageCls.createInstance(thread); - - assertEquals(Set.of("", "", "run", "thread"), - cn.methods.stream().map(m -> m.name).collect(Collectors.toSet())); - - MethodNode initMethod = - Unique.assertOne(cn.methods.stream().filter(m -> "".equals(m.name))); - MethodNode runMethod = - Unique.assertOne(cn.methods.stream().filter(m -> "run".equals(m.name))); - return new Translation(program, initMethod, runMethod, thread, - (TestUseropLibrary) thread.getMachine().getUseropLibrary(), state, passageCls, passage); - } - - public static class TestUseropLibrary extends AnnotatedPcodeUseropLibrary { - boolean gotJavaUseropCall = false; - boolean gotFuncUseropCall = false; - boolean gotSleighUseropCall = false; - - @PcodeUserop - public long java_userop(long a, long b) { - gotJavaUseropCall = true; - return 2 * a + b; - } - - @PcodeUserop(functional = true) - public long func_userop(long a, long b) { - gotFuncUseropCall = true; - return 2 * a + b; - } - - @PcodeUserop(functional = true) - public void func_mpUserop(@OpOutput int[] out, int[] a, int[] b) { - gotFuncUseropCall = true; - - if (out == null) { - return; - } - - out[0] = b[0]; - out[1] = a[0]; - for (int i = 0; i < 8; i++) { - out[0] |= out[0] << 4; - out[1] |= out[1] << 4; - } - } - - @PcodeUserop(canInline = true) - public void sleigh_userop(@OpExecutor PcodeExecutor executor, - @OpLibrary PcodeUseropLibrary library, @OpOutput Varnode out, Varnode a, - Varnode b) { - gotSleighUseropCall = true; - PcodeProgram opProg = SleighProgramCompiler.compileUserop(executor.getLanguage(), - "sleigh_userop", List.of("__result", "a", "b"), """ - __result = 2*a + b; - """, library, List.of(out, a, b)); - executor.execute(opProg, library); - } - - @PcodeUserop(functional = true) - public int tap_int(int a) { - System.err.println("tap: %x".formatted(a)); - return a; - } - } - - public static class TestJitPcodeEmulator extends JitPcodeEmulator { - public TestJitPcodeEmulator(Language language) { - super(language, new JitConfiguration(), MethodHandles.lookup()); - } - - @Override - protected PcodeUseropLibrary createUseropLibrary() { - return new TestUseropLibrary(); - } - } - - public static class TestPlainPcodeEmulator extends PcodeEmulator { - public TestPlainPcodeEmulator(Language language) { - super(language); - } - - @Override - protected PcodeUseropLibrary createUseropLibrary() { - return new TestUseropLibrary(); - } - } - - record Eval(String expr, BigInteger value) { - } - - static Eval ev(String name, BigInteger value) { - return new Eval(name, value); - } - - static Eval ev(String name, String value) { - BigInteger bi = NumericUtilities.decodeBigInteger(value); - return ev(name, bi); - } - - static Eval ev(String name, double value) { - BigInteger bi = BigInteger.valueOf(Double.doubleToRawLongBits(value)); - return new Eval(name, bi); - } - - static Eval ev(String name, float value) { - BigInteger bi = BigInteger.valueOf(Integer.toUnsignedLong(Float.floatToRawIntBits(value))); - return new Eval(name, bi); - } - - /** - * @deprecated Because this one is accident prone when it comes to signedness. Use - * {@link #ev(String, String)} instead. - */ - @Deprecated // Just produce a warning - static Eval ev(String name, long value) { - throw new AssertionError("Use the String or BigInteger one instead"); - } - - record Case(String name, String init, List evals) { - } - - static final int nNaNf = Float.floatToRawIntBits(Float.NaN) | Integer.MIN_VALUE; - static final long nNaNd = Double.doubleToRawLongBits(Double.NaN) | Long.MIN_VALUE; - static final BigInteger nNaN_F = BigInteger.valueOf(nNaNf); - static final BigInteger nNaN_D = BigInteger.valueOf(nNaNd); - - protected void runEquivalenceTest(Translation tr, List cases) { - PcodeEmulator plainEmu = new TestPlainPcodeEmulator(tr.program.getLanguage()); - PcodeThread plainThread = plainEmu.newThread(); - - for (Case c : cases) { - if (!c.init.isBlank()) { - plainThread.getExecutor().executeSleigh(c.init); - tr.thread.getExecutor().executeSleigh(c.init); - } - - plainThread.getExecutor().execute(tr.program, plainThread.getUseropLibrary()); - assertEquals("Mismatch of PC.", plainThread.getCounter().getOffset(), tr.runClean()); - - for (Eval e : c.evals) { - PcodeExpression expr = - SleighProgramCompiler.compileExpression(tr.program.getLanguage(), e.expr); - BigInteger plnResult = plainThread.getArithmetic() - .toBigInteger(expr.evaluate(plainThread.getExecutor()), Purpose.INSPECT); - BigInteger jitResult = tr.thread.getArithmetic() - .toBigInteger(expr.evaluate(tr.thread.getExecutor()), Purpose.INSPECT); - - BigInteger expResult = - new RegisterValue(tr.program.getLanguage().getRegister(e.expr), e.value) - .getUnsignedValue(); - - assertEquals( - "WRONG ASSERTION For case '%s': Mismatch of '%s'.".formatted(c.name, e.expr), - expResult.toString(16), plnResult.toString(16)); - assertEquals("For case '%s': Mismatch of '%s'.".formatted(c.name, e.expr), - expResult.toString(16), jitResult.toString(16)); - } - } - } - - public Translation translateSleigh(LanguageID langId, String source) throws Exception { - SleighLanguage language = - (SleighLanguage) DefaultLanguageService.getLanguageService().getLanguage(langId); - List lines = new ArrayList<>(Arrays.asList(source.split("\n"))); - if (!lines.getLast().startsWith("goto ")) { - // Cannot end with fall-through - // TODO: how to specify positive? - lines.add("goto 0xdeadbeef;"); - source = lines.stream().collect(Collectors.joining("\n")); - } - JitPcodeEmulator emu = new TestJitPcodeEmulator(language); - JitPcodeThread thread = emu.newThread(); - PcodeProgram program = SleighProgramCompiler.compileProgram(language, "test", source, - thread.getUseropLibrary()); - return translateProgram(program, thread); - } - - public AssemblyBuffer createBuffer(LanguageID languageID, long entry) throws Exception { - Language language = DefaultLanguageService.getLanguageService().getLanguage(languageID); - Address addr = language.getDefaultSpace().getAddress(entry); - Assembler asm = Assemblers.getAssembler(language); - return new AssemblyBuffer(asm, addr); - } - - public Translation translateBuffer(AssemblyBuffer buf, Address entry, Map injects) - throws Exception { - Language language = buf.getAssembler().getLanguage(); - - JitPcodeEmulator emu = new TestJitPcodeEmulator(language); - AddressSpace space = language.getDefaultSpace(); - for (Map.Entry ent : injects.entrySet()) { - emu.inject(space.getAddress(ent.getKey()), ent.getValue()); - } - JitPcodeThread thread = emu.newThread(); - byte[] bytes = buf.getBytes(); - emu.getSharedState().setVar(buf.getEntry(), bytes.length, false, bytes); - - thread.setCounter(entry); - thread.overrideContextWithDefault(); - JitPassage passage = decodePassage(thread); - return translateProgram(passage, thread); - } - - public Translation translateLang(LanguageID languageID, long offset, String source, - Map injects) throws Exception { - AssemblyBuffer buf = createBuffer(languageID, offset); - for (String line : source.split("\n")) { - if (line.isBlank()) { - } - else if (line.startsWith(".emit ")) { - buf.emit(NumericUtilities - .convertStringToBytes(line.substring(".emit ".length()).replace(" ", ""))); - } - else { - buf.assemble(line); - } - } - return translateBuffer(buf, buf.getEntry(), injects); - } - - public Translation translateToy(long offset, String source) throws Exception { - return translateLang(ID_TOYBE64, offset, source, Map.of()); - } - - @Test - public void testSimpleInt() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:4 = 0x1234; - """); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(0x1234, tr.getLongVnVal(temp)); - } - - @Test - public void testToyOneBlockHasFallthroughExit() throws Exception { - Translation tr = translateToy(0x00400000, """ - imm r0, #0x123 - """); - tr.runDecodeErr(0x00400002); - assertEquals(0x123, tr.getLongRegVal("r0")); - } - - @Test - public void testSimpleLong() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:8 = 0x1234; - """); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(0x1234, tr.getLongVnVal(temp)); - } - - @Test - public void testSimpleFloat() throws Exception { - int fDot5 = Float.floatToRawIntBits(0.5f); - int fDot75 = Float.floatToRawIntBits(0.75f); - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:4 = 0x%x f+ 0x%x; - """.formatted(fDot5, fDot75)); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(1.25f, Float.intBitsToFloat((int) tr.getLongVnVal(temp)), 0); - } - - @Test - public void testSimpleDouble() throws Exception { - long dDot5 = Double.doubleToRawLongBits(0.5); - long dDot75 = Double.doubleToRawLongBits(0.75); - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:8 = 0x%x f+ 0x%x; - """.formatted(dDot5, dDot75)); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(1.25f, Double.longBitsToDouble(tr.getLongVnVal(temp)), 0); - } - - @Test - public void testReadMemMappedRegBE() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - * 0:8 = 0x%x:8; - temp:8 = mmr0; - """.formatted(LONG_CONST)); - Varnode temp = tr.program.getCode().get(1).getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - public void testReadMemDirectWithPartsSpanningBlockBE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:8 = * 0x%x:8; - """.formatted(offset)); - tr.setLongMemVal(offset, LONG_CONST, 8); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - public void testReadMemDirectWithPartsSpanningBlockLE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYLE64, """ - temp:8 = * 0x%x:8; - """.formatted(offset)); - tr.setLongMemVal(offset, LONG_CONST, 8); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - @Ignore("Undefined") - public void testReadMemDirectWithSpanWrapSpaceBE() throws Exception { - long offset = -2; - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:8 = * 0x%x:8; - """.formatted(offset)); - tr.setLongMemVal(offset, LONG_CONST, 8); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - @Ignore("Undefined") - public void testReadMemDirectWithSpanWrapSpaceLE() throws Exception { - long offset = -2; - Translation tr = translateSleigh(ID_TOYLE64, """ - temp:8 = * 0x%x:8; - """.formatted(offset)); - tr.setLongMemVal(offset, LONG_CONST, 8); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp.isUnique()); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - public void testWriteMemDirectWithPartsSpanningBlockBE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYBE64, """ - local temp:8; - * 0x%x:8 = temp; - """.formatted(offset)); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - assertTrue(temp.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - public void testWriteMemDirectWithPartsSpanningBlockLE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYLE64, """ - local temp:8; - * 0x%x:8 = temp; - """.formatted(offset)); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - assertTrue(temp.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - @Ignore("Undefined") - public void testWriteMemDirectWithSpanWrapSpaceBE() throws Exception { - long offset = -2; - Translation tr = translateSleigh(ID_TOYBE64, """ - local temp:8; - * 0x%x:8 = temp; - """.formatted(offset)); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - assertTrue(temp.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - @Ignore("Undefined") - public void testWriteMemDirectWithSpanWrapSpaceLE() throws Exception { - long offset = -2; - Translation tr = translateSleigh(ID_TOYLE64, """ - local temp:8; - * 0x%x:8 = temp; - """.formatted(offset)); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - assertTrue(temp.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - public void testReadMemIndirectBE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYBE64, """ - local temp:8; - local addr:8; - temp = * addr; - """); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - Varnode addr = tr.program.getCode().getFirst().getInput(1); - assertTrue(temp.isUnique()); - assertTrue(addr.isUnique()); - tr.setLongMemVal(offset, LONG_CONST, 8); - tr.setLongVnVal(addr, offset); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - public void testReadMemIndirectLE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYLE64, """ - local temp:8; - local addr:8; - temp = * addr; - """); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - Varnode addr = tr.program.getCode().getFirst().getInput(1); - assertTrue(temp.isUnique()); - assertTrue(addr.isUnique()); - tr.setLongMemVal(offset, LONG_CONST, 8); - tr.setLongVnVal(addr, offset); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - @Test - public void testWriteMemIndirectBE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYBE64, """ - local temp:8; - local addr:8; - * addr = temp; - """); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - Varnode addr = tr.program.getCode().getFirst().getInput(1); - assertTrue(temp.isUnique()); - assertTrue(addr.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.setLongVnVal(addr, offset); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - public void testWriteMemIndirectLE() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYLE64, """ - local temp:8; - local addr:8; - * addr = temp; - """); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - Varnode addr = tr.program.getCode().getFirst().getInput(1); - assertTrue(temp.isUnique()); - assertTrue(addr.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.setLongVnVal(addr, offset); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - public void testAddressSize32() throws Exception { - long offset = GenConsts.BLOCK_SIZE - 2; - Translation tr = translateSleigh(ID_TOYBE32, """ - local temp:8; - local addr:4; - * addr = temp; - """); - Varnode temp = tr.program.getCode().getFirst().getInput(2); - Varnode addr = tr.program.getCode().getFirst().getInput(1); - assertTrue(temp.isUnique()); - assertTrue(addr.isUnique()); - tr.setLongVnVal(temp, LONG_CONST); - tr.setLongVnVal(addr, offset); - tr.runFallthrough32(); - assertEquals(LONG_CONST, tr.getLongMemVal(offset, 8)); - } - - @Test - public void testVariablesAreRetiredBranchInd() throws Exception { - /** - * Considering detection of inter-passage indirect branching, I think this will complicate - * things way too much. All of the control-flow analysis must consider indirect flows, and - * then, the dataflow could be affected by that, so there's a circular dependency. With some - * care, that can be done, though I'm not sure it's always guaranteed to converge. Another - * possibility is to retire all the variables, but then, there has to be a special branch - * target that knows to birth the appropriate ones before entering the real block. - */ - Translation tr = translateSleigh(ID_TOYBE64, """ - local jump:8; - temp:8 = 0x%x; - goto [jump]; - """.formatted(LONG_CONST)); - Varnode temp = tr.program.getCode().getFirst().getOutput(); - Varnode jump = tr.program.getCode().get(1).getInput(0); - assertTrue(temp.isUnique()); - assertTrue(jump.isUnique()); - tr.setLongVnVal(jump, 0x1234); - assertEquals(0x1234, tr.runClean()); - assertEquals(LONG_CONST, tr.getLongVnVal(temp)); - } - - /** - * Test reading from a variable in an unreachable block. - * - *

    - * Yes, this test is valid, because, even though no slaspec should produce this, they could, but - * more to the point, a user injection could. Note that the underlying classfile writer may - * analyze and remove the unreachable code. Doesn't matter. What matters is that it doesn't - * crash, and that it produces correct results. - */ - @Test - public void testWithMissingVariable() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - local temp:8; - local temp2:8; - temp2 = 1; - goto 0xdeadbeef; - temp2 = temp; - """); - Varnode temp2 = tr.program.getCode().getFirst().getOutput(); - assertTrue(temp2.isUnique()); - tr.runFallthrough(); - assertEquals(1, tr.getLongVnVal(temp2)); - } - - void runTestMpIntOffcutLoad(LanguageID langID) throws Exception { - runEquivalenceTest(translateSleigh(langID, """ - local temp:16; - temp[0,64] = r1; - temp[64,64] = r2; - temp2:14 = temp[8,112]; - r0 = zext(temp2); - """), List.of(new Case("only", """ - r1 = 0x1122334455667788; - r2 = 0x99aabbccddeeff00; - """, List.of(ev("r0", "0x11223344556677"))))); - } - - @Test - public void testMpIntOffcutLoadBE() throws Exception { - runTestMpIntOffcutLoad(ID_TOYBE64); - } - - @Test - public void testMpIntOffcutLoadLE() throws Exception { - runTestMpIntOffcutLoad(ID_TOYLE64); - } - - @Test - public void testCallOtherSleighDef() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - r0 = sleigh_userop(6:8, 2:8); - """); - assertTrue(tr.library.gotSleighUseropCall); - tr.library.gotSleighUseropCall = false; - tr.runFallthrough(); - assertFalse(tr.library.gotSleighUseropCall); - assertEquals(14, tr.getLongRegVal("r0")); - } - - @Test - public void testCallOtherJavaDef() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - r0 = java_userop(6:8, 2:8); - """); - assertFalse(tr.library.gotJavaUseropCall); - tr.runFallthrough(); - assertTrue(tr.library.gotJavaUseropCall); - assertEquals(14, tr.getLongRegVal("r0")); - } - - @Test - public void testCallOtherJavaDefNoOut() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - java_userop(6:8, 2:8); - """); - assertFalse(tr.library.gotJavaUseropCall); - tr.runFallthrough(); - assertTrue(tr.library.gotJavaUseropCall); - assertEquals(0, tr.getLongRegVal("r0")); - } - - @Test - public void testCallOtherFuncJavaDef() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - r0 = func_userop(6:8, 2:8); - """); - assertFalse(tr.library.gotFuncUseropCall); - tr.runFallthrough(); - assertTrue(tr.library.gotFuncUseropCall); - assertEquals(14, tr.getLongRegVal("r0")); - } - - @Test - public void testCallOtherFuncJavaDefNoOut() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - func_userop(6:8, 2:8); - """); - assertFalse(tr.library.gotFuncUseropCall); - tr.runFallthrough(); - assertTrue(tr.library.gotFuncUseropCall); - assertEquals(0, tr.getLongRegVal("r0")); - } - - @Test - public void testCallOtherFuncJavaDefMpInt() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(6:8); - temp2:9 = zext(2:8); - temp0:9 = func_mpUserop(temp1, temp2); - r0 = temp0(0); - """); - assertFalse(tr.library.gotFuncUseropCall); - tr.runFallthrough(); - assertTrue(tr.library.gotFuncUseropCall); - assertEquals(0x6666666622222222L, tr.getLongRegVal("r0")); - } - - @Test - public void testCallOtherFuncJavaDefNoOutMpInt() throws Exception { - Translation tr = translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(6:8); - temp2:9 = zext(2:8); - func_mpUserop(temp1, temp2); - """); - assertFalse(tr.library.gotFuncUseropCall); - tr.runFallthrough(); - assertTrue(tr.library.gotFuncUseropCall); - assertEquals(0, tr.getLongRegVal("r0")); - } - - /** - * Test that the emulator doesn't throw until the userop is actually encountered at run time. - * - *

    - * NOTE: The userop must be defined by the language, but left undefined by the library. - * Otherwise, we cannot even compile the sample Sleigh. - * - *

    - * NOTE: Must also use actual instructions, because the test passage constructor will fail fast - * on the undefined userop. - */ - @Test - public void testCallOtherUndef() throws Exception { - Translation tr = translateToy(0x00400000, """ - user_one r0 - """); - tr.runErr(SleighLinkException.class, "Sleigh userop 'pcodeop_one' is not in the library"); - assertEquals(0x00400000, tr.thread.getCounter().getOffset()); - } - - /** - * I need to find an example of this: - * - *

    -	 *  *[register] OFFSET = ... ?
    -	 * 
    - * - *

    - * Honestly, if this actually occurs frequently, we could be in trouble. We would need either: - * 1) To re-write all the offending semantic blocks, or 2) Work out a way to re-write them - * during JIT compilation. People tell me this is done by some vector instructions, which makes - * me thing re-writing would be possible, since they should all fold to constants. If we're - * lucky, the planned constant folding would just take care of these; however, I'd still want to - * allocate them as locals, not just direct array access. For now, I'm just going to feign - * ignorance. If it becomes a problem, then we can treat all register accesses like memory - * within the entire passage containing one of these "indirect-register-access" ops. - */ - @Test - @Ignore("No examples, yet") - public void testComputedOffsetsInRegisterSpace() throws Exception { - TODO(); - } - - /** - * This is interesting, because it may necessitate MpInt DIV - */ - @Test - @Ignore("TODO") - public void testX86DIV() throws Exception { - TODO(); - } - - @Test - public void testBranchOpGenInternal() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = 0xbeef; - goto ; - r0 = 0xdead; - - """), List.of(new Case("only", "", List.of(ev("r0", "0xbeef"))))); - } - - @Test - public void testBranchOpGenExternal() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = 0xbeef; - goto 0xdeadbeef; - r0 = 0xdead; - """), List.of(new Case("only", "", List.of(ev("r0", "0xbeef"))))); - } - - @Test - public void testCBranchOpGenInternalIntPredicate() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = 0xbeef; - if (r1!=0) goto ; - r0 = 0xdead; - - """), List.of(new Case("take", "r1=1;", List.of(ev("r0", "0xbeef"))), - new Case("fall", "r1=0;", List.of(ev("r0", "0xdead"))))); - } - - @Test - public void testCBranchOpGenExternalLongPredicate() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = 0xbeef; - if (r1) goto 0xdeadbeef; - r0 = 0xdead; - """), List.of(new Case("take", "r1=1;", List.of(ev("r0", "0xbeef"))), - new Case("fall", "r1=0;", List.of(ev("r0", "0xdead"))))); - } - - @Test - public void testCBranchOpGenExternalMpIntPredicate() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = 0xbeef; - temp:9 = zext(r1); - if (temp) goto 0xdeadbeef; - r0 = 0xdead; - """), - List.of(new Case("sm_take", "r1 = 1;", List.of(ev("r0", "0xbeef"))), - new Case("sm_fall", "r1 = 0;", List.of(ev("r0", "0xdead"))), - new Case("lg_take", "r1 = 0x8000000000000000;", List.of(ev("r0", "0xbeef"))))); - } - - @Test - public void testBoolNegateOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = !r1; - r6l = !r7l; - """), List.of(new Case("f", """ - r1 = 0; - r7l = 0; - """, List.of(ev("r0", "1"), ev("r6", "1"))), new Case("t", """ - r1 = 1; - r7l = 1; - """, List.of(ev("r0", "0"), ev("r6", "0"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testBoolNegateMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp:9 = zext(r1); - temp = !temp; - r0 = temp(1); - """), List.of(new Case("f", """ - r1 = 0; - """, List.of(ev("r0", "0"))))); - } - - @Test - public void testBoolAndOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 && r2; - r3 = r4 && r5l; - r6l = r7l && r8; - r9l = r10l && r11l; - """), - List.of(new Case("ff", """ - r1 =0; r2 =0; - r4 =0; r5l =0; - r7l =0; r8 =0; - r10l=0; r11l=0; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))), - new Case("ft", """ - r1 =0; r2 =1; - r4 =0; r5l =1; - r7l =0; r8 =1; - r10l=0; r11l=1; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))), - new Case("tf", """ - r1 =1; r2 =0; - r4 =1; r5l =0; - r7l =1; r8 =0; - r10l=1; r11l=0; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))), - new Case("tt", """ - r1 =1; r2 =1; - r4 =1; r5l =1; - r7l =1; r8 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testBoolAndMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 && temp2; - r0 = temp0(0); - r3 = temp0(1); - """), List.of(new Case("ff", """ - r1 = 0; r2 = 0; - """, List.of(ev("r0", "0"), ev("r3", "0"))), new Case("ft", """ - r1 =0; r2 = 1; - """, List.of(ev("r0", "0"), ev("r3", "0"))), new Case("tf", """ - r1 = 1; r2 = 0; - """, List.of(ev("r0", "0"), ev("r3", "0"))), new Case("tt", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "1"), ev("r3", "0"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testBoolOrOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 || r2; - r3 = r4 || r5l; - r6l = r7l || r8; - r9l = r10l || r11l; - """), - List.of(new Case("ff", """ - r1 =0; r2 =0; - r4 =0; r5l =0; - r7l =0; r8 =0; - r10l=0; r11l=0; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))), - new Case("ft", """ - r1 =0; r2 =1; - r4 =0; r5l =1; - r7l =0; r8 =1; - r10l=0; r11l=1; - """, List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))), - new Case("tf", """ - r1 =1; r2 =0; - r4 =1; r5l =0; - r7l =1; r8 =0; - r10l=1; r11l=0; - """, List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))), - new Case("tt", """ - r1 =1; r2 =1; - r4 =1; r5l =1; - r7l =1; r8 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testBoolOrMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 || temp2; - r0 = temp0(0); - r3 = temp0(1); - """), List.of(new Case("ff", """ - r1 =0; r2 =0; - """, List.of(ev("r0", "0"), ev("r3", "0"))), new Case("ft", """ - r1 =0; r2 =1; - """, List.of(ev("r0", "1"), ev("r3", "0"))), new Case("tf", """ - r1 =1; r2 =0; - """, List.of(ev("r0", "1"), ev("r3", "0"))), new Case("tt", """ - r1 =1; r2 =1; - """, List.of(ev("r0", "1"), ev("r3", "0"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testBoolXorOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 ^^ r2; - r3 = r4 ^^ r5l; - r6l = r7l ^^ r8; - r9l = r10l ^^ r11l; - """), - List.of(new Case("ff", """ - r1 =0; r2 =0; - r4 =0; r5l =0; - r7l =0; r8 =0; - r10l=0; r11l=0; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))), - new Case("ft", """ - r1 =0; r2 =1; - r4 =0; r5l =1; - r7l =0; r8 =1; - r10l=0; r11l=1; - """, List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))), - new Case("tf", """ - r1 =1; r2 =0; - r4 =1; r5l =0; - r7l =1; r8 =0; - r10l=1; r11l=0; - """, List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))), - new Case("tt", """ - r1 =1; r2 =1; - r4 =1; r5l =1; - r7l =1; r8 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testBoolXorMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 ^^ temp2; - r0 = temp0(0); - r3 = temp0(1); - """), List.of(new Case("ff", """ - r1 =0; r2 =0; - """, List.of(ev("r0", "0"), ev("r3", "0"))), new Case("ft", """ - r1 =0; r2 =1; - """, List.of(ev("r0", "1"), ev("r3", "0"))), new Case("tf", """ - r1 =1; r2 =0; - """, List.of(ev("r0", "1"), ev("r3", "0"))), new Case("tt", """ - r1 =1; r2 =1; - """, List.of(ev("r0", "0"), ev("r3", "0"))))); - // NOTE: Not testing cases with other bits set - } - - @Test - public void testFloatAbsOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - long dn0dot5 = Double.doubleToLongBits(-0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - int fn0dot5 = Float.floatToIntBits(-0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = abs(r1); - r6l = abs(r7l); - """), List.of(new Case("p", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of(ev("r0", 0.5d), ev("r6", 0.5f))), - new Case("n", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of(ev("r0", 0.5d), ev("r6", 0.5f))))); - } - - /** - * Note that the test case for sqrt(n) where n is negative could be brittle, because of - * undefined behavior in the IEEE 754 spec. My JVM will result in "negative NaN." It used to be - * this test failed, but I've made an adjustment to {@link FloatFormat} to ensure it keeps - * whatever sign bit was returned by {@link Math#sqrt(double)}. It shouldn't matter to the - * emulation target one way or another (in theory), but I do want to ensure the two emulators - * behave the same. It seems easier to me to have {@link FloatFormat} keep the sign bit than to - * have the JIT compile in code that checks for and fixes "negative NaN." Less run-time cost, - * too. - */ - @Test - public void testFloatSqrtOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - long dn0dot5 = Double.doubleToLongBits(-0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - int fn0dot5 = Float.floatToIntBits(-0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = sqrt(r1); - r6l = sqrt(r7l); - """), - List.of( - new Case("p", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot5, f0dot5), - List.of(ev("r0", Math.sqrt(0.5)), ev("r6", (float) Math.sqrt(0.5)))), - new Case("n", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), - List.of(ev("r0", nNaN_D), ev("r6l", nNaN_F))))); - } - - @Test - public void testFloatCeilOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - long dn0dot5 = Double.doubleToLongBits(-0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - int fn0dot5 = Float.floatToIntBits(-0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = ceil(r1); - r6l = ceil(r7l); - """), List.of(new Case("p", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of(ev("r0", 1.0d), ev("r6", 1.0f))), - new Case("n", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of(ev("r0", -0.0d), ev("r6", -0.0f))))); - } - - @Test - public void testFloatFloorOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - long dn0dot5 = Double.doubleToLongBits(-0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - int fn0dot5 = Float.floatToIntBits(-0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = floor(r1); - r6l = floor(r7l); - """), List.of(new Case("p", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of(ev("r0", 0.0d), ev("r6", 0.0f))), - new Case("n", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of(ev("r0", -1.0d), ev("r6", -1.0f))))); - } - - @Test - public void testFloatRoundOpGen() throws Exception { - long d0dot25 = Double.doubleToLongBits(0.25); - long dn0dot25 = Double.doubleToLongBits(-0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - int fn0dot25 = Float.floatToIntBits(-0.25f); - long d0dot5 = Double.doubleToLongBits(0.5); - long dn0dot5 = Double.doubleToLongBits(-0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - int fn0dot5 = Float.floatToIntBits(-0.5f); - long d0dot75 = Double.doubleToLongBits(0.75); - long dn0dot75 = Double.doubleToLongBits(-0.75); - int f0dot75 = Float.floatToIntBits(0.75f); - int fn0dot75 = Float.floatToIntBits(-0.75f); - long d1dot0 = Double.doubleToLongBits(1.0); - long dn1dot0 = Double.doubleToLongBits(-1.0); - int f1dot0 = Float.floatToIntBits(1.0f); - int fn1dot0 = Float.floatToIntBits(-1.0f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = round(r1); - r6l = round(r7l); - """), List.of(new Case("+0.25", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot25, f0dot25), List.of(ev("r0", 0.0d), ev("r6", 0.0f))), - new Case("-0.25", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot25, fn0dot25), List.of(ev("r0", 0.0d), ev("r6", 0.0f))), - new Case("+0.5", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of(ev("r0", 1.0d), ev("r6", 1.0f))), - new Case("-0.5", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot5, fn0dot5), List.of(ev("r0", 0.0d), ev("r6", 0.0f))), - new Case("+0.75", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot75, f0dot75), List.of(ev("r0", 1.0d), ev("r6", 1.0f))), - new Case("-0.75", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn0dot75, fn0dot75), List.of(ev("r0", -1.0d), ev("r6", -1.0f))), - new Case("+1.0", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d1dot0, f1dot0), List.of(ev("r0", 1.0d), ev("r6", 1.0f))), - new Case("-1.0", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(dn1dot0, fn1dot0), List.of(ev("r0", -1.0d), ev("r6", -1.0f))))); - } - - @Test - public void testFloat2FloatOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = float2float(r1l); - r6l = float2float(r7); - """), List.of(new Case("only", """ - r1l =0x%x; - r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of(ev("r0", 0.5d), ev("r6", 0.5f))))); - } - - @Test - public void testFloatInt2FloatOpGen() throws Exception { - /** - * The size swap is not necessary, but test anyway. - */ - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = int2float(r1l); - r6l = int2float(r7); - """), List.of(new Case("only", """ - r1l =1; - r7 =2; - """, List.of(ev("r0", 1.0d), ev("r6", 2.0f))))); - } - - @Test - public void testFloatTruncOpGen() throws Exception { - long d1dot0 = Double.doubleToLongBits(1.0); - long d0dot5 = Double.doubleToLongBits(0.5); - long dn0dot5 = Double.doubleToLongBits(-0.5); - int f1dot0 = Float.floatToIntBits(1.0f); - int f0dot5 = Float.floatToIntBits(0.5f); - int fn0dot5 = Float.floatToIntBits(-0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = trunc(r1); - r3 = trunc(r4l); - r6l = trunc(r7); - r9l = trunc(r10l); - """), - List.of( - new Case("+1.0", """ - r1 =0x%x; - r4l =0x%x; - r7 =0x%x; - r10l=0x%x; - """.formatted(d1dot0, f1dot0, d1dot0, f1dot0), - List.of(ev("r0", "1"), ev("r3", "1"), ev("r6", "1"), ev("r9", "1"))), - new Case("+0.5", """ - r1 =0x%x; - r4l =0x%x; - r7 =0x%x; - r10l=0x%x; - """.formatted(d0dot5, f0dot5, d0dot5, f0dot5), - List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))), - new Case("-0.5", """ - r1 =0x%x; - r4l =0x%x; - r7 =0x%x; - r10l=0x%x; - """.formatted(dn0dot5, dn0dot5, dn0dot5, fn0dot5), - List.of(ev("r0", "0"), ev("r3", "0"), ev("r6", "0"), ev("r9", "0"))))); - } - - @Test - public void testFloatNaNOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long dNaN = Double.doubleToRawLongBits(Double.NaN); - int fNaN = Float.floatToRawIntBits(Float.NaN); - /** - * The size swap is not necessary, but test anyway. - */ - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = nan(r1l); - r6l = nan(r7); - """), List.of(new Case("num", """ - r1l =0x%x; - r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of(ev("r0", "0"), ev("r6", "0"))), - new Case("nan", """ - r1l =0x%x; - r7 =0x%x; - """.formatted(fNaN, dNaN), List.of(ev("r0", "1"), ev("r6", "1"))))); - } - - @Test - public void testFloatNegOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = f-r1; - r6l = f-r7l; - """), List.of(new Case("num", """ - r1 =0x%x; - r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of(ev("r0", -0.5d), ev("r6l", -0.5f))))); - } - - @Test - public void testFloatAddOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f+ r2; - r9l = r10l f+ r11l; - """), List.of(new Case("only", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", 0.75d), ev("r9", 0.75f))))); - } - - @Test - public void testFloatSubOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f- r2; - r9l = r10l f- r11l; - """), List.of(new Case("only", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", 0.25d), ev("r9", 0.25f))))); - } - - @Test - public void testFloatMultOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f* r2; - r9l = r10l f* r11l; - """), List.of(new Case("only", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", 0.125d), ev("r9", 0.125f))))); - } - - @Test - public void testFloatDivOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f/ r2; - r9l = r10l f/ r11l; - """), List.of(new Case("only", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", 2.0d), ev("r9", 2.0f))))); - } - - @Test - public void testFloatEqualOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f== r2; - r9l = r10l f== r11l; - """), - List.of( - new Case("lt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of(ev("r0", "0"), ev("r9", "0"))), - new Case("eq", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of(ev("r0", "1"), ev("r9", "1"))), - new Case("gt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testFloatNotEqualOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f!= r2; - r9l = r10l f!= r11l; - """), - List.of( - new Case("lt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of(ev("r0", "1"), ev("r9", "1"))), - new Case("eq", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of(ev("r0", "0"), ev("r9", "0"))), - new Case("gt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testFloatLessEqualOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f<= r2; - r9l = r10l f<= r11l; - """), - List.of( - new Case("lt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of(ev("r0", "1"), ev("r9", "1"))), - new Case("eq", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of(ev("r0", "1"), ev("r9", "1"))), - new Case("gt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testFloatLessOpGen() throws Exception { - long d0dot5 = Double.doubleToLongBits(0.5); - int f0dot5 = Float.floatToIntBits(0.5f); - long d0dot25 = Double.doubleToLongBits(0.25); - int f0dot25 = Float.floatToIntBits(0.25f); - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 f< r2; - r9l = r10l f< r11l; - """), - List.of( - new Case("lt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of(ev("r0", "1"), ev("r9", "1"))), - new Case("eq", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot5, f0dot5, f0dot5), - List.of(ev("r0", "0"), ev("r9", "0"))), - new Case("gt", """ - r1 =0x%x; r2 =0x%x; - r10l=0x%x; r11l=0x%x; - """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testInt2CompOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = -r1; - r6l = -r7l; - """), List.of(new Case("pos", """ - r1 =4; - r7l =4; - """, List.of(ev("r0", "-4"), ev("r6l", "-4"))), new Case("neg", """ - r1 =-4; - r7l =-4; - """, List.of(ev("r0", "4"), ev("r6l", "4"))))); - } - - @Test - public void testInt2CompMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp0:9 = -temp1; - r0 = temp0(0); - r2 = temp0(1); - """), List.of(new Case("pos", """ - r1 = 4; - """, List.of(ev("r0", "-4"), ev("r2", "-1"))), new Case("neg", """ - r1 =-4; - """, List.of(ev("r0", "4"), ev("r2", "0"))))); - } - - @Test - public void testIntNegateOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = ~r1; - r6l = ~r7l; - """), List.of(new Case("pos", """ - r1 =4; - r7l =4; - """, List.of(ev("r0", "-5"), ev("r6l", "-5"))), new Case("neg", """ - r1 =-4; - r7l =-4; - """, List.of(ev("r0", "3"), ev("r6l", "3"))))); - } - - @Test - public void testIntNegateMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp0:9 = ~temp1; - r0 = temp0(0); - r2 = temp0(1); - """), List.of(new Case("pos", """ - r1 = 4; - """, List.of(ev("r0", "-5"), ev("r2", "-1"))), new Case("neg", """ - r1 = -4; - """, List.of(ev("r0", "3"), ev("r2", "0"))))); - } - - @Test - public void testIntSExtOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = sext(r1l); - """), List.of(new Case("pos", """ - r1l =4; - """, List.of(ev("r0", "4"))), new Case("neg", """ - r1l =-4; - """, List.of(ev("r0", "-4"))))); - } - - @Test - public void testIntSExtMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:9 = sext(r1l); - r0 = temp0(0); - r2 = temp0(1); - """), List.of(new Case("pos", """ - r1l =4; - """, List.of(ev("r0", "4"), ev("r2", "0"))), new Case("neg", """ - r1l =-4; - """, List.of(ev("r0", "-4"), ev("r2", "-1"))))); - } - - @Test - public void testIntZExtOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = zext(r1l); - """), List.of(new Case("pos", """ - r1l =4; - """, List.of(ev("r0", "4"))), new Case("neg", """ - r1l =-4; - """, List.of(ev("r0", "0xfffffffc"))))); - } - - @Test - public void testIntZExtMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:9 = zext(r1l); - r0 = temp0(0); - r2 = temp0(1); - """), List.of(new Case("pos", """ - r1l =4; - """, List.of(ev("r0", "4"), ev("r2", "0"))), new Case("neg", """ - r1l =-4; - """, List.of(ev("r0", "0xfffffffc"), ev("r2", "0xffffff"))))); - } - - @Test - public void testLzCountOpGen() throws Exception { - // Test size change, even though not necessary here - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = lzcount(r1l); - - temp:3 = r3(0); - r2 = lzcount(temp); - """), List.of(new Case("pos", """ - r1l =4; - r3 =4; - """, List.of(ev("r0", "29"), ev("r2", "21"))), new Case("neg", """ - r1l =-4; - r3 =-4; - """, List.of(ev("r0", "0"), ev("r2", "0"))))); - } - - @Test - public void testLzCountMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1s:9 = sext(r1); - temp1z:9 = zext(r1); - r0 = lzcount(temp1s); - r2 = lzcount(temp1z); - """), List.of(new Case("pos", """ - r1 =4; - """, List.of(ev("r0", "69"), ev("r2", "69"))), new Case("neg", """ - r1 =-4; - """, List.of(ev("r0", "0"), ev("r2", "8"))))); - } - - @Test - public void testPopCountOpGen() throws Exception { - // Test size change, even though not necessary here - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = popcount(r1l); - """), List.of(new Case("pos", """ - r1l =4; - """, List.of(ev("r0", "1"))), new Case("neg", """ - r1l =-4; - """, List.of(ev("r0", "30"))))); - } - - @Test - public void testPopCountMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1s:9 = sext(r1); - temp1z:9 = zext(r1); - r0 = popcount(temp1s); - r2 = popcount(temp1z); - """), List.of(new Case("pos", """ - r1 =4; - """, List.of(ev("r0", "1"), ev("r2", "1"))), new Case("neg", """ - r1 =-4; - """, List.of(ev("r0", "70"), ev("r2", "62"))))); - } - - @Test - public void testSubPieceOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0l = r1(3); - r3 = r4l(3); - """), List.of(new Case("only", """ - r1 =0x%x; - r4l=0x12345678; - """.formatted(LONG_CONST), List.of(ev("r0l", "0xadbeefca"), ev("r3", "0x12"))))); - } - - @Test - public void testSubPieceMpIntConst9_0() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:9 = 0x1122334455667788; - r0 = temp0(0); - """), List.of(new Case("only", "", List.of(ev("r0", "0x1122334455667788"))))); - } - - @Test - public void testSubPieceMpIntConst9_1() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:9 = 0x1122334455667788; - r0 = temp0(1); - """), List.of(new Case("only", "", List.of(ev("r0", "0x11223344556677"))))); - } - - @Test - public void testSubPieceMpIntConst10_0() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:10 = 0x1122334455667788; - r0 = temp0(0); - """), List.of(new Case("only", "", List.of(ev("r0", "0x1122334455667788"))))); - } - - @Test - public void testSubPieceMpIntConst10_1() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:10 = 0x1122334455667788; - r0 = temp0(1); - """), List.of(new Case("only", "", List.of(ev("r0", "0x11223344556677"))))); - } - - @Test - public void testSubPieceMpIntConst10_2() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:10 = 0x1122334455667788; - r0 = temp0(2); - """), List.of(new Case("only", "", List.of(ev("r0", "0x112233445566"))))); - } - - @Test - public void testSubPieceMpIntConst11_0() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:11 = 0x1122334455667788; - r0 = temp0(0); - """), List.of(new Case("only", "", List.of(ev("r0", "0x1122334455667788"))))); - } - - @Test - public void testSubPieceMpIntConst11_1() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:11 = 0x1122334455667788; - r0 = temp0(1); - """), List.of(new Case("only", "", List.of(ev("r0", "0x11223344556677"))))); - } - - @Test - public void testSubPieceMpIntConst11_2() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:11 = 0x1122334455667788; - r0 = temp0(2); - """), List.of(new Case("only", "", List.of(ev("r0", "0x112233445566"))))); - } - - @Test - public void testSubPieceMpIntConst11_3() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:11 = 0x1122334455667788; - r0 = temp0(3); - """), List.of(new Case("only", "", List.of(ev("r0", "0x1122334455"))))); - } - - @Test - public void testSubPieceMpIntConst12_0() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:12 = 0x1122334455667788; - r0 = temp0(0); - """), List.of(new Case("only", "", List.of(ev("r0", "0x1122334455667788"))))); - } - - @Test - public void testSubPieceMpIntConst12_1() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:12 = 0x1122334455667788; - r0 = temp0(1); - """), List.of(new Case("only", "", List.of(ev("r0", "0x11223344556677"))))); - } - - @Test - public void testSubPieceMpIntConst12_2() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:12 = 0x1122334455667788; - r0 = temp0(2); - """), List.of(new Case("only", "", List.of(ev("r0", "0x112233445566"))))); - } - - @Test - public void testSubPieceMpIntConst12_3() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:12 = 0x1122334455667788; - r0 = temp0(3); - """), List.of(new Case("only", "", List.of(ev("r0", "0x1122334455"))))); - } - - @Test - public void testSubPieceMpIntConst12_4() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:12 = 0x1122334455667788; - r0 = temp0(4); - """), List.of(new Case("only", "", List.of(ev("r0", "0x11223344"))))); - } - - @Test - public void testIntAddOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 + r2; - r9l = r10l + r11l; - """), List.of(new Case("only", """ - r1 =2; r2 =2; - r10l=2; r11l=2; - """, List.of(ev("r0", "4"), ev("r9", "4"))))); - } - - @Test - public void testIntAddMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 + temp2; - r0 = temp0(0); - """), List.of(new Case("small", """ - r1 = 2; r2 = 2; - """, List.of(ev("r0", "4"))), new Case("large", """ - r1 = 0x8111111122222222; r2 = 0x8765432112345678; - """, List.of(ev("r0", "0x87654323456789a"))))); - } - - @Test - public void testIntSubOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 - r2; - r9l = r10l - r11l; - """), List.of(new Case("only", """ - r1 =2; r2 =2; - r10l=2; r11l=2; - """, List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testIntSubMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 - temp2; - r0 = temp0(0); - r3 = temp0(1); - """), List.of(new Case("small", """ - r1 = 2; r2 = 2; - """, List.of(ev("r0", "0"), ev("r3", "0"))), new Case("large", """ - r1 = 0x8111111122222222; r2 = 0x8765432112345678; - """, List.of(ev("r0", "0xf9abcdf00fedcbaa"), ev("r3", "0xfff9abcdf00fedcb"))))); - } - - @Test - public void testIntMultOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 * r2; - r9l = r10l * r11l; - """), List.of(new Case("only", """ - r1 =2; r2 =2; - r10l=2; r11l=2; - """, List.of(ev("r0", "4"), ev("r9", "4"))))); - } - - @Test - public void testIntMultMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp0:16 = zext(r1) * zext(r2); - r0 = temp0[0,64]; - r3 = temp0[64,64]; - """), List.of(new Case("small", """ - r1 = 2; r2 = 7; - """, List.of(ev("r0", "14"), ev("r3", "0"))), new Case("large", """ - r1 = 0xffeeddccbbaa9988; r2 = 0x8877665544332211; - """, List.of(ev("r0", "0x30fdc971d4d04208"), ev("r3", "0x886e442c48bba72d"))))); - } - - @Test - public void testIntDivOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 / r2; - r9l = r10l / r11l; - """), List.of(new Case("pp", """ - r1 =5; r2 =2; - r10l=5; r11l=2; - """, List.of(ev("r0", "2"), ev("r9", "2"))), new Case("pn", """ - r1 =5; r2 =-2; - r10l=5; r11l=-2; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("np", """ - r1 =-5; r2 =2; - r10l=-5; r11l=2; - """, List.of(ev("r0", "0x7ffffffffffffffd"), ev("r9", "0x7ffffffd"))), - new Case("nn", """ - r1 =-5; r2 =-2; - r10l=-5; r11l=-2; - """, List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testIntDivOpGenWith3ByteOperand() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp:3 = r1 + r2; - r0 = temp / r0; - """), List.of(new Case("only", """ - r1 = 0xdead; - r2 = 0xbeef; - r0 = 4; - """, List.of(ev("r0", "0x6767"))))); - } - - @Test - public void testIntDivMpIntOpGenNonUniform() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - r0l = temp1 / r2; - """), List.of(new Case("pp", """ - r1 = 0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0l", "0x2ee95b10"))), new Case("pn", """ - r1 = 0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0l", "0x00000000"))), new Case("np", """ - r1 = -0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0l", "0x0e658826"))), new Case("nn", """ - r1 = -0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0l", "0x000000ff"))))); - } - - @Test - public void testIntDivMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - local quotient = temp1 / temp2; - r0l = quotient(0); - """), List.of(new Case("pp", """ - r1 = 0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0l", "0x2ee95b10"))), new Case("pn", """ - r1 = 0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0l", "0x00000000"))), new Case("np", """ - r1 = -0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0l", "0x0e658826"))), - // NOTE: Result differs from NonUniform, because r2 is also sext()ed - new Case("nn", """ - r1 = -0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0l", "0x00000000"))))); - } - - @Test - public void testIntSDivOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 s/ r2; - r9l = r10l s/ r11l; - """), List.of(new Case("pp", """ - r1 =5; r2 =2; - r10l=5; r11l=2; - """, List.of(ev("r0", "2"), ev("r9l", "2"))), new Case("pn", """ - r1 =5; r2 =-2; - r10l=5; r11l=-2; - """, List.of(ev("r0", "-2"), ev("r9l", "-2"))), new Case("np", """ - r1 =-5; r2 =2; - r10l=-5; r11l=2; - """, List.of(ev("r0", "-2"), ev("r9l", "-2"))), new Case("nn", """ - r1 =-5; r2 =-2; - r10l=-5; r11l=-2; - """, List.of(ev("r0", "2"), ev("r9l", "2"))))); - } - - @Test - public void testIntSDivMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - local quotient = temp1 s/ temp2; - r0l = quotient(0); - """), List.of(new Case("pp", """ - r1 = 0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0", "0x2ee95b10"))), new Case("pn", """ - r1 = 0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0", "0xd116a4f0"))), new Case("np", """ - r1 = -0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0", "0xd116a4f0"))), new Case("nn", """ - r1 = -0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0", "0x2ee95b10"))))); - } - - @Test - public void testIntRemOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 % r2; - r9l = r10l % r11l; - """), List.of(new Case("pp", """ - r1 =5; r2 =2; - r10l=5; r11l=2; - """, List.of(ev("r0", "1"), ev("r9l", "1"))), new Case("pn", """ - r1 =5; r2 =-2; - r10l=5; r11l=-2; - """, List.of(ev("r0", "5"), ev("r9l", "5"))), new Case("np", """ - r1 =-5; r2 =2; - r10l=-5; r11l=2; - """, List.of(ev("r0", "1"), ev("r9l", "1"))), new Case("nn", """ - r1 =-5; r2 =-2; - r10l=-5; r11l=-2; - """, List.of(ev("r0", "-5"), ev("r9l", "-5"))))); - } - - @Test - public void testIntRemMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - local remainder = temp1 % temp2; - r0l = remainder(0); - """), List.of(new Case("pp", """ - r1 = 0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0", "0x0c49"))), new Case("pn", """ - r1 = 0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0", "0xefcdab89"))), new Case("np", """ - r1 = -0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0", "0x00bf"))), new Case("nn", """ - r1 = -0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0", "0x10325477"))))); - } - - @Test - public void testIntSRemOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 s% r2; - r9l = r10l s% r11l; - """), List.of(new Case("pp", """ - r1 =5; r2 =2; - r10l=5; r11l=2; - """, List.of(ev("r0", "1"), ev("r9l", "1"))), new Case("pn", """ - r1 =5; r2 =-2; - r10l=5; r11l=-2; - """, List.of(ev("r0", "1"), ev("r9l", "1"))), new Case("np", """ - r1 =-5; r2 =2; - r10l=-5; r11l=2; - """, List.of(ev("r0", "-1"), ev("r9l", "-1"))), new Case("nn", """ - r1 =-5; r2 =-2; - r10l=-5; r11l=-2; - """, List.of(ev("r0", "-1"), ev("r9l", "-1"))))); - } - - @Test - public void testIntSRemMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - local quotient = temp1 s% temp2; - r0l = quotient(0); - """), List.of(new Case("pp", """ - r1 = 0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0", "0x0c49"))), new Case("pn", """ - r1 = 0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0", "0x0c49"))), new Case("np", """ - r1 = -0x67452301efcdab89; - r2 = 0x1234; - """, List.of(ev("r0", "0xfffff3b7"))), new Case("nn", """ - r1 = -0x67452301efcdab89; - r2 = -0x1234; - """, List.of(ev("r0", "0xfffff3b7"))))); - } - - @Test - public void testIntAndOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 & r2; - r9l = r10l & r11l; - """), List.of(new Case("only", """ - r1 =0x3; r2 =0x5; - r10l=0x3; r11l=0x5; - """, List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testIntAndMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 & temp2; - r0 = temp0(0); - """), List.of(new Case("small", """ - r1 = 2; r2 = 2; - """, List.of(ev("r0", "2"))), new Case("large", """ - r1 = 0x8111111122222222; r2 = 0x8765432112345678; - """, List.of(ev("r0", "0x8101010102200220"))))); - } - - @Test - public void testIntOrOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 | r2; - r9l = r10l | r11l; - """), List.of(new Case("only", """ - r1 =0x3; r2 =0x5; - r10l=0x3; r11l=0x5; - """, List.of(ev("r0", "7"), ev("r9", "7"))))); - } - - @Test - public void testIntOrMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 | temp2; - r0 = temp0(0); - """), List.of(new Case("small", """ - r1 = 2; r2 = 2; - """, List.of(ev("r0", "2"))), new Case("large", """ - r1 = 0x8111111122222222; r2 = 0x8765432112345678; - """, List.of(ev("r0", "0x877553313236767a"))))); - } - - @Test - public void testIntXorOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 ^ r2; - r9l = r10l ^ r11l; - """), List.of(new Case("only", """ - r1 =0x3; r2 =0x5; - r10l=0x3; r11l=0x5; - """, List.of(ev("r0", "6"), ev("r9", "6"))))); - } - - @Test - public void testIntXorMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1); - temp2:9 = zext(r2); - temp0:9 = temp1 ^ temp2; - r0 = temp0(0); - """), List.of(new Case("small", """ - r1 = 2; r2 = 2; - """, List.of(ev("r0", "0"))), new Case("large", """ - r1 = 0x8111111122222222; r2 = 0x8765432112345678; - """, List.of(ev("r0", "0x67452303016745a"))))); - } - - @Test - public void testIntEqualOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 == r2; - r9l = r10l == r11l; - """), List.of(new Case("lt", """ - r1 =1; r2 =2; - r10l=1; r11l=2; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("slt", """ - r1 =-1; r2 =2; - r10l=-1; r11l=2; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("eq", """ - r1 =1; r2 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("gt", """ - r1 =2; r2 =1; - r10l=2; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("sgt", """ - r1 =2; r2 =-1; - r10l=2; r11l=-1; - """, List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testIntEqualMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - r0 = temp1 == temp2; - """), List.of(new Case("lt", """ - r1 = 1; r2 = 2; - """, List.of(ev("r0", "0"))), new Case("slt", """ - r1 = -1; r2 = 0x7fffffffffffffff; - """, List.of(ev("r0", "0"))), new Case("eq", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "1"))), new Case("gt", """ - r1 = 2; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("sgt", """ - r1 = 2; r2 = -1; - """, List.of(ev("r0", "0"))))); - } - - @Test - public void testIntNotEqualOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 != r2; - r9l = r10l != r11l; - """), List.of(new Case("lt", """ - r1 =1; r2 =2; - r10l=1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("slt", """ - r1 =-1; r2 =2; - r10l=-1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("eq", """ - r1 =1; r2 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("gt", """ - r1 =2; r2 =1; - r10l=2; r11l=1; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("sgt", """ - r1 =2; r2 =-1; - r10l=2; r11l=-1; - """, List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testIntNotEqualMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - r0 = temp1 != temp2; - """), List.of(new Case("lt", """ - r1 = 1; r2 = 2; - """, List.of(ev("r0", "1"))), new Case("slt", """ - r1 = -1; r2 = 0x7fffffffffffffff; - """, List.of(ev("r0", "1"))), new Case("eq", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("gt", """ - r1 = 2; r2 = 1; - """, List.of(ev("r0", "1"))), new Case("sgt", """ - r1 = 2; r2 = -1; - """, List.of(ev("r0", "1"))))); - } - - @Test - public void testIntLessEqualOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 <= r2; - r9l = r10l <= r11l; - """), List.of(new Case("lt", """ - r1 =1; r2 =2; - r10l=1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("slt", """ - r1 =-1; r2 =2; - r10l=-1; r11l=2; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("eq", """ - r1 =1; r2 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("gt", """ - r1 =2; r2 =1; - r10l=2; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("sgt", """ - r1 =2; r2 =-1; - r10l=2; r11l=-1; - """, List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testIntLessEqualMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - r0 = temp1 <= temp2; - """), List.of(new Case("lt", """ - r1 = 1; r2 = 2; - """, List.of(ev("r0", "1"))), new Case("slt", """ - r1 = -1; r2 = 0x7fffffffffffffff; - """, List.of(ev("r0", "0"))), new Case("eq", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "1"))), new Case("gt", """ - r1 = 2; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("sgt", """ - r1 = 2; r2 = -1; - """, List.of(ev("r0", "1"))))); - } - - @Test - public void testIntSLessEqualOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 s<= r2; - r9l = r10l s<= r11l; - """), List.of(new Case("lt", """ - r1 =1; r2 =2; - r10l=1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("slt", """ - r1 =-1; r2 =2; - r10l=-1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("eq", """ - r1 =1; r2 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("gt", """ - r1 =2; r2 =1; - r10l=2; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("sgt", """ - r1 =2; r2 =-1; - r10l=2; r11l=-1; - """, List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testIntSLessEqualMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - r0 = temp1 s<= temp2; - """), List.of(new Case("lt", """ - r1 = 1; r2 = 2; - """, List.of(ev("r0", "1"))), new Case("slt", """ - r1 = -1; r2 = 0x7fffffffffffffff; - """, List.of(ev("r0", "1"))), new Case("eq", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "1"))), new Case("gt", """ - r1 = 2; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("sgt", """ - r1 = 2; r2 = -1; - """, List.of(ev("r0", "0"))))); - } - - @Test - public void testIntLessOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 < r2; - r9l = r10l < r11l; - """), List.of(new Case("lt", """ - r1 =1; r2 =2; - r10l=1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("slt", """ - r1 =-1; r2 =2; - r10l=-1; r11l=2; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("eq", """ - r1 =1; r2 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("gt", """ - r1 =2; r2 =1; - r10l=2; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("sgt", """ - r1 =2; r2 =-1; - r10l=2; r11l=-1; - """, List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testIntLessMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - r0 = temp1 < temp2; - """), List.of(new Case("lt", """ - r1 = 1; r2 = 2; - """, List.of(ev("r0", "1"))), new Case("slt", """ - r1 = -1; r2 = 0x7fffffffffffffff; - """, List.of(ev("r0", "0"))), new Case("eq", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("gt", """ - r1 = 2; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("sgt", """ - r1 = 2; r2 = -1; - """, List.of(ev("r0", "1"))))); - } - - @Test - public void testIntSLessOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 s< r2; - r9l = r10l s< r11l; - """), List.of(new Case("lt", """ - r1 =1; r2 =2; - r10l=1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("slt", """ - r1 =-1; r2 =2; - r10l=-1; r11l=2; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("eq", """ - r1 =1; r2 =1; - r10l=1; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("gt", """ - r1 =2; r2 =1; - r10l=2; r11l=1; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("sgt", """ - r1 =2; r2 =-1; - r10l=2; r11l=-1; - """, List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testIntSLessMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = sext(r2); - r0 = temp1 s< temp2; - """), List.of(new Case("lt", """ - r1 = 1; r2 = 2; - """, List.of(ev("r0", "1"))), new Case("slt", """ - r1 = -1; r2 = 0x7fffffffffffffff; - """, List.of(ev("r0", "1"))), new Case("eq", """ - r1 = 1; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("gt", """ - r1 = 2; r2 = 1; - """, List.of(ev("r0", "0"))), new Case("sgt", """ - r1 = 2; r2 = -1; - """, List.of(ev("r0", "0"))))); - } - - @Test - public void testIntCarryOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = carry(r1, r2); - r9l = carry(r10l, r11l); - """), List.of(new Case("f", """ - r1 =0x8000000000000000; r2 =0x4000000000000000; - r10l=0x80000000; r11l=0x40000000; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("t", """ - r1 =0x8000000000000000; r2 =0x8000000000000000; - r10l=0x80000000; r11l=0x80000000; - """, List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testIntCarryMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1) << 8; - temp2:9 = zext(r2) << 8; - r0 = carry(temp1, temp2); - """), List.of(new Case("f", """ - r1 =0x8000000000000000; r2 =0x4000000000000000; - r10l=0x80000000; r11l=0x40000000; - """, List.of(ev("r0", "0"))), new Case("t", """ - r1 =0x8000000000000000; r2 =0x8000000000000000; - r10l=0x80000000; r11l=0x80000000; - """, List.of(ev("r0", "1"))))); - } - - @Test - public void testIntSCarryOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = scarry(r1, r2); - r9l = scarry(r10l, r11l); - """), List.of(new Case("f", """ - r1 =0x8000000000000000; r2 =0x4000000000000000; - r10l=0x80000000; r11l=0x40000000; - """, List.of(ev("r0", "0"), ev("r9", "0"))), new Case("t", """ - r1 =0x4000000000000000; r2 =0x4000000000000000; - r10l=0x40000000; r11l=0x40000000; - """, List.of(ev("r0", "1"), ev("r9", "1"))))); - } - - @Test - public void testIntSCarryMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1) << 8; - temp2:9 = zext(r2) << 8; - r0 = scarry(temp1, temp2); - """), List.of(new Case("f", """ - r1 =0x8000000000000000; r2 =0x4000000000000000; - r10l=0x80000000; r11l=0x40000000; - """, List.of(ev("r0", "0"))), new Case("t", """ - r1 =0x4000000000000000; r2 =0x4000000000000000; - r10l=0x40000000; r11l=0x40000000; - """, List.of(ev("r0", "1"))))); - } - - @Test - public void testIntSBorrowOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = sborrow(r1, r2); - r9l = sborrow(r10l, r11l); - """), List.of(new Case("t", """ - r1 =0x8000000000000000; r2 =0x4000000000000000; - r10l=0x80000000; r11l=0x40000000; - """, List.of(ev("r0", "1"), ev("r9", "1"))), new Case("f", """ - r1 =0xc000000000000000; r2 =0x4000000000000000; - r10l=0xc0000000; r11l=0x40000000; - """, List.of(ev("r0", "0"), ev("r9", "0"))))); - } - - @Test - public void testIntSBorrowMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = zext(r1) << 8; - temp2:9 = zext(r2) << 8; - r0 = sborrow(temp1, temp2); - """), List.of(new Case("t", """ - r1 =0x8000000000000000; r2 =0x4000000000000000; - r10l=0x80000000; r11l=0x40000000; - """, List.of(ev("r0", "1"))), new Case("f", """ - r1 =0xc000000000000000; r2 =0x4000000000000000; - r10l=0xc0000000; r11l=0x40000000; - """, List.of(ev("r0", "0"))))); - } - - @Test - public void testIntLeftOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 << r2; - r3 = r4 << r5l; - r6l = r7l << r8; - r9l = r10l << r11l; - """), List.of( - new Case("posLposR", """ - r1 =100; r2 =4; - r4 =100; r5l =4; - r7l =100; r8 =4; - r10l=100; r11l=4; - """, - List.of(ev("r0", "0x640"), ev("r3", "0x640"), ev("r6l", "0x640"), - ev("r9l", "0x640"))), - new Case("posLbigR", """ - r1 =100; r2 =0x100000004; - r4 =100; r5l =0x100000004; - r7l =100; r8 =0x100000004; - r10l=100; r11l=0x100000004; - """, - List.of(ev("r0", "0"), ev("r3", "0x640"), ev("r6l", "0"), ev("r9l", "0x640"))), - new Case("posLnegR", """ - r1 =100; r2 =-4; - r4 =100; r5l =-4; - r7l =100; r8 =-4; - r10l=100; r11l=-4; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6l", "0"), ev("r9l", "0"))), - new Case("negLposR", """ - r1 =-100; r2 =4; - r4 =-100; r5l =4; - r7l =-100; r8 =4; - r10l=-100; r11l=4; - """, - List.of(ev("r0", "-0x640"), ev("r3", "-0x640"), ev("r6l", "-0x640"), - ev("r9l", "-0x640"))), - new Case("negLnegR", """ - r1 =-100; r2 =-4; - r4 =-100; r5l =-4; - r7l =-100; r8 =-4; - r10l=-100; r11l=-4; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6l", "0"), ev("r9l", "0"))))); - } - - @Test - public void testIntLeftMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = (zext(r2) << 64) + r3; - temp0:9 = temp1 << temp2; - r0 = temp0(0); - r4 = temp0(1); - """), List.of(new Case("posLposR", """ - r1 = 0x7edcba9876543210; - r2 = 0; - r3 = 4; - """, List.of(ev("r0", "0xedcba98765432100"), ev("r4", "0x07edcba987654321"))), - new Case("posLmedR", """ - r1 = 0x7edcba9876543210; - r2 = 0; - r3 = 36; - """, List.of(ev("r0", "0x6543210000000000"), ev("r4", "0x8765432100000000"))), - new Case("posLbigR", """ - r1 = 0x7edcba9876543210; - r2 = 0x40; - r3 = 4; - """, List.of(ev("r0", "0"), ev("r4", "0"))), new Case("posLnegR", """ - r1 = 0x7edcba9876543210; - r2 = -1; - r3 = -4; - """, List.of(ev("r0", "0"), ev("r4", "0"))), new Case("negLposR", """ - r1 = 0xfedcba9876543210; - r2 = 0; - r3 = 4; - """, List.of(ev("r0", "0xedcba98765432100"), ev("r4", "0xffedcba987654321"))), - new Case("negLnegR", """ - r1 = 0xfedcba9876543210; - r2 = -1; - r3 = -4; - """, List.of(ev("r0", "0"), ev("r4", "0"))))); - } - - @Test - public void testIntRightOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 >> r2; - r3 = r4 >> r5l; - r6l = r7l >> r8; - r9l = r10l >> r11l; - """), List.of(new Case("posLposR", """ - r1 =100; r2 =4; - r4 =100; r5l =4; - r7l =100; r8 =4; - r10l=100; r11l=4; - """, List.of(ev("r0", "6"), ev("r3", "6"), ev("r6l", "6"), ev("r9l", "6"))), - new Case("posLbigR", """ - r1 =100; r2 =0x100000004; - r4 =100; r5l =0x100000004; - r7l =100; r8 =0x100000004; - r10l=100; r11l=0x100000004; - """, List.of(ev("r0", "0"), ev("r3", "6"), ev("r6l", "0"), ev("r9l", "6"))), - new Case("posLnegR", """ - r1 =100; r2 =-4; - r4 =100; r5l =-4; - r7l =100; r8 =-4; - r10l=100; r11l=-4; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6l", "0"), ev("r9l", "0"))), - new Case("negLposR", """ - r1 =-100; r2 =4; - r4 =-100; r5l =4; - r7l =-100; r8 =4; - r10l=-100; r11l=4; - """, - List.of(ev("r0", "0x0ffffffffffffff9"), ev("r3", "0x0ffffffffffffff9"), - ev("r6l", "0x0ffffff9"), ev("r9l", "0x0ffffff9"))), - new Case("negLnegR", """ - r1 =-100; r2 =-4; - r4 =-100; r5l =-4; - r7l =-100; r8 =-4; - r10l=-100; r11l=-4; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6l", "0"), ev("r9l", "0"))))); - } - - @Test - public void testIntRightMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = (zext(r2) << 64) + r3; - temp0:9 = temp1 >> temp2; - r0 = temp0(0); - r4 = temp0(1); - """), List.of(new Case("posLposR", """ - r1 = 0x7edcba9876543210; - r2 = 0; - r3 = 4; - """, List.of(ev("r0", "0x07edcba987654321"), ev("r4", "0x0007edcba9876543"))), - new Case("posLmedR", """ - r1 = 0x7edcba9876543210; - r2 = 0; - r3 = 36; - """, List.of(ev("r0", "0x0000000007edcba9"), ev("r4", "0x000000000007edcb"))), - new Case("posLbigR", """ - r1 = 0x7edcba9876543210; - r2 = 0x40; - r3 = 4; - """, List.of(ev("r0", "0"), ev("r4", "0"))), new Case("posLnegR", """ - r1 = 0x7edcba9876543210; - r2 = -1; - r3 = -4; - """, List.of(ev("r0", "0"), ev("r4", "0"))), new Case("negLposR", """ - r1 = 0xfedcba9876543210; - r2 = 0; - r3 = 4; - """, List.of(ev("r0", "0xffedcba987654321"), ev("r4", "0x0fffedcba9876543"))), - new Case("negLmedR", """ - r1 = 0xfedcba9876543210; - r2 = 0; - r3 = 36; - """, List.of(ev("r0", "0x0000000fffedcba9"), ev("r4", "0x000000000fffedcb"))), - new Case("negLnegR", """ - r1 = 0xfedcba9876543210; - r2 = -1; - r3 = -4; - """, List.of(ev("r0", "0"), ev("r4", "0"))))); - } - - @Test - public void testIntSRightOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = r1 s>> r2; - r3 = r4 s>> r5l; - r6l = r7l s>> r8; - r9l = r10l s>> r11l; - """), - List.of(new Case("posLposR", """ - r1 =100; r2 =4; - r4 =100; r5l =4; - r7l =100; r8 =4; - r10l=100; r11l=4; - """, List.of(ev("r0", "6"), ev("r3", "6"), ev("r6l", "6"), ev("r9l", "6"))), - new Case("posLbigR", """ - r1 =100; r2 =0x100000004; - r4 =100; r5l =0x100000004; - r7l =100; r8 =0x100000004; - r10l=100; r11l=0x100000004; - """, List.of(ev("r0", "0"), ev("r3", "6"), ev("r6l", "0"), ev("r9l", "6"))), - new Case("posLnegR", """ - r1 =100; r2 =-4; - r4 =100; r5l =-4; - r7l =100; r8 =-4; - r10l=100; r11l=-4; - """, List.of(ev("r0", "0"), ev("r3", "0"), ev("r6l", "0"), ev("r9l", "0"))), - new Case("negLposR", """ - r1 =-100; r2 =4; - r4 =-100; r5l =4; - r7l =-100; r8 =4; - r10l=-100; r11l=4; - """, - List.of(ev("r0", "-7"), ev("r3", "-7"), ev("r6l", "-7"), ev("r9l", "-7"))), - new Case("negLnegR", """ - r1 =-100; r2 =-4; - r4 =-100; r5l =-4; - r7l =-100; r8 =-4; - r10l=-100; r11l=-4; - """, - List.of(ev("r0", "-1"), ev("r3", "-1"), ev("r6l", "-1"), ev("r9l", "-1"))))); - } - - @Test - public void testIntSRight3IntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:3 = r1(0); - temp0:3 = temp1 s>> r2; - r0 = zext(temp0); - """), List.of(new Case("posLposR", """ - r1 = 0xfedcba; - r2 = 4; - """, List.of(ev("r0", "0xffedcb"))))); - } - - @Test - public void testIntSRightMpIntOpGen() throws Exception { - runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - temp1:9 = sext(r1); - temp2:9 = (zext(r2) << 64) + r3; - temp0:9 = temp1 s>> temp2; - r0 = temp0(0); - r4 = temp0(1); - """), List.of(new Case("posLposR", """ - r1 = 0x7edcba9876543210; - r2 = 0; - r3 = 4; - """, List.of(ev("r0", "0x07edcba987654321"), ev("r4", "0x0007edcba9876543"))), - new Case("posLmedR", """ - r1 = 0x7edcba9876543210; - r2 = 0; - r3 = 36; - """, List.of(ev("r0", "0x0000000007edcba9"), ev("r4", "0x000000000007edcb"))), - new Case("posLbigR", """ - r1 = 0x7edcba9876543210; - r2 = 0x40; - r3 = 4; - """, List.of(ev("r0", "0"), ev("r4", "0"))), new Case("posLnegR", """ - r1 = 0x7edcba9876543210; - r2 = -1; - r3 = -4; - """, List.of(ev("r0", "0"), ev("r4", "0"))), new Case("negLposR", """ - r1 = 0xfedcba9876543210; - r2 = 0; - r3 = 4; - """, List.of(ev("r0", "0xffedcba987654321"), ev("r4", "0xffffedcba9876543"))), - new Case("negLlegR", """ - r1 = 0xfedcba9876543210; - r2 = 0; - r3 = 32; - """, List.of(ev("r0", "0xfffffffffedcba98"), ev("r4", "0xfffffffffffedcba"))), - new Case("negLmedR", """ - r1 = 0xfedcba9876543210; - r2 = 0; - r3 = 36; - """, List.of(ev("r0", "0xffffffffffedcba9"), ev("r4", "0xffffffffffffedcb"))), - new Case("negLnegR", """ - r1 = 0xfedcba9876543210; - r2 = -1; - r3 = -4; - """, List.of(ev("r0", "-1"), ev("r4", "-1"))))); - } - - @Test - public void testFloatAsOffset() throws Exception { - int fDot5 = Float.floatToRawIntBits(0.5f); - int f1Dot0 = Float.floatToRawIntBits(1.0f); - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:4 = 0x%x f+ 0x%x; - temp2:8 = *temp; - """.formatted(fDot5, fDot5)); - Varnode temp2 = tr.program.getCode().get(1).getOutput(); - assertTrue(temp2.isUnique()); - tr.setLongMemVal(f1Dot0, LONG_CONST, 8); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp2)); - } - - @Test - public void testDoubleAsOffset() throws Exception { - long dDot5 = Double.doubleToRawLongBits(0.5); - long d1Dot0 = Double.doubleToRawLongBits(1.0); - Translation tr = translateSleigh(ID_TOYBE64, """ - temp:8 = 0x%x f+ 0x%x; - temp2:8 = *temp; - """.formatted(dDot5, dDot5)); - Varnode temp2 = tr.program.getCode().get(1).getOutput(); - assertTrue(temp2.isUnique()); - tr.setLongMemVal(d1Dot0, LONG_CONST, 8); - tr.runFallthrough(); - assertEquals(LONG_CONST, tr.getLongVnVal(temp2)); - } - - @Test - public void testArmThumbFunc() throws Exception { - AssemblyBuffer asm = createBuffer(ID_ARMv8LE, 0x00400000); - - Language language = asm.getAssembler().getLanguage(); - Register regCtx = language.getContextBaseRegister(); - Register regT = language.getRegister("T"); - RegisterValue rvDefault = new RegisterValue(regCtx, - asm.getAssembler().getContextAt(asm.getNext()).toBigInteger(regCtx.getNumBytes())); - RegisterValue rvArm = rvDefault.assign(regT, BigInteger.ZERO); - RegisterValue rvThumb = rvDefault.assign(regT, BigInteger.ONE); - - AssemblyPatternBlock ctxThumb = AssemblyPatternBlock.fromRegisterValue(rvThumb); - - asm.assemble("mov r1, #456"); - Address addrBlx = asm.getNext(); - asm.assemble("blx 0x0"); - Address addrRet = asm.getNext(); // The address where we expect to return - asm.assemble("bx lr"); // Follows CALL, so principally, must be here, but not decoded - Address addrThumb = asm.getNext(); - asm.assemble("add r0, r1", ctxThumb); - asm.assemble("bx lr", ctxThumb); - - asm.assemble(addrBlx, "blx 0x%s".formatted(addrThumb)); - - Translation tr = translateBuffer(asm, asm.getEntry(), Map.of()); - - assertEquals(Map.ofEntries(tr.entryPrototype(asm.getEntry(), rvArm, 0), - tr.entryPrototype(addrThumb, rvThumb, 1)), tr.passageCls.getBlockEntries()); - - /** - * The blx will be a direct branch, so that will get executed in the bytecode. However, the - * bx lr (from THUMB) will be an indirect jump, causing a passage exit, so we should expect - * the return value to be the address immediately after the blx. Of course, that's not all - * that convincing.... So, we'll assert that r0 was set, too. - */ - assertEquals(addrRet.getOffset(), tr.runClean()); - assertEquals(456, tr.getLongRegVal("r0")); - } - - /** - * This is more diagnostics, but at the least, I should document that it doesn't work as - * expected, or perhaps just turn it completely off. - */ - @Test - @Ignore("TODO") - public void testUninitializedVsInitializedReads() { - TODO(); - } - - @Test - public void testExitAsThumb() throws Exception { - Translation tr = translateLang(ID_ARMv8LE, 0x00400000, """ - blx 0x00500000 - """, Map.of()); - Language language = tr.state.getLanguage(); - Register regCtx = language.getContextBaseRegister(); - Register regT = language.getRegister("T"); - - tr.runDecodeErr(0x00500000); - RegisterValue actualCtx = tr.getRegVal(regCtx); - RegisterValue expectedCtx = actualCtx.assign(regT, BigInteger.ONE); - assertEquals(expectedCtx, actualCtx); - } - - @Test - public void testDelaySlot() throws Exception { - Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ - brds r0 - imm r0, #123 - """, Map.of()); - tr.setLongRegVal("r0", 0x1234); - assertEquals(0x1234, tr.runClean()); - assertEquals(123, tr.getLongRegVal("r0")); - } - - @Test - public void testX86OffcutJump() throws Exception { - Translation tr = translateLang(ID_X8664, 0x00400000, """ - .emit eb ff c0 - CALL 0x0dedbeef - """.formatted(LONG_CONST), Map.of()); - tr.runDecodeErr(0x0dedbeef); - } - - @Test - public void testEmuInjectionCallEmuSwi() throws Exception { - Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ - imm r0,#123 - add r0,#7 - """, Map.ofEntries(Map.entry(0x00400002L, "emu_swi();"))); - - tr.runErr(InterruptPcodeExecutionException.class, "Execution hit breakpoint"); - - /** - * Two reasons we don't reach the add: 1) It's overridden, and there's no deferral to the - * decoded instruction. 2) Even if there were, we got interrupted before it executed. - */ - assertEquals(123, tr.getLongRegVal("r0")); - } - - @Test - public void testEmuInjectionCallEmuExecDecoded() throws Exception { - Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ - imm r0,#123 - add r0,#7 - """, Map.ofEntries(Map.entry(0x00400002L, """ - r1 = sleigh_userop(r0, 4:8); - emu_exec_decoded(); - """))); - - tr.runDecodeErr(0x00400004); - assertEquals(123 + 7, tr.getLongRegVal("r0")); - assertEquals(123 * 2 + 4, tr.getLongRegVal("r1")); - } - - @Test - public void testEmuInjectionCallEmuSkipDecoded() throws Exception { - Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ - imm r0,#123 - add r0,#7 - """, Map.ofEntries(Map.entry(0x00400002L, """ - r1 = sleigh_userop(r0, 4:8); - emu_skip_decoded(); - """))); - - tr.runDecodeErr(0x00400004); - assertEquals(123, tr.getLongRegVal("r0")); - assertEquals(123 * 2 + 4, tr.getLongRegVal("r1")); - } - - @Test - public void testFlagOpsRemoved() throws Exception { - Translation tr = translateLang(ID_TOYBE64, 0x00400000, """ - add r0,#6 - add r0,#7 - """, Map.of()); - - tr.runDecodeErr(0x00400004); - assertEquals(13, tr.getLongRegVal("r0")); - - long countSCarrys = Stream.of(tr.run.instructions.toArray()).filter(i -> { - if (!(i instanceof MethodInsnNode mi)) { - return false; - } - return "sCarryLongRaw".equals(mi.name); - }).count(); - assertEquals(1, countSCarrys); - } - - @Test - public void testCtxHazardousFallthrough() throws Exception { - Translation tr = translateLang(ID_ARMv8LE, 0x00400000, """ - mov r0,#6 - mov r1,#7 - """, Map.ofEntries(Map.entry(0x00400000L, """ - setISAMode(1:1); - emu_exec_decoded(); - """))); - - tr.runClean(); - assertEquals(6, tr.getLongRegVal("r0")); - // Should not execute second instruction, because of injected ctx change - assertEquals(0, tr.getLongRegVal("r1")); - } - - @Test - public void testCtxMaybeHazardousFallthrough() throws Exception { - /** - * For this test to produce the "MAYBE" case, the multiple paths have to be - * internal to an instruction (or inject). All that logic is only applied on an - * instruction-by-instruction basis. - */ - Translation tr = translateLang(ID_ARMv8LE, 0x00400000, """ - mov r0,#6 - mov r1,#7 - """, Map.ofEntries(Map.entry(0x00400000L, """ - if (!ZR) goto ; - ISAModeSwitch = 1; - setISAMode(ISAModeSwitch); - - emu_exec_decoded(); - """))); - - tr.setLongRegVal("r1", 0); // Reset - tr.setLongRegVal("ZR", 0); - // Since ctx wasn't touched at runtime, we fall out of program - tr.runDecodeErr(0x00400008); - assertEquals(6, tr.getLongRegVal("r0")); - assertEquals(7, tr.getLongRegVal("r1")); - assertEquals(0, tr.getLongRegVal("ISAModeSwitch")); - - tr.setLongRegVal("r1", 0); // Reset - tr.setLongRegVal("ZR", 1); - // Hazard causes exit before 2nd instruction - tr.runClean(); - assertEquals(6, tr.getLongRegVal("r0")); - assertEquals(0, tr.getLongRegVal("r1")); - assertEquals(1, tr.getLongRegVal("ISAModeSwitch")); - } -} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ToyBEJitCodeGeneratorTest.java similarity index 66% rename from Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java rename to Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ToyBEJitCodeGeneratorTest.java index 5cf18e9e27..bb6f00f3f6 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ToyBEJitCodeGeneratorTest.java @@ -13,15 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.jit.gen.op; +package ghidra.pcode.emu.jit.gen; -import ghidra.pcode.emu.jit.op.JitIntUnOp; +import ghidra.program.model.lang.Endian; -/** - * An extension for integer unary operators - * - * @param the class of p-code op node in the use-def graph - */ -public interface IntUnOpGen extends UnOpGen { - // Intentionally empty +public class ToyBEJitCodeGeneratorTest extends AbstractToyJitCodeGeneratorTest { + @Override + protected Endian getEndian() { + return Endian.BIG; + } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ToyLEJitCodeGeneratorTest.java similarity index 64% rename from Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java rename to Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ToyLEJitCodeGeneratorTest.java index a69074fe40..96d216d76b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/ToyLEJitCodeGeneratorTest.java @@ -13,18 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.emu.jit.gen.op; +package ghidra.pcode.emu.jit.gen; -import ghidra.pcode.emu.jit.op.JitFloatUnOp; +import ghidra.program.model.lang.Endian; -/** - * An extension for floating-point unary operators - * - * @param the class of p-code op node in the use-def graph - */ -public interface FloatUnOpGen extends UnOpGen { +public class ToyLEJitCodeGeneratorTest extends AbstractToyJitCodeGeneratorTest { @Override - default boolean isSigned() { - return false; + protected Endian getEndian() { + return Endian.LITTLE; } } diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/X86JitCodeGeneratorTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/X86JitCodeGeneratorTest.java new file mode 100644 index 0000000000..a2f7259f48 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/X86JitCodeGeneratorTest.java @@ -0,0 +1,61 @@ +/* ### + * IP: GHIDRA + * + * 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 ghidra.pcode.emu.jit.gen; + +import static org.junit.Assert.assertEquals; + +import java.math.BigInteger; +import java.util.Map; + +import org.junit.Test; + +import ghidra.program.model.lang.LanguageID; + +public class X86JitCodeGeneratorTest extends AbstractJitCodeGeneratorTest { + + protected static final LanguageID ID_X8664 = new LanguageID("x86:LE:64:default"); + + @Override + protected LanguageID getLanguageID() { + return ID_X8664; + } + + @Test + public void testX86DIV() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + MOV RAX, 0xdeadbeef + MOV RDX, 0x1234 + MOV RBX, 0x4321 + DIV RBX + """, Map.of()); + tr.runDecodeErr(0x0040001b); + BigInteger dividend = new BigInteger("123400000000deadbeef", 16); + BigInteger divisor = new BigInteger("4321", 16); + long quotient = dividend.divide(divisor).longValue(); + long remainder = dividend.remainder(divisor).longValue(); + assertEquals(quotient, tr.getLongRegVal("RAX")); + assertEquals(remainder, tr.getLongRegVal("RDX")); + } + + @Test + public void testX86OffcutJump() throws Exception { + Translation tr = translateLang(getLanguageID(), 0x00400000, """ + .emit eb ff c0 + CALL 0x0dedbeef + """.formatted(LONG_CONST), Map.of()); + tr.runDecodeErr(0x0dedbeef); + } +}