diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java index 3f582677c0..ed15729210 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java @@ -191,6 +191,11 @@ public interface EmuSyscallLibrary extends PcodeUseropLibrary { return false; } + @Override + public Class getOutputType() { + return void.class; + } + @Override public PcodeUseropLibrary getDefiningLibrary() { return syslib; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index bd75907bfa..269ba3c924 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -145,6 +145,11 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { return false; } + @Override + public Class getOutputType() { + return void.class; + } + @Override public Method getJavaMethod() { return null; 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 aa4080681e..5f3ab37a8f 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 @@ -15,14 +15,14 @@ */ package ghidra.pcode.emu.jit.analysis; -import static ghidra.pcode.emu.jit.analysis.JitVarScopeModel.*; +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.apache.commons.collections4.iterators.ReverseListIterator; import org.objectweb.asm.*; import ghidra.app.plugin.processors.sleigh.SleighLanguage; @@ -33,6 +33,7 @@ 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.var.*; import ghidra.program.model.address.*; @@ -258,6 +259,13 @@ public class JitAllocationModel { 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); + } } /** @@ -296,9 +304,10 @@ public class JitAllocationModel { * @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, MethodVisitor rv); + void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, MethodVisitor rv); /** * Emit bytecode to load the varnode's value onto the JVM stack. @@ -306,9 +315,10 @@ public class JitAllocationModel { * @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, MethodVisitor rv); + void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, MethodVisitor rv); } /** @@ -334,14 +344,16 @@ public class JitAllocationModel { } @Override - default void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + default void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { local().generateLoadCode(rv); - TypeConversions.generate(gen, this.type(), type, rv); + TypeConversions.generate(gen, this.type(), type, ext, rv); } @Override - default void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type(), rv); + default void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { + TypeConversions.generate(gen, type, this.type(), ext, rv); local().generateStoreCode(rv); } } @@ -387,9 +399,9 @@ public class JitAllocationModel { * 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 + * @param shift the number of bytes and direction to shift (+ is right) */ - public record MultiLocalPart(JvmLocal local, int shift) { + public record MultiLocalSub(JvmLocal local, int shift) { private JitType chooseLargerType(JitType t1, JitType t2) { return t1.size() > t2.size() ? t1 : t2; } @@ -405,15 +417,17 @@ public class JitAllocationModel { * @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, MethodVisitor rv) { + 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, rv); + TypeConversions.generate(gen, local.type, tempType, ext, rv); if (shift > 0) { switch (tempType) { case IntJitType t -> { @@ -440,7 +454,7 @@ public class JitAllocationModel { default -> throw new AssertionError(); } } - TypeConversions.generate(gen, tempType, type, rv); + TypeConversions.generate(gen, tempType, type, ext, rv); } /** @@ -454,14 +468,16 @@ public class JitAllocationModel { * @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, MethodVisitor rv) { + public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { JitType tempType = chooseLargerType(local.type, type); - TypeConversions.generate(gen, type, tempType, rv); + TypeConversions.generate(gen, type, tempType, ext, rv); switch (tempType) { case IntJitType t -> { if (shift > 0) { @@ -483,9 +499,9 @@ public class JitAllocationModel { rv.visitInsn(LUSHR); } } - default -> throw new AssertionError(); + default -> throw new AssertionError("tempType = " + tempType); } - TypeConversions.generate(gen, tempType, local.type, rv); + TypeConversions.generate(gen, tempType, local.type, ext, rv); switch (local.type) { case IntJitType t -> { int mask = -1 >>> (Integer.SIZE - Byte.SIZE * type.size()); @@ -524,6 +540,34 @@ public class JitAllocationModel { } } + 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 * @@ -537,6 +581,7 @@ public class JitAllocationModel { */ public record MultiLocalVarHandler(List parts, JitType type) implements VarHandler { + @Override public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { // Generator calls local inits directly @@ -549,31 +594,23 @@ public class JitAllocationModel { } @Override - public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { - parts.get(0).generateLoadCode(gen, this.type, rv); - for (MultiLocalPart part : parts.subList(1, parts.size())) { - part.generateLoadCode(gen, this.type, rv); - switch (this.type) { - case IntJitType t -> rv.visitInsn(IOR); - case LongJitType t -> rv.visitInsn(LOR); - default -> throw new AssertionError(); - } + 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, rv); + TypeConversions.generate(gen, this.type, type, ext, rv); } @Override - public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { - TypeConversions.generate(gen, type, this.type, rv); - for (MultiLocalPart part : parts.subList(1, parts.size()).reversed()) { - switch (this.type) { - case IntJitType t -> rv.visitInsn(DUP); - case LongJitType t -> rv.visitInsn(DUP2); - default -> throw new AssertionError(); - } - part.generateStoreCode(gen, this.type, rv); + 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 } - parts.get(0).generateStoreCode(gen, this.type, rv); } } @@ -599,12 +636,14 @@ public class JitAllocationModel { } @Override - public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { throw new AssertionError(); } @Override - public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { + public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext, + MethodVisitor rv) { throw new AssertionError(); } } @@ -711,6 +750,15 @@ public class JitAllocationModel { 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. * @@ -816,9 +864,7 @@ public class JitAllocationModel { } @Override - public void generateDeclCode(MethodVisitor mv, String nameThis, Label startLocals, - Label endLocals) { - super.generateDeclCode(mv, nameThis, startLocals, endLocals); + public void generateInitCode(MethodVisitor mv, String nameThis) { mv.visitLdcInsn(0); mv.visitVarInsn(ISTORE, index()); } @@ -860,6 +906,54 @@ public class JitAllocationModel { } } + 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; @@ -871,12 +965,13 @@ public class JitAllocationModel { private final Map handlers = new HashMap<>(); private final Map handlersPerVarnode = new HashMap<>(); private final NavigableMap locals = new TreeMap<>(); + private final Deque tempAllocs = new LinkedList<>(); /** * Construct the allocation model. * * @param context the analysis context - * @param dfm the data flow moel + * @param dfm the data flow model * @param vsm the variable scope model * @param tm the type model */ @@ -912,18 +1007,70 @@ public class JitAllocationModel { } /** - * Get the next free local index without reserving it + * Temporarily allocate the next {@code count} indices of local variables * *

- * This should be used by operator code generators after all the - * {@link JitBytesPcodeExecutorState state} bypassing local variables have been allocated. The - * variables should be scoped to that operator only, so that the ids used are freed for the next - * operator. + * 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. * - * @return the next id + * @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 int nextFreeLocal() { - return nextLocal; + 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; } /** @@ -934,12 +1081,10 @@ 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, + private List genFreeLocals(String name, List types, VarDesc desc) { JvmLocal[] result = new JvmLocal[types.size()]; - Iterable it = language.isBigEndian() - ? types - : () -> new ReverseListIterator(types); + Iterable it = language.isBigEndian() ? types : types.reversed(); long offset = desc.offset; int i = 0; for (SimpleJitType t : it) { @@ -1022,20 +1167,55 @@ public class JitAllocationModel { * locals. */ private VarHandler createComplicatedHandler(Varnode vn) { - Entry leftEntry = locals.floorEntry(vn.getAddress()); - assert overlapsLeft(leftEntry.getValue().vn, vn); - Address min = leftEntry.getKey(); - NavigableMap sub = locals.subMap(min, true, maxAddr(vn), true); - - List parts = new ArrayList<>(); - for (JvmLocal local : sub.values()) { - int offset = (int) switch (endian) { - case BIG -> maxAddr(leftEntry.getValue().vn).subtract(maxAddr(vn)); - case LITTLE -> vn.getAddress().subtract(leftEntry.getKey()); - }; - parts.add(new MultiLocalPart(local, offset)); + 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); + } + } } - return new MultiLocalVarHandler(parts, JitTypeBehavior.INTEGER.type(vn.getSize())); + + Entry firstEntry = locals.floorEntry(vn.getAddress()); + assert overlapsLeft(firstEntry.getValue().vn, vn); + 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)); + } + } + List parts = List.copyOf(legs.values()); + return new MultiLocalVarHandler(switch (endian) { + case BIG -> parts; + case LITTLE -> parts.reversed(); + }, type); } /** 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 ab8814bd11..ef7f79d971 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,10 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { return endian; } + public Varnode truncVnFromRight(Varnode vn, int amt) { + return new Varnode(vn.getAddress(), vn.getSize() - amt); + } + /** * Remove {@code amt} bytes from the right of the varnode. * @@ -94,10 +98,14 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @return the resulting value */ public JitVal truncFromRight(Varnode in1Vn, int amt, JitVal in1) { - Varnode outVn = new Varnode(in1Vn.getAddress(), in1Vn.getSize() - amt); + Varnode outVn = truncVnFromRight(in1Vn, amt); return subpiece(outVn, endian.isBigEndian() ? amt : 0, in1); } + public Varnode truncVnFromLeft(Varnode vn, int amt) { + return new Varnode(vn.getAddress().add(amt), vn.getSize() - amt); + } + /** * Remove {@code amt} bytes from the left of the varnode. * @@ -113,7 +121,7 @@ public class JitDataFlowArithmetic implements PcodeArithmetic { * @return the resulting value */ public JitVal truncFromLeft(Varnode in1Vn, int amt, JitVal in1) { - Varnode outVn = new Varnode(in1Vn.getAddress().add(amt), in1Vn.getSize() - amt); + Varnode outVn = truncVnFromLeft(in1Vn, amt); return subpiece(outVn, endian.isBigEndian() ? 0 : amt, in1); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java index a69de6514a..2976a4a286 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowState.java @@ -224,30 +224,40 @@ public class JitDataFlowState implements PcodeExecutorState { */ protected List doGetDefinitions(NavigableMap map, AddressSpace space, long offset, int size) { + long end = offset + size; List result = new ArrayList<>(); Entry preEntry = map.lowerEntry(offset); long cursor = offset; if (preEntry != null) { - if (endOf(preEntry) > offset) { + if (endOf(preEntry) > offset) { // Do I intersect the lower entry? JitVal preVal = preEntry.getValue(); Varnode preVn = new Varnode(space.getAddress(preEntry.getKey()), preVal.size()); - int shave = (int) (offset - preEntry.getKey()); - JitVal truncVal = arithmetic.truncFromLeft(preVn, shave, preVal); - cursor = endOf(preEntry); - result.add(truncVal); + int shaveLeft = (int) (offset - preEntry.getKey()); + JitVal truncVal = arithmetic.truncFromLeft(preVn, shaveLeft, preVal); + if (endOf(preEntry) > end) { // Am I contained in the lower entry? + Varnode truncVn = arithmetic.truncVnFromLeft(preVn, shaveLeft); + int shaveRight = (int) (endOf(preEntry) - end); + truncVal = arithmetic.truncFromRight(truncVn, shaveRight, truncVal); + cursor = end; + result.add(truncVal); + } + else { + cursor = endOf(preEntry); + result.add(truncVal); + } } } - long end = offset + size; for (Entry entry : map.subMap(offset, end).entrySet()) { if (entry.getKey() > cursor) { result.add(new JitMissingVar( new Varnode(space.getAddress(cursor), (int) (entry.getKey() - cursor)))); } - if (endOf(entry) > end) { + if (endOf(entry) > end) { // Do I have off the end? JitVal postVal = entry.getValue(); Varnode postVn = new Varnode(space.getAddress(entry.getKey()), postVal.size()); int shave = (int) (endOf(entry) - end); JitVal truncVal = arithmetic.truncFromRight(postVn, shave, postVal); + // NOTE: No need to check for contained here. Would have been caught above. cursor = end; result.add(truncVal); break; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java index 44f0fae094..313319f6e0 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitDataFlowUseropLibrary.java @@ -152,17 +152,13 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { * Get the type behavior from the userop's Java method * *

- * If the userop is not backed by a Java method, or its return type is not supported, this + * If the userop is not backed by a Java method, or its output type is not supported, this * return {@link JitTypeBehavior#ANY}. * * @return the type behavior */ - private JitTypeBehavior getReturnType() { - Method method = decOp.getJavaMethod(); - if (method == null) { - return JitTypeBehavior.ANY; - } - return JitTypeBehavior.forJavaType(method.getReturnType()); + private JitTypeBehavior getOutputTypeBehavior() { + return JitTypeBehavior.forJavaType(getOutputType()); } /** @@ -210,8 +206,8 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { } else { JitOutVar out = dfm.generateOutVar(outVn); - dfm.notifyOp(new JitCallOtherDefOp(op, out, getReturnType(), decOp, inVals, inTypes, - state.captureState())); + dfm.notifyOp(new JitCallOtherDefOp(op, out, getOutputTypeBehavior(), decOp, inVals, + inTypes, state.captureState())); state.setVar(outVn, out); } } @@ -236,6 +232,11 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary { return decOp.canInlinePcode(); } + @Override + public Class getOutputType() { + return decOp.getOutputType(); + } + @Override public Method getJavaMethod() { return decOp.getJavaMethod(); 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 c435f48b4e..cdf7f4e8b5 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 @@ -21,6 +21,8 @@ import java.util.*; import org.objectweb.asm.Opcodes; +import ghidra.lifecycle.Unfinished; + /** * The p-code type of an operand. * @@ -124,6 +126,9 @@ public interface JitType { * @return this type as an int */ SimpleJitType asInt(); + + @Override + SimpleJitType ext(); } /** @@ -201,6 +206,11 @@ public interface JitType { public IntJitType asInt() { return this; } + + @Override + public List legTypes() { + return List.of(this); + } } /** @@ -278,6 +288,11 @@ public interface JitType { public LongJitType asInt() { return this; } + + @Override + public List legTypes() { + return List.of(this); + } } /** @@ -326,6 +341,11 @@ public interface JitType { public IntJitType asInt() { return IntJitType.I4; } + + @Override + public List legTypes() { + return List.of(this); + } } /** @@ -374,10 +394,20 @@ public interface JitType { public LongJitType asInt() { return LongJitType.I8; } + + @Override + public List legTypes() { + return List.of(this); + } } /** - * WIP: The p-code types for integers of size 9 and greater. + * The p-code types for integers of size 9 and greater. + * + *

+ * We take the strategy of inlined manipulation of int locals, composed to form the full + * variable. When stored on the stack, the least-significant portion is always toward the top, + * no matter the language endianness. * * @param size the size in bytes */ @@ -432,22 +462,14 @@ public interface JitType { return size % Integer.BYTES; } - /** - * 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. - */ - public List legTypes() { + @Override + public List legTypes() { IntJitType[] types = new IntJitType[legsAlloc()]; int i = 0; if (partialSize() != 0) { types[i++] = IntJitType.forSize(partialSize()); } - for (; i < legsWhole(); i++) { + for (; i < types.length; i++) { types[i] = IntJitType.I4; } return Arrays.asList(types); @@ -492,6 +514,11 @@ public interface JitType { public MpFloatJitType ext() { return this; } + + @Override + public List legTypes() { + return Unfinished.TODO("MpFloat"); + } } /** @@ -526,4 +553,15 @@ public interface JitType { * @return the extended type */ JitType ext(); + + /** + * 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 big-endian order. + */ + List legTypes(); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java index 64a152ee2a..196a67123e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitTypeBehavior.java @@ -159,6 +159,9 @@ public enum JitTypeBehavior { if (cls == long.class) { return INTEGER; } + if (cls == int[].class) { + return INTEGER; + } if (cls == float.class) { return FLOAT; } 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 5bcd20b258..29254e2d70 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 @@ -222,6 +222,7 @@ public class JitTypeModel { * @param c the number of votes cast */ private void vote(JitTypeBehavior candidate, int c) { + Objects.requireNonNull(candidate); if (candidate == JitTypeBehavior.ANY || candidate == JitTypeBehavior.COPY) { return; } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java index 75b5c7d8b7..9337a09f6f 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/DecoderUseropLibrary.java @@ -128,6 +128,11 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary { return rtOp.canInlinePcode(); } + @Override + public Class getOutputType() { + return rtOp.getOutputType(); + } + @Override public Method getJavaMethod() { return rtOp.getJavaMethod(); 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 b8dbd1160c..15f679fee8 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 @@ -91,7 +91,7 @@ public interface GenConsts { 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_UNSIGNED = + 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); @@ -135,6 +135,9 @@ public interface GenConsts { 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 = @@ -147,6 +150,9 @@ public interface GenConsts { 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); @@ -182,10 +188,23 @@ public interface GenConsts { 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 = 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 d09dd3cda5..4a51e8fb6f 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 @@ -39,6 +39,7 @@ 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.var.ValGen; import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; @@ -216,9 +217,6 @@ public class JitCodeGenerator { private final MethodVisitor initMv; private final MethodVisitor runMv; - private final Label startLocals = new Label(); - private final Label endLocals = new Label(); - /** * Construct a code generator for the given passage's target classfile * @@ -594,10 +592,11 @@ public class JitCodeGenerator { * * @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 */ - public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq) { - return ValGen.lookup(v).generateValReadCode(this, v, typeReq, runMv); + public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq, Ext ext) { + return ValGen.lookup(v).generateValReadCode(this, v, typeReq, ext, runMv); } /** @@ -611,9 +610,10 @@ public class JitCodeGenerator { * * @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 */ - public void generateVarWriteCode(JitVar v, JitType type) { - VarGen.lookup(v).generateVarWriteCode(this, v, type, runMv); + public void generateVarWriteCode(JitVar v, JitType type, Ext ext) { + VarGen.lookup(v).generateVarWriteCode(this, v, type, ext, runMv); } /** @@ -858,20 +858,9 @@ public class JitCodeGenerator { */ protected void generateRunCode() { runMv.visitCode(); + final Label startLocals = new Label(); runMv.visitLabel(startLocals); - for (FixedLocal fixed : RunFixedLocal.ALL) { - fixed.generateDeclCode(runMv, nameThis, startLocals, endLocals); - } - - for (JvmLocal local : am.allLocals()) { - local.generateDeclCode(this, startLocals, endLocals, runMv); - } - // TODO: This for loop doesn't actually do anything.... - for (JitVal v : dfm.allValuesSorted()) { - VarHandler handler = am.getHandler(v); - handler.generateDeclCode(this, startLocals, endLocals, runMv); - } /** * NB. opIdx starts at 1, because JVM will ignore "Line number 0" */ @@ -886,6 +875,10 @@ public class JitCodeGenerator { } } + for (FixedLocal fixed : RunFixedLocal.ALL) { + fixed.generateInitCode(runMv, nameThis); + } + // [] RunFixedLocal.BLOCK_ID.generateLoadCode(runMv); // [blockId] @@ -929,6 +922,22 @@ public class JitCodeGenerator { for (ExceptionHandler handler : excHandlers.values()) { handler.generateRunCode(this, runMv); } + + final Label endLocals = new Label(); + runMv.visitLabel(endLocals); + + for (FixedLocal fixed : RunFixedLocal.ALL) { + fixed.generateDeclCode(runMv, nameThis, startLocals, endLocals); + } + + for (JvmLocal local : am.allLocals()) { + local.generateDeclCode(this, startLocals, endLocals, runMv); + } + // TODO: This for loop doesn't actually do anything.... + for (JitVal v : dfm.allValuesSorted()) { + VarHandler handler = am.getHandler(v); + handler.generateDeclCode(this, startLocals, endLocals, runMv); + } } /** @@ -953,8 +962,9 @@ public class JitCodeGenerator { dest.getParentFile().mkdirs(); try (OutputStream os = new FileOutputStream(dest)) { os.write(bytes); + new ProcessBuilder("javap", "-c", "-l", dest.getPath()).inheritIO().start().waitFor(); } - catch (IOException e) { + catch (IOException | InterruptedException e) { Msg.warn(this, "Could not dump class file: " + nameThis + " (" + e + ")"); } return bytes; @@ -989,7 +999,6 @@ public class JitCodeGenerator { initMv.visitMaxs(20, 20); initMv.visitEnd(); - runMv.visitLabel(endLocals); try { runMv.visitMaxs(20, 20); } 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 054bcc10ab..cc53bfb6a5 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,11 +15,19 @@ */ 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.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitBinOp; /** @@ -29,6 +37,134 @@ import ghidra.pcode.emu.jit.op.JitBinOp; */ public interface BinOpGen extends OpGen { + /** + * A choice of static method parameter to take as operator output + */ + enum TakeOut { + /** + * The out (first) parameter + */ + OUT, + /** + * The left (second) parameter + */ + LEFT; + } + + /** + * Emit bytecode that implements an mp-int binary operator via delegation to a static method on + * {@link JitCompiledPassage}. The method must have the signature: + * + *
+	 * void method(int[] out, int[] inL, int[] inR);
+	 * 
+ * + *

+ * This method presumes that the left operand's legs are at the top of the stack, + * least-significant leg on top, followed by the right operand legs, also least-significant leg + * on top. This will allocate the output array, move the operands into their respective input + * arrays, invoke the method, and then place the result legs on the stack, least-significant leg + * on top. + * + * @param gen the code generator + * @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 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. + */ + static void generateMpDelegationToStaticMethod(JitCodeGenerator gen, MpIntJitType type, + String methodName, MethodVisitor mv, int overProvisionLeft, TakeOut takeOut) { + /** + * 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); + } + + /** + * Whether this operator is signed + *

+ * In many cases, the operator itself is not affected by the signedness of the operands; + * however, if size adjustments to the operands are needed, this can determine how those + * operands are extended. + * + * @return true for signed, false if not + */ + boolean isSigned(); + + /** + * When loading and storing variables, the kind of extension to apply + * + * @return the extension kind + */ + default Ext ext() { + return Ext.forSigned(isSigned()); + } + + /** + * When loading the right operand, the kind of extension to apply + * + * @return the extension kind + */ + default Ext rExt() { + return ext(); + } + /** * Emit code between reading the left and right operands * @@ -79,12 +215,12 @@ public interface BinOpGen extends OpGen { */ @Override default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { - JitType lType = gen.generateValReadCode(op.l(), op.lType()); + 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()); + 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); + 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 index 604a26ae32..056d80fa05 100644 --- 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 @@ -17,14 +17,16 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.*; +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; /** @@ -32,7 +34,11 @@ import ghidra.pcode.emu.jit.op.JitBinOp; * * @param the class of p-code op node in the use-def graph */ -public interface BitwiseBinOpGen extends BinOpGen { +public interface BitwiseBinOpGen extends IntBinOpGen { + @Override + default boolean isSigned() { + return false; + } /** * The JVM opcode to implement this operator with int operands on the stack. @@ -49,7 +55,7 @@ public interface BitwiseBinOpGen extends BinOpGen { int longOpcode(); /** - * WIP: The implementation for multi-precision ints. + * The implementation for multi-precision ints. * * @param gen the code generator * @param type the type of each operand, including the reuslt @@ -66,37 +72,25 @@ public interface BitwiseBinOpGen extends BinOpGen { */ // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("result" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) + 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); } - for (int i = 0; i < legCount; i++) { - // [lleg1,...,llegN:INT] - mv.visitVarInsn(ILOAD, firstIndex + i); - // [lleg1,...,llegN:INT,rlegN:INT] - mv.visitInsn(intOpcode()); - // [lleg1,...,olegN:INT] - mv.visitVarInsn(ISTORE, firstIndex + i); - // [lleg1,...] - } - - // Push it all back, in reverse order - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); - } - mv.visitLabel(end); } @Override default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } /** @@ -110,7 +104,7 @@ public interface BitwiseBinOpGen extends BinOpGen { @Override default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); switch (rType) { case IntJitType t -> rv.visitInsn(intOpcode()); case LongJitType t -> rv.visitInsn(longOpcode()); @@ -118,6 +112,6 @@ public interface BitwiseBinOpGen extends BinOpGen { case MpIntJitType t -> TODO("MpInt of differing sizes"); default -> throw new AssertionError(); } - return lType; + 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 d21f88bad2..68d228440c 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 @@ -24,6 +24,9 @@ import ghidra.pcode.opbehavior.OpBehaviorBoolAnd; * @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 OpBehaviorBoolAnd}. Thus, this is identical to {@link IntAndOpGen}. + * @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 BoolAndOpGen implements BitwiseBinOpGen { /** The generator singleton */ 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 9d0d1f6a73..c97e13507c 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 @@ -17,7 +17,6 @@ package ghidra.pcode.emu.jit.gen.op; 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.*; @@ -36,6 +35,11 @@ public enum BoolNegateOpGen implements UnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitBoolNegateOp op, JitBlock block, JitType uType, MethodVisitor rv) { @@ -48,7 +52,11 @@ public enum BoolNegateOpGen implements UnOpGen { rv.visitLdcInsn(1L); rv.visitInsn(LXOR); } - case MpIntJitType t -> Unfinished.TODO("MpInt"); + 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; 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 abd1652197..6bb9f16edc 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 @@ -25,6 +25,7 @@ import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; 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.op.JitBranchIndOp; import ghidra.program.model.address.Address; import ghidra.program.model.lang.RegisterValue; @@ -55,9 +56,9 @@ public enum BranchIndOpGen implements OpGen { JitBlock block, MethodVisitor rv) { gen.generatePassageExit(block, () -> { // [...] - JitType targetType = gen.generateValReadCode(op.target(), op.targetType()); + JitType targetType = gen.generateValReadCode(op.target(), op.targetType(), Ext.ZERO); // [...,target:?] - TypeConversions.generateToLong(targetType, LongJitType.I8, rv); + TypeConversions.generateToLong(targetType, LongJitType.I8, Ext.ZERO, rv); // [...,target:LONG] }, ctx, rv); 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 7813d10e41..1416e4fb81 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 @@ -27,6 +27,7 @@ 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.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.op.JitCBranchOp; @@ -146,7 +147,7 @@ public enum CBranchOpGen implements OpGen { return; } - JitType cType = gen.generateValReadCode(op.cond(), op.condType()); + 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); 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 6c07775ccc..994dd4c2b6 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,23 +19,31 @@ 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.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.gen.*; import ghidra.pcode.emu.jit.gen.JitCodeGenerator.RetireMode; 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.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.PcodeUseropDefinition; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; @@ -153,6 +161,31 @@ public enum CallOtherOpGen implements OpGen { transition.generateInv(rv); } + static Parameter findOutputParameter(Parameter[] parameters, Method method) { + List found = + Stream.of(parameters).filter(p -> p.getAnnotation(OpOutput.class) != null).toList(); + return switch (found.size()) { + case 0 -> null; + case 1 -> { + Parameter p = found.get(0); + if (p.getType() == int[].class) { + yield p; + } + throw new IllegalArgumentException(""" + @%s requires parameter to have type int[] when functional=true. \ + Got %s (method %s)""".formatted( + OpOutput.class.getSimpleName(), p, method.getName())); + } + default -> { + throw new IllegalArgumentException(""" + @%s can only be applied to one parameter of method %s. \ + It is applied to: %s""".formatted( + OpOutput.class.getSimpleName(), method.getName(), + found.stream().map(Parameter::toString).collect(Collectors.joining(", ")))); + } + }; + } + /** * Emit code to implement the Direct strategy (see the class documentation) * @@ -175,6 +208,8 @@ public enum CallOtherOpGen implements OpGen { rv.visitTryCatchBlock(tryStart, tryEnd, gen.requestExceptionHandler((DecodedPcodeOp) op.op(), block).label(), NAME_THROWABLE); + JitAllocationModel am = gen.getAllocationModel(); + // [] useropField.generateLoadCode(gen, rv); // [userop] @@ -186,29 +221,106 @@ public enum CallOtherOpGen implements OpGen { rv.visitTypeInsn(CHECKCAST, owningLibName); // [library:OWNING_TYPE] Parameter[] parameters = method.getParameters(); - for (int i = 0; i < op.args().size(); i++) { - JitVal arg = op.args().get(i); - Parameter p = parameters[i]; - - JitType type = gen.generateValReadCode(arg, JitTypeBehavior.ANY); - if (p.getType() == boolean.class) { - TypeConversions.generateIntToBool(type, rv); + Parameter outputParameter = findOutputParameter(parameters, method); + if (outputParameter != null && method.getReturnType() != void.class) { + throw new IllegalArgumentException(""" + @%s cannot be applied to any parameter of a method returning non-void. \ + 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)); } else { - TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), rv); + 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); } } - // [library,params...] - rv.visitLabel(tryStart); - rv.visitMethodInsn(INVOKEVIRTUAL, owningLibName, method.getName(), - Type.getMethodDescriptor(method), false); - // [return?] - rv.visitLabel(tryEnd); - if (op instanceof JitCallOtherDefOp defOp) { - gen.generateVarWriteCode(defOp.out(), JitType.forJavaType(method.getReturnType())); + } + + static class ResourceGroup implements AutoCloseable { + private final List resources = new ArrayList<>(); + + @Override + public void close() throws Exception { + for (AutoCloseable r : resources) { + r.close(); + } } - else if (method.getReturnType() != void.class) { - TypeConversions.generatePop(JitType.forJavaType(method.getReturnType()), rv); + + public T add(T resource) { + resources.add(resource); + return resource; } } 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 index c9dcf1d4d6..f08041e67e 100644 --- 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 @@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatTestOp; * * @param the class of p-code op node in the use-def graph */ -public interface CompareFloatOpGen extends BinOpGen { +public interface CompareFloatOpGen extends FloatBinOpGen { /** * The JVM opcode to perform the comparison with float operands on the stack. 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 index 7fa382b629..43b8bd3cdc 100644 --- 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 @@ -20,7 +20,7 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; +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.*; @@ -34,11 +34,10 @@ import ghidra.pcode.emu.jit.op.JitIntTestOp; * * @param the class of p-code op node in the use-def graph */ -public interface CompareIntBinOpGen extends BinOpGen { +public interface CompareIntBinOpGen extends IntBinOpGen { /** - * Whether the comparison of p-code integers is signed - * + * {@inheritDoc} *

* If the comparison is unsigned, we will emit invocations of * {@link Integer#compareUnsigned(int, int)} or {@link Long#compareUnsigned(long, long)}, @@ -49,6 +48,7 @@ public interface CompareIntBinOpGen extends BinOpGen * * @return true if signed, false if not */ + @Override boolean isSigned(); /** @@ -58,6 +58,11 @@ public interface CompareIntBinOpGen extends BinOpGen */ 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 * @@ -69,8 +74,7 @@ public interface CompareIntBinOpGen extends BinOpGen rv.visitJumpInsn(icmpOpcode(), lblTrue); } else { - rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", - MDESC_INTEGER__COMPARE_UNSIGNED, false); + generateIntCmp("compareUnsigned", rv); rv.visitJumpInsn(ifOpcode(), lblTrue); } } @@ -94,7 +98,7 @@ public interface CompareIntBinOpGen extends BinOpGen /** * 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 @@ -104,7 +108,34 @@ public interface CompareIntBinOpGen extends BinOpGen @Override default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, 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; } /** @@ -123,11 +154,11 @@ public interface CompareIntBinOpGen extends BinOpGen Label lblTrue = new Label(); Label lblDone = new Label(); - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + 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 -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> generateMpIntCmp(gen, t, lblTrue, rv); default -> throw new AssertionError(); } JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); 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 e02aa8472c..52190562ba 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 @@ -34,6 +34,11 @@ public enum CopyOpGen implements UnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitCopyOp op, JitBlock block, JitType uType, MethodVisitor rv) { 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 d34dec6cdd..b179fd80a9 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 @@ -33,7 +33,7 @@ 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 UnOpGen { +public enum FloatAbsOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 4c4332b202..d903f4c550 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 @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatAddOp; * This uses the binary operator generator and simply emits {@link #FADD} or {@link #DADD} depending * on the type. */ -public enum FloatAddOpGen implements BinOpGen { +public enum FloatAddOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; 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/op/FloatBinOpGen.java new file mode 100644 index 0000000000..3e80cf42c6 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatBinOpGen.java @@ -0,0 +1,30 @@ +/* ### + * 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.op.JitFloatBinOp; + +/** + * An extension for floating-point binary operators + * + * @param the class of p-code op node in the use-def graph + */ +public interface FloatBinOpGen extends BinOpGen { + @Override + default boolean isSigned() { + return false; + } +} 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 1757504e38..526d3f9f56 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 @@ -34,7 +34,7 @@ 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 UnOpGen { +public enum FloatCeilOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 fb210ba10c..8af81ed2e2 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 @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatDivOp; * This uses the binary operator generator and simply emits {@link #FDIV} or {@link #DDIV} depending * on the type. */ -public enum FloatDivOpGen implements BinOpGen { +public enum FloatDivOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; 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 f1835eb3e0..330a56bc4f 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 @@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatFloat2FloatOp; *

* This uses the unary operator generator and emits {@link #F2D} or {@link #D2F}. */ -public enum FloatFloat2FloatOpGen implements UnOpGen { +public enum FloatFloat2FloatOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 60636d7d05..3a90bef445 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 @@ -34,7 +34,7 @@ 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 UnOpGen { +public enum FloatFloorOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 dc952442ad..5de1e6215b 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 @@ -36,6 +36,11 @@ public enum FloatInt2FloatOpGen implements UnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + 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; 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 651773b2a0..e202284646 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 @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatMultOp; * This uses the binary operator generator and simply emits {@link #FMUL} or {@link #DMUL} depending * on the type. */ -public enum FloatMultOpGen implements BinOpGen { +public enum FloatMultOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; 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 25822468b0..2c73c8b4b6 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 @@ -33,7 +33,7 @@ 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 UnOpGen { +public enum FloatNaNOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 3b48f90317..e2777d0734 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 @@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatNegOp; *

* This uses the unary operator generator and emits {@link #FNEG} or {@link #DNEG}. */ -public enum FloatNegOpGen implements UnOpGen { +public enum FloatNegOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 f7708f58b3..b28cb5d4ba 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 @@ -37,7 +37,7 @@ 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 UnOpGen { +public enum FloatRoundOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 a6fd21fbd3..5bb86b34fe 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 @@ -34,7 +34,7 @@ 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 UnOpGen { +public enum FloatSqrtOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; 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 75951643f8..9bfc9a683a 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 @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatSubOp; * This uses the binary operator generator and simply emits {@link #FSUB} or {@link #DSUB} depending * on the type. */ -public enum FloatSubOpGen implements BinOpGen { +public enum FloatSubOpGen implements FloatBinOpGen { /** The generator singleton */ GEN; 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 2348237af7..e7a2f41898 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 @@ -32,7 +32,7 @@ import ghidra.pcode.emu.jit.op.JitFloatTruncOp; * This uses the unary operator generator and emits {@link #F2I}, {@link #F2L}, {@link #D2I}, or * {@link #D2L}. */ -public enum FloatTruncOpGen implements UnOpGen { +public enum FloatTruncOpGen implements FloatUnOpGen { /** The generator singleton */ GEN; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java new file mode 100644 index 0000000000..a69074fe40 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/FloatUnOpGen.java @@ -0,0 +1,30 @@ +/* ### + * 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.op.JitFloatUnOp; + +/** + * 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 { + @Override + default boolean isSigned() { + return false; + } +} 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 4a210ed96d..2e9b107b5b 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,13 +15,17 @@ */ package ghidra.pcode.emu.jit.gen.op; +import java.util.List; + import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; +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.op.JitInt2CompOp; /** @@ -31,17 +35,69 @@ import ghidra.pcode.emu.jit.op.JitInt2CompOp; * This uses the unary operator generator and emits {@link #INEG} or {@link #LNEG}, depending on * type. */ -public enum Int2CompOpGen implements UnOpGen { +public enum Int2CompOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + 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 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 -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> generateMpInt2Comp(gen, t, rv); default -> throw new AssertionError(); } return uType; 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 bb0b775129..d19eaf951c 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,15 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; - -import org.objectweb.asm.*; +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.op.JitIntAddOp; /** @@ -36,12 +36,12 @@ import ghidra.pcode.emu.jit.op.JitIntAddOp; *

* NOTE: The multi-precision integer parts of this are a work in progress. */ -public enum IntAddOpGen implements BinOpGen { +public enum IntAddOpGen implements IntBinOpGen { /** The generator singleton */ GEN; static void generateMpIntLegAdd(JitCodeGenerator gen, int idx, boolean takesCarry, - boolean givesCarry, MethodVisitor mv) { + boolean givesCarry, boolean storesResult, MethodVisitor mv) { if (takesCarry) { // [...,llegN:INT,olegN+1:LONG] mv.visitLdcInsn(32); @@ -50,31 +50,38 @@ public enum IntAddOpGen implements BinOpGen { mv.visitInsn(DUP2_X1); mv.visitInsn(POP2); // [...,carryinN:LONG,llegN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,carryinN:LONG,llegN:LONG] mv.visitInsn(LADD); // [...,sumpartN:LONG] } else { - // [...,legN:INT] - TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); + // [...,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, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,sumpartN:LONG,rlegN:LONG] mv.visitInsn(LADD); // [...,olegN:LONG] - if (givesCarry) { - mv.visitInsn(DUP2); + 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); + } } - // [...,(olegN:LONG),olegN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); - // [...,(olegN:LONG),olegN:INT] - /** NB. The store will perform the masking */ - mv.visitVarInsn(ISTORE, idx); - // [...,(olegN:LONG)] } private void generateMpIntAdd(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { @@ -88,47 +95,40 @@ public enum IntAddOpGen implements BinOpGen { */ // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("result" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) - } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - boolean takesCarry = i != 0; // not first - generateMpIntLegAdd(gen, firstIndex + i, takesCarry, !isLast, mv); + 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); } + } - // Push it all back, in reverse order - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); - } - mv.visitLabel(end); + @Override + public boolean isSigned() { + return false; } @Override public JitType afterLeft(JitCodeGenerator gen, JitIntAddOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntAddOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, 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 -> TODO("MpInt of differing sizes"); + case MpIntJitType t -> throw new AssertionError("forceUniform didn't work?"); default -> throw new AssertionError(); } - return lType; + return rType; } } 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/op/IntBinOpGen.java new file mode 100644 index 0000000000..bfd398b686 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntBinOpGen.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.op; + +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 IntBinOpGen extends BinOpGen { + // Intentionally empty +} 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 395b3d54b9..8c23201f47 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 @@ -18,13 +18,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.*; +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.op.JitIntCarryOp; /** @@ -48,7 +50,7 @@ import ghidra.pcode.emu.jit.op.JitIntCarryOp; *

* NOTE: The multi-precision integer parts of this are a work in progress. */ -public enum IntCarryOpGen implements BinOpGen { +public enum IntCarryOpGen implements IntBinOpGen { /** The generator singleton */ GEN; @@ -59,35 +61,36 @@ public enum IntCarryOpGen implements BinOpGen { // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) int legCount = type.legsAlloc(); int remSize = type.partialSize(); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("temp" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) + + 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); } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean takesCarry = i != 0; // not first - IntAddOpGen.generateMpIntLegAdd(gen, firstIndex + i, takesCarry, true, 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, mv); - mv.visitLdcInsn(1); - mv.visitInsn(IAND); - mv.visitLabel(end); + } + + @Override + public boolean isSigned() { + return false; } @Override @@ -102,7 +105,7 @@ public enum IntCarryOpGen implements BinOpGen { * 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.forceUniformZExt(lType, rType, rv); + lType = TypeConversions.forceUniform(gen, lType, rType, ext(), rv); switch (lType) { case IntJitType(int size) when size == Integer.BYTES -> rv.visitInsn(DUP); case IntJitType lt -> { @@ -110,7 +113,8 @@ public enum IntCarryOpGen implements BinOpGen { case LongJitType(int size) when size == Long.BYTES -> rv.visitInsn(DUP2); case LongJitType lt -> { } - case MpIntJitType lt -> TODO("MpInt"); + case MpIntJitType lt -> { + } default -> throw new AssertionError(); } return lType; @@ -119,7 +123,7 @@ public enum IntCarryOpGen implements BinOpGen { @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntCarryOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); switch (rType) { case IntJitType(int size) when size == Integer.BYTES -> { // [l,l,r] @@ -128,7 +132,7 @@ public enum IntCarryOpGen implements BinOpGen { rv.visitInsn(SWAP); // spare an LDC,XOR // [sum,l] rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", - MDESC_INTEGER__COMPARE_UNSIGNED, false); + MDESC_INTEGER__COMPARE, false); // [cmpU(sum,l)] sum < l iff sign bit is 1 rv.visitLdcInsn(31); rv.visitInsn(IUSHR); 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 e15b508d19..8d8506dae1 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,7 +15,6 @@ */ 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; @@ -36,29 +35,40 @@ import ghidra.pcode.emu.jit.op.JitIntDivOp; * {@link Integer#divideUnsigned(int, int)} or {@link Long#divideUnsigned(long, long)} depending on * the type. */ -public enum IntDivOpGen implements BinOpGen { +public enum IntDivOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + private void generateMpIntDiv(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntDivide", mv, 1, TakeOut.OUT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntDivOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntDivOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, 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 -> TODO("MpInt"); + 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(); } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return lType; + // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations. + return rType; } } 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 9ca3b2fa54..a2618f65fc 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 @@ -29,6 +29,11 @@ public enum IntLeftOpGen implements ShiftIntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public String methodName() { return "intLeft"; 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 cab8efca0a..82b9c65ec8 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,8 +15,6 @@ */ 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; @@ -24,6 +22,7 @@ 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.op.JitIntMultOp; /** @@ -33,27 +32,63 @@ import ghidra.pcode.emu.jit.op.JitIntMultOp; * This uses the binary operator generator and simply emits {@link #IMUL} or {@link #LMUL} depending * on the type. */ -public enum IntMultOpGen implements BinOpGen { +public enum IntMultOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + /** + * Generate the mp-int multiply code. + *

+ * NOTE: I'd really like to know how many legs of the input operands are actually + * relevant. Very often, the following idiom is used: + * + *

+	 * temp: 16 = zext(r1) * zext(r2);
+	 * r0 = temp(0);
+	 * 
+ *

+ * 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. + *

+ * 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 + * used by a later cross-build. Maybe with the right API calls, I could derive that without + * additional Sleigh compiler support. If used, I should not cull any computations, so that the + * retired value is the full value. + * + * @param gen the code generator + * @param type the (uniform) type of the inputs and output operands + * @param mv the method visitor + */ + 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.forceUniformZExt(lType, rType, 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.forceUniformZExt(rType, lType, 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 -> TODO("MpInt"); + 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(); } - // TODO: For MpInt case, we should use the outvar's size to cull operations. - return lType; + // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations. + return rType; } } 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 a903b58a99..690462b051 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 @@ -17,7 +17,7 @@ package ghidra.pcode.emu.jit.gen.op; import org.objectweb.asm.MethodVisitor; -import ghidra.lifecycle.Unfinished; +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.*; @@ -32,23 +32,45 @@ import ghidra.pcode.emu.jit.op.JitIntNegateOp; * 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 UnOpGen { +public enum IntNegateOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + 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 JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntNegateOp op, JitBlock block, JitType uType, MethodVisitor rv) { switch (uType) { case IntJitType t -> { - rv.visitInsn(ICONST_M1); + rv.visitLdcInsn(-1 >>> (Integer.SIZE - t.size() * Byte.SIZE)); rv.visitInsn(IXOR); } case LongJitType t -> { - rv.visitLdcInsn(-1L); + rv.visitLdcInsn(-1L >>> (Long.SIZE - t.size() * Byte.SIZE)); rv.visitInsn(LXOR); } - case MpIntJitType t -> Unfinished.TODO("MpInt"); + case MpIntJitType t -> generateMpIntNegate(gen, t, rv); default -> throw new AssertionError(); } return uType; 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 3bc4e50936..bb85065519 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,7 +15,6 @@ */ 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; @@ -35,29 +34,40 @@ import ghidra.pcode.emu.jit.op.JitIntRemOp; * {@link Integer#remainderUnsigned(int, int)} or {@link Long#remainderUnsigned(long, long)} * depending on the type. */ -public enum IntRemOpGen implements BinOpGen { +public enum IntRemOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + + private void generateMpIntRem(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntDivide", mv, 1, TakeOut.LEFT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntRemOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntRemOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, 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 -> TODO("MpInt"); + 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 lType; + return rType; } } 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 7640a069e2..8905fa84ef 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 @@ -29,6 +29,11 @@ public enum IntRightOpGen implements ShiftIntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + @Override public String methodName() { return "intRight"; 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 09bfc6eaaf..408d5d1c40 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,7 +15,6 @@ */ 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; @@ -26,6 +25,7 @@ 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.JitIntSBorrowOp; /** @@ -37,20 +37,25 @@ import ghidra.pcode.emu.jit.op.JitIntSBorrowOp; * {@link JitCompiledPassage#sBorrowLongRaw(long, long)} depending on the type. We must then emit a * shift and mask to extract the correct bit. */ -public enum IntSBorrowOpGen implements BinOpGen { +public enum IntSBorrowOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSBorrowOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.SIGN, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSBorrowOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.SIGN, rv); switch (rType) { case IntJitType(int size) -> { rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sBorrowIntRaw", @@ -74,7 +79,7 @@ public enum IntSBorrowOpGen implements BinOpGen { return IntJitType.I1; } case MpIntJitType t -> { - return TODO("MpInt"); + return IntSCarryOpGen.generateMpIntSCarry(gen, t, "sBorrowMpInt", rv); } default -> throw new AssertionError(); } 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 6228aeb689..99dd1b166f 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,11 +15,12 @@ */ 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; +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.*; @@ -37,20 +38,39 @@ import ghidra.pcode.emu.jit.op.JitIntSCarryOp; * {@link JitCompiledPassage#sCarryLongRaw(long, long)} depending on the type. We must then emit a * shift and mask to extract the correct bit. */ -public enum IntSCarryOpGen implements BinOpGen { +public enum IntSCarryOpGen implements IntBinOpGen { /** The generator singleton */ GEN; @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSCarryOp op, JitType lType, JitType rType, - MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + public boolean isSigned() { + 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 JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSCarryOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv); switch (rType) { case IntJitType(int size) -> { rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sCarryIntRaw", @@ -74,7 +94,7 @@ public enum IntSCarryOpGen implements BinOpGen { return IntJitType.I1; } case MpIntJitType t -> { - return TODO("MpInt"); + return generateMpIntSCarry(gen, t, "sCarryMpInt", rv); } default -> throw new AssertionError(); } 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 76ddbf3fdc..c720f98d4a 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,8 +15,6 @@ */ 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; @@ -33,24 +31,36 @@ import ghidra.pcode.emu.jit.op.JitIntSDivOp; * This uses the binary operator generator and simply emits {@link #IDIV} or {@link #LDIV} depending * on the type. */ -public enum IntSDivOpGen implements BinOpGen { +public enum IntSDivOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + + private void generateMpIntSDiv(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntSignedDivide", mv, 1, + TakeOut.OUT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSDivOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSDivOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, 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 -> TODO("MpInt"); + 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. 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 129597f661..ee4437291a 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 @@ -17,12 +17,11 @@ package ghidra.pcode.emu.jit.gen.op; 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.analysis.JitType.IntJitType; +import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; import ghidra.pcode.emu.jit.gen.JitCodeGenerator; -import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.op.JitIntSExtOp; /** @@ -35,43 +34,18 @@ import ghidra.pcode.emu.jit.op.JitIntSExtOp; * {@link IntJitType#I4 int4} to {@link LongJitType#I8 int8} is implemented with by emitting only * {@link #I2L}. */ -public enum IntSExtOpGen implements UnOpGen { +public enum IntSExtOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + @Override public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntSExtOp op, JitBlock block, JitType uType, MethodVisitor rv) { - JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); - - if (uType == IntJitType.I4 && outType == LongJitType.I8) { - rv.visitInsn(I2L); - return outType; - } - - TypeConversions.generate(gen, uType, outType, rv); - switch (outType) { - case IntJitType t -> { - int shamt = Integer.SIZE - op.u().size() * Byte.SIZE; - if (shamt != 0) { - rv.visitLdcInsn(shamt); - rv.visitInsn(ISHL); - rv.visitLdcInsn(shamt); - rv.visitInsn(ISHR); - } - } - case LongJitType t -> { - int shamt = Long.SIZE - op.u().size() * Byte.SIZE; - if (shamt != 0) { - rv.visitLdcInsn(shamt); - rv.visitInsn(LSHL); - rv.visitLdcInsn(shamt); - rv.visitInsn(LSHR); - } - } - case MpIntJitType t -> Unfinished.TODO("MpInt"); - default -> throw new AssertionError(); - } - return outType; + return uType; } } 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 94397ee823..ac9a6daa8c 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 @@ -17,7 +17,6 @@ package ghidra.pcode.emu.jit.gen.op; 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.*; @@ -32,24 +31,36 @@ import ghidra.pcode.emu.jit.op.JitIntSRemOp; * This uses the binary operator generator and simply emits {@link #IREM} or {@link #LREM} depending * on the type. */ -public enum IntSRemOpGen implements BinOpGen { +public enum IntSRemOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return true; + } + + private void generateMpIntSRem(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { + BinOpGen.generateMpDelegationToStaticMethod(gen, type, "mpIntSignedDivide", mv, 1, + TakeOut.LEFT); + } + @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSRemOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformSExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, ext(), rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSRemOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformSExt(rType, lType, 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 -> Unfinished.TODO("MpInt"); + 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. 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 ffe78ec626..c8c10dc824 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 @@ -15,12 +15,7 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; - -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.JitIntSRightOp; /** @@ -35,14 +30,12 @@ public enum IntSRightOpGen implements ShiftIntBinOpGen { GEN; @Override - public String methodName() { - return "intSRight"; + public boolean isSigned() { + return true; } @Override - public JitType afterLeft(JitCodeGenerator gen, JitIntSRightOp op, JitType lType, JitType rType, - MethodVisitor rv) { - TypeConversions.generateSExt(lType, rv); - return lType; + public String methodName() { + return "intSRight"; } } 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 d52c1174d1..a19730af1b 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 @@ -17,13 +17,15 @@ package ghidra.pcode.emu.jit.gen.op; import static ghidra.lifecycle.Unfinished.TODO; -import org.objectweb.asm.*; +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.op.JitIntSubOp; /** @@ -36,10 +38,15 @@ import ghidra.pcode.emu.jit.op.JitIntSubOp; *

* NOTE: The multi-precision integer parts of this are a work in progress. */ -public enum IntSubOpGen implements BinOpGen { +public enum IntSubOpGen implements IntBinOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + return false; + } + private void generateMpIntLegSub(JitCodeGenerator gen, int idx, boolean takesBorrow, boolean givesBorrow, MethodVisitor mv) { if (takesBorrow) { @@ -50,19 +57,19 @@ public enum IntSubOpGen implements BinOpGen { mv.visitInsn(DUP2_X1); mv.visitInsn(POP2); // [...,borrowinN:LONG,llegN:INT] - mv.visitInsn(I2L); // yes, signed + 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, mv); + 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, mv); + TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv); // [...,diffpartN:LONG,rlegN:LONG] mv.visitInsn(LSUB); // [...,olegN:LONG] @@ -70,7 +77,7 @@ public enum IntSubOpGen implements BinOpGen { mv.visitInsn(DUP2); } // [...,(olegN:LONG),olegN:LONG] - TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); + TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv); // [...,(olegN:LONG),olegN:INT] /** NB. The store will perform the masking */ mv.visitVarInsn(ISTORE, idx); @@ -82,46 +89,34 @@ public enum IntSubOpGen implements BinOpGen { * 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 + * 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 - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - mv.visitLabel(start); - for (int i = 0; i < legCount; i++) { - mv.visitLocalVariable("result" + i, Type.getDescriptor(int.class), null, start, end, - firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) + 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); } - // [lleg1,...,llegN:INT] - for (int i = 0; i < legCount; i++) { - boolean isLast = i == legCount - 1; - boolean takesCarry = i != 0; // not first - generateMpIntLegSub(gen, firstIndex + i, takesCarry, !isLast, mv); - } - - // Push it all back, in reverse order - for (int i = 0; i < legCount; i++) { - mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); - } - mv.visitLabel(end); } @Override public JitType afterLeft(JitCodeGenerator gen, JitIntSubOp op, JitType lType, JitType rType, MethodVisitor rv) { - return TypeConversions.forceUniformZExt(lType, rType, rv); + return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv); } @Override public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSubOp op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - rType = TypeConversions.forceUniformZExt(rType, lType, rv); + rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv); switch (rType) { case IntJitType t -> rv.visitInsn(ISUB); case LongJitType t -> rv.visitInsn(LSUB); @@ -129,6 +124,6 @@ public enum IntSubOpGen implements BinOpGen { case MpIntJitType t -> TODO("MpInt of differing sizes"); default -> throw new AssertionError(); } - return lType; + return rType; } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.java new file mode 100644 index 0000000000..5cf18e9e27 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/gen/op/IntUnOpGen.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.op; + +import ghidra.pcode.emu.jit.op.JitIntUnOp; + +/** + * 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 +} 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 d66a9746a6..7e4c2e1eeb 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 @@ -35,10 +35,22 @@ import ghidra.pcode.emu.jit.op.JitIntZExtOp; * Note that this implementation is equivalent to {@link CopyOpGen}, except that differences in * operand sizes are expected. */ -public enum IntZExtOpGen implements UnOpGen { +public enum IntZExtOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + 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) { 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 c3d0fc295b..b9f6d2562c 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 @@ -26,6 +26,7 @@ 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.*; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitLoadOp; import ghidra.program.model.lang.Endian; @@ -184,9 +185,9 @@ public enum LoadOpGen implements OpGen { // [...] gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); // [...,space] - JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType()); + JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO); // [...,space,offset:?INT/LONG] - TypeConversions.generateToLong(offsetType, LongJitType.I8, rv); + TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv); // [...,space,offset:LONG] rv.visitLdcInsn(op.out().size()); // [...,space,offset,size] @@ -204,7 +205,7 @@ public enum LoadOpGen implements OpGen { default -> throw new AssertionError(); } // [...,value] - gen.generateVarWriteCode(op.out(), outType); + gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); // [...] } } 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 ca1847599d..2f7e076a0d 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 @@ -15,9 +15,10 @@ */ package ghidra.pcode.emu.jit.gen.op; -import static ghidra.lifecycle.Unfinished.TODO; 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; @@ -34,19 +35,77 @@ import ghidra.pcode.emu.jit.op.JitLzCountOp; * {@link Integer#numberOfLeadingZeros(int)} or {@link Long#numberOfLeadingZeros(long)}, depending * on the type. */ -public enum LzCountOpGen implements UnOpGen { +public enum LzCountOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + /** + * We use zero extension and then, when there is slack, we subtract off the zero bits that + * came from the extension. + */ + 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 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); - case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, - "numberOfLeadingZeros", MDESC_LONG__NUMBER_OF_LEADING_ZEROS, false); - case MpIntJitType t -> TODO("MpInt"); + 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(); } return IntJitType.I4; 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 3607703d2e..654abc790e 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,14 +15,19 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; +import java.io.PrintStream; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.objectweb.asm.*; 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.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.op.*; import ghidra.pcode.emu.jit.var.*; @@ -562,6 +567,148 @@ 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. + * + * @param temp the allocation of temporary legs + * @param legCount the number of legs to move + * @param mv the method visitor + */ + static void generateMpLegsIntoTemp(JvmTempAlloc temp, int legCount, MethodVisitor mv) { + // [leg1,...,legN] + for (int i = 0; i < legCount; i++) { + mv.visitVarInsn(ISTORE, temp.idx(i)); + } + // [] + } + + /** + * 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 + */ + 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] + } + + /** + * 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. + * + * @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 + */ + 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]] + } + } + + /** + * 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. + * + * @param legCount the number of legs in the array + * @param mv the method visitor + */ + 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. * @@ -579,9 +726,9 @@ 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)}, - * {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType)}, and {@link TypeConversions} - * appropriately. + * {@link JitCodeGenerator#generateValReadCode(JitVal, JitTypeBehavior, Ext)}, + * {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType, Ext)}, and + * {@link TypeConversions} appropriately. * * @param gen the code generator * @param op the p-code op (use-def node) to translate 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 5e5b273c6b..642b623572 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 @@ -15,7 +15,6 @@ */ 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; @@ -33,10 +32,30 @@ 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 UnOpGen { +public enum PopCountOpGen implements IntUnOpGen { /** The generator singleton */ GEN; + @Override + public boolean isSigned() { + 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 JitType generateUnOpRunCode(JitCodeGenerator gen, JitPopCountOp op, JitBlock block, JitType uType, MethodVisitor rv) { @@ -45,7 +64,7 @@ public enum PopCountOpGen implements UnOpGen { MDESC_INTEGER__BIT_COUNT, false); case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "bitCount", MDESC_LONG__BIT_COUNT, false); - case MpIntJitType t -> TODO("MpInt"); + case MpIntJitType t -> generateMpIntPopCount(gen, t, rv); default -> throw new AssertionError(); } return IntJitType.I4; 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 index 980e68681d..65acabca9b 100644 --- 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 @@ -19,12 +19,14 @@ 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.IntJitType; -import ghidra.pcode.emu.jit.analysis.JitType.LongJitType; +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; /** @@ -37,7 +39,17 @@ import ghidra.pcode.emu.jit.op.JitIntBinOp; * * @param the class of p-code op node in the use-def graph */ -public interface ShiftIntBinOpGen extends BinOpGen { +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 * @@ -45,6 +57,89 @@ public interface ShiftIntBinOpGen extends BinOpGen { */ 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} * @@ -55,20 +150,49 @@ public interface ShiftIntBinOpGen extends BinOpGen { @Override default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, JitType rType, MethodVisitor rv) { - String mdesc = switch (lType) { + JitAllocationModel am = gen.getAllocationModel(); + return switch (lType) { case IntJitType lt -> switch (rType) { - case IntJitType rt -> MDESC_$SHIFT_II; - case LongJitType rt -> MDESC_$SHIFT_IJ; + 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 -> MDESC_$SHIFT_JI; - case LongJitType rt -> MDESC_$SHIFT_JJ; + 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(); }; - rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true); - return lType.ext(); } } 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 4e4efb9a53..9e5cff18bc 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 @@ -26,6 +26,7 @@ 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.*; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitStoreOp; import ghidra.program.model.lang.Endian; @@ -184,11 +185,11 @@ public enum StoreOpGen implements OpGen { // [...] gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); // [...,space] - JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType()); + JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO); // [...,space,offset:?] - TypeConversions.generateToLong(offsetType, LongJitType.I8, rv); + TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv); // [...,space,offset:LONG] - JitType valueType = gen.generateValReadCode(op.value(), op.valueType()); + JitType valueType = gen.generateValReadCode(op.value(), op.valueType(), Ext.ZERO); // [...,space,offset,value] rv.visitLdcInsn(op.value().size()); // [...,space,offset,value,size] 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 dbbd64fbf8..88000acaee 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,12 +15,15 @@ */ package ghidra.pcode.emu.jit.gen.op; -import org.objectweb.asm.*; +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.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.JitSubPieceOp; /** @@ -42,18 +45,23 @@ public enum SubPieceOpGen implements OpGen { GEN; /** - * WIP: Assumes the previous (next more significant) leg 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. + * 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 void generateShiftWithPrevLeg(MethodVisitor rv, int bitShift, int index) { + private static void generateShiftWithPrevLeg(MethodVisitor rv, int bitShift, int index) { // [...,prevLegIn] rv.visitLdcInsn(Integer.SIZE - bitShift); - rv.visitInsn(ISHR); + rv.visitInsn(ISHL); // [...,prevLegIn:SLACK] rv.visitVarInsn(ILOAD, index); // [...,prevLegIn:SLACK,legIn] @@ -68,86 +76,85 @@ public enum SubPieceOpGen implements OpGen { // [...,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()); - JitType outType; - switch (vType) { + 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); - outType = vIType; + yield vIType; } case LongJitType vLType -> { rv.visitLdcInsn(op.offset() * Byte.SIZE); rv.visitInsn(LUSHR); - outType = vLType; - } - case MpIntJitType vMpType -> { - // WIP - MpIntJitType outMpType = MpIntJitType.forSize(op.out().size()); - int outLegCount = outMpType.legsAlloc(); - int legsLeft = vMpType.legsAlloc(); - int popCount = op.offset() / Integer.BYTES; - int byteShift = op.offset() % Integer.BYTES; - for (int i = 0; i < popCount; i++) { - rv.visitInsn(POP); - } - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label start = new Label(); - Label end = new Label(); - rv.visitLabel(start); - for (int i = 0; i < outLegCount; i++) { - rv.visitLocalVariable("subpiece" + i, Type.getDescriptor(int.class), null, - start, end, firstIndex + i); - rv.visitVarInsn(ISTORE, firstIndex + i); - // NOTE: More significant legs have higher indices (reverse of stack) - legsLeft--; - } - - if (byteShift > 0) { - int curLeg = outLegCount - 1; - if (legsLeft > 0) { - // [...,prevLegIn] - generateShiftWithPrevLeg(rv, byteShift * Byte.SIZE, firstIndex + curLeg); - // [...,legIn] - legsLeft--; - curLeg--; - } - else { - // [...] - rv.visitVarInsn(ILOAD, firstIndex + curLeg); - // [...,legIn] - rv.visitInsn(DUP); - // [...,legIn,legIn] - rv.visitLdcInsn(byteShift * Byte.SIZE); - rv.visitInsn(IUSHR); - // [...,legIn,legOut] - rv.visitVarInsn(ISTORE, firstIndex + curLeg); - // [...,legIn] - curLeg--; - } - while (curLeg >= 0) { - generateShiftWithPrevLeg(rv, byteShift * Byte.SIZE, firstIndex + curLeg); - legsLeft--; - curLeg--; - } - } - while (legsLeft > 0) { - rv.visitInsn(POP); - legsLeft--; - } - // NOTE: More significant legs have higher indices - for (int i = outLegCount - 1; i >= 0; i--) { - rv.visitVarInsn(ILOAD, firstIndex + i); - } - rv.visitLabel(end); - outType = outMpType; + yield vLType; } + case MpIntJitType vMpType -> generateMpIntSubPiece(gen, op, vMpType, rv); default -> throw new AssertionError(); - } - gen.generateVarWriteCode(op.out(), outType); + }; + gen.generateVarWriteCode(op.out(), outType, Ext.ZERO); } } 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 5ebe23d1c0..a2f87f58ee 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 @@ -20,6 +20,7 @@ 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.op.JitUnOp; /** @@ -29,6 +30,26 @@ import ghidra.pcode.emu.jit.op.JitUnOp; */ public interface UnOpGen extends OpGen { + /** + * Whether this operator is signed + *

+ * In many cases, the operator itself is not affected by the signedness of the operands; + * however, if size adjustments to the operands are needed, this can determine how those + * operands are extended. + * + * @return true for signed, false if not + */ + boolean isSigned(); + + /** + * When loading and storing variables, the kind of extension to apply + * + * @return the extension kind + */ + default Ext ext() { + return Ext.forSigned(isSigned()); + } + /** * Emit code for the unary operator * @@ -56,8 +77,8 @@ public interface UnOpGen extends OpGen { */ @Override default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { - JitType uType = gen.generateValReadCode(op.u(), op.uType()); + JitType uType = gen.generateValReadCode(op.u(), op.uType(), ext()); JitType outType = generateUnOpRunCode(gen, op, block, uType, rv); - gen.generateVarWriteCode(op.out(), outType); + gen.generateVarWriteCode(op.out(), outType, ext()); } } 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 774bc04916..3ca550fc95 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 @@ -17,6 +17,8 @@ package ghidra.pcode.emu.jit.gen.tgt; import java.math.BigInteger; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.objectweb.asm.Opcodes; @@ -1058,6 +1060,35 @@ public interface JitCompiledPassage { return a; } + /** + * The implementation of {@link PcodeOp#INT_SCARRY int_sborrow} on multi-precision ints. + * + * @param a the first operand as in {@code a - b} + * @param b the second operand as in {@code a - b} + * @param shift one less than the number of bits in each most-significant leg, i.e., the number + * of bits to shift right such that the most-significant bit of the most-significant + * leg becomes the least-significant bit of the most-significant leg. + * @return the one carry bit + */ + static int sBorrowMpInt(int[] a, int[] b, int shift) { + assert a.length == b.length; + long carry = 0; + for (int i = 0; i < a.length; i++) { + carry >>= Integer.SIZE; + carry += (a[i] & MASK_I2UL) - (b[i] & MASK_I2UL); + } + int msr = (int) carry; + int msa = a[a.length - 1]; + int msb = b[b.length - 1]; + + msa ^= msr; + msr ^= msb; + msr ^= -1; + msa &= msr; + + return (msa >> shift) & 1; + } + /** * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on JVM ints. * @@ -1098,6 +1129,199 @@ public interface JitCompiledPassage { return r; } + /** + * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on multi-precision ints. + * + * @param a the first operand as in {@code a + b} + * @param b the second operand as in {@code a + b} + * @param shift one less than the number of bits in each most-significant leg, i.e., the number + * of bits to shift right such that the most-significant bit of the most-significant + * leg becomes the least-significant bit of the most-significant leg. + * @return the one carry bit + */ + static int sCarryMpInt(int[] a, int[] b, int shift) { + assert a.length == b.length; + long carry = 0; + for (int i = 0; i < a.length; i++) { + carry >>>= Integer.SIZE; + carry += (a[i] & MASK_I2UL) + (b[i] & MASK_I2UL); + } + int msr = (int) carry; + int msa = a[a.length - 1]; + int msb = b[b.length - 1]; + + msr ^= msa; + msa ^= msb; + msa ^= -1; + msr &= msa; + + return (msr >> shift) & 1; + } + + enum MpShiftPrivate { + ; + static void shl(int[] out, int[] val, int amt) { + int legs = amt >>> 5; + int bits = amt & 0x1f; + /*for (int i = 0; i < out.length && i < legs; i++) { + out[i] = 0; + }*/ + if (bits == 0) { + for (int i = 0; i < val.length - legs & i < out.length - legs; i++) { + out[i + legs] = val[i]; + } + return; + } + int prev = 0; + for (int i = 0; i < val.length - legs & i < out.length - legs; i++) { + out[i + legs] = (val[i] << bits) | (prev >>> (Integer.SIZE - bits)); + prev = val[i]; + } + } + + static void ushr(int[] out, int[] val, int amt) { + int legs = amt >>> 5; + int bits = amt & 0x1f; + /*for (int i = 0; i < out.length && i < legs; i++) { + out[i + legs] = 0; + }*/ + if (bits == 0) { + for (int i = 0; i < val.length - legs & i < out.length; i++) { + out[i] = val[i + legs]; + } + return; + } + int prev = 0; + for (int i = Math.min(val.length - legs, out.length) - 1; i >= 0; i--) { + out[i] = (val[i + legs] >>> bits) | (prev << (Integer.SIZE - bits)); + prev = val[i + legs]; + } + } + + static void sshr(int[] out, int[] val, int amt, int sign) { + int legs = amt >>> 5; + int bits = amt & 0x1f; + if (bits == 0) { + for (int i = 0; i < val.length - legs & i < out.length; i++) { + out[i] = val[i + legs]; + } + if (sign != 0) { + for (int i = val.length - legs; i < out.length; i++) { + out[i] = sign; + } + } + return; + } + int prev = 0; + // Only apply signed shift to most-significant leg of val + if (val.length - legs - 1 >= 0) { + out[val.length - legs - 1] = (val[val.length - 1] >> bits); + prev = val[val.length - 1]; + } + for (int i = Math.min(val.length - legs, out.length) - 2; i >= 0; i--) { + out[i] = (val[i + legs] >>> bits) | (prev << (Integer.SIZE - bits)); + prev = val[i + legs]; + } + if (sign != 0) { + for (int i = val.length - legs; i < out.length; i++) { + out[i] = sign; + } + } + } + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on multi-precision ints. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val << amt}, in little-endian order + * @param amt the amt as in {@code val << amt}, in little-endian order + */ + static void intLeft(int[] out, int outBytes, int[] val, int[] amt) { + if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + Arrays.fill(out, 0); + return; + } + } + MpShiftPrivate.shl(out, val, amt[0]); + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on an mp-int with a JVM long shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val << amt}, in little-endian order + * @param amt the amt as in {@code val << amt} + */ + static void intLeft(int[] out, int outBytes, int[] val, long amt) { + if (Long.compareUnsigned(amt, (outBytes & MASK_I2UL) * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.shl(out, val, (int) amt); + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on an mp-int with a JVM int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val << amt}, in little-endian order + * @param amt the amt as in {@code val << amt} + */ + static void intLeft(int[] out, int outBytes, int[] val, int amt) { + if (Integer.compareUnsigned(amt, outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.shl(out, val, amt); + } + + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM long with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val << amt} + * @param amt the amt as in {@code val << amt}, in little-endian order + * @return the value + */ + static long intLeft(long val, int[] amt) { + if (Long.compareUnsigned(Integer.toUnsignedLong(amt[0]), Long.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val << amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM longs. * @@ -1134,6 +1358,30 @@ public interface JitCompiledPassage { return val << amt; } + /** + * The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM int with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHL ishl}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val << amt} + * @param amt the amt as in {@code val << amt}, in little-endian order + * @return the value + */ + static long intLeft(int val, int[] amt) { + if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val << amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM int with long amt. * @@ -1170,6 +1418,98 @@ public interface JitCompiledPassage { return val << amt; } + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on multi-precision ints. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val >> amt}, in little-endian order + * @param amt the amt as in {@code val >> amt}, in little-endian order + */ + static void intRight(int[] out, int outBytes, int[] val, int[] amt) { + if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + Arrays.fill(out, 0); + return; + } + } + MpShiftPrivate.ushr(out, val, amt[0]); + } + + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on an mp-int with a JVM long shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val >> amt}, in little-endian order + * @param amt the amt as in {@code val >> amt} + */ + static void intRight(int[] out, int outBytes, int[] val, long amt) { + if (Long.compareUnsigned(amt, (outBytes & MASK_I2UL) * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.ushr(out, val, (int) amt); + } + + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on an mp-int with a JVM int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val >> amt}, in little-endian order + * @param amt the amt as in {@code val >> amt} + */ + static void intRight(int[] out, int outBytes, int[] val, int amt) { + if (Integer.compareUnsigned(amt, outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, 0); + return; + } + MpShiftPrivate.ushr(out, val, amt); + } + + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM long with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val >> amt} + * @param amt the amt as in {@code val >> amt}, in little-endian order + * @return the value + */ + static long intRight(long val, int[] amt) { + if (Long.compareUnsigned(Integer.toUnsignedLong(amt[0]), Long.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val >>> amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM longs. * @@ -1206,6 +1546,30 @@ public interface JitCompiledPassage { return val >>> amt; } + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM int with an mp-int shift + * amount. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#IUSHR iushr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size clear the register. + * + * @param val the value as in {@code val >> amt} + * @param amt the amt as in {@code val >> amt}, in little-endian order + * @return the value + */ + static long intRight(int val, int[] amt) { + if (Integer.compareUnsigned(amt[0], Integer.SIZE) >= 0) { + return 0; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + return 0; + } + } + return val >>> amt[0]; + } + /** * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM int with long amt. * @@ -1242,6 +1606,34 @@ public interface JitCompiledPassage { return val >>> amt; } + /** + * The implementation of {@link PcodeOp#INT_RIGHT int_sright} on multi-precision ints. + * + *

+ * The semantics here are subtly different than the JVM's {@link Opcodes#ISHR ishr}: 1) The + * amount must be treated as unsigned. 2) Shifts in excess of val's size fill the register with + * the sign bit. + * + * @param out the array to receive the output, in little-endian order + * @param outBytes the actual size in bytes of the output operand + * @param val the value as in {@code val s>> amt}, in little-endian order + * @param amt the amt as in {@code val s>> amt}, in little-endian order + */ + static void intSRight(int[] out, int outBytes, int[] val, int[] amt) { + int sign = val[val.length - 1] < 0 ? -1 : 0; + if (Integer.compareUnsigned(amt[0], outBytes * Byte.SIZE) >= 0) { + Arrays.fill(out, sign); + return; + } + for (int i = 1; i < amt.length; i++) { + if (amt[i] != 0) { + Arrays.fill(out, sign); + return; + } + } + MpShiftPrivate.sshr(out, val, amt[0], sign); + } + /** * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM longs. * @@ -1318,6 +1710,323 @@ public interface JitCompiledPassage { return val >> amt; } + static final long MASK_I2UL = 0x0000_0000_ffff_ffffL; + + /** + * The implementation of {@link PcodeOp#INT_MULT} on mp-ints. + *

+ * All arrays are in little-endian order + * + * @param out the array allocated to receive the output + * @param inL the array of left input legs + * @param inR the array of right input legs + */ + static void mpIntMultiply(int[] out, int[] inL, int[] inR) { + long carry = 0; + long rp = inR[0] & MASK_I2UL; + for (int li = 0; li < inL.length && li < out.length; li++) { + long lp = inL[li] & MASK_I2UL; + carry += lp * rp; + out[li] = (int) carry; + carry >>>= Integer.SIZE; + } + + for (int ri = 1; ri < inR.length && ri < out.length; ri++) { + carry = 0; + rp = inR[ri] & MASK_I2UL; + for (int li = 0; li < inL.length && ri + li < out.length; li++) { + long lp = inL[li] & MASK_I2UL; + long op = out[li + ri] & MASK_I2UL; + carry += op + lp * rp; + out[li + ri] = (int) carry; + carry >>>= Integer.SIZE; + } + } + } + + public static String mpToString(int[] legs) { + if (legs == null) { + return "null"; + } + List list = IntStream.of(legs).mapToObj(i -> "%08x".formatted(i)).toList(); + return list.reversed().stream().collect(Collectors.joining(":")); + } + + enum MpDivPrivate { + ; + + /** + * Count the number of leading 0 bits in the first non-zero leg, and identify that leg's + * index + * + * @param legs + */ + static int lz(int[] legs) { + // Least-significant leg is first + int count = 0; + for (int i = legs.length - 1; i >= 0; i--) { + int llz = Integer.numberOfLeadingZeros(legs[i]); + count += llz; + if (llz != Integer.SIZE) { + break; + } + } + return count; + } + + static int size(int[] legs) { + // Least-significant leg is first + for (int i = legs.length - 1; i >= 0; i--) { + if (legs[i] != 0) { + return i + 1; + } + } + return 0; + } + + /** + * Shift the given mp-int legs left the given number of bits + * + * @param legs the legs + * @param shift the number of bits to shift left + */ + static void shl(int[] legs, int shift) { + if (shift == 0) { + return; // The extra leading leg is already 0 + } + assert shift >= 0 && shift < Integer.SIZE; + + long carry = 0; + for (int i = 0; i < legs.length; i++) { + carry |= (legs[i] & MASK_I2UL) << shift; + legs[i] = (int) carry; + carry >>>= Integer.SIZE; + } + } + + static void shr(int[] legs, int shift) { + if (shift == 0) { + return; + } + assert shift >= 0 && shift < Integer.SIZE; + + long carry = 0; + for (int i = legs.length - 1; i >= 0; i--) { + carry |= (legs[i] & MASK_I2UL) << (Integer.SIZE - shift); + legs[i] = (int) (carry >> Integer.SIZE); + carry <<= Integer.SIZE; + } + } + + /** + * Perform unsigned division for a multi-precision dividend and single-precision divisor + * + * @param out the output for the quotient + * @param inL the dividend, and the output for the remainder + * @param sizeL the number of legs in the dividend + * @param inR the divisor + */ + static void divideMpSp(int[] out, int[] inL, int sizeL, int inR) { + long r = 0; + for (int j = sizeL - 1; j >= 0; j--) { + r <<= Integer.SIZE; + r += inL[j] & MASK_I2UL; + out[j] = (int) (r / inR); + r %= inR; + inL[j] = 0; // So that the mp-int inL is truly the remainder + } + inL[0] = (int) r; + } + + /** + * Perform unsigned division (or division of magnitudes) + * + * @param out the output for the quotient + * @param inL the dividend, and the output for the remainder + * @param inR the divisor + * @implNote this is just Algorithm D from Knuth's TAOCP Volume 2 without any sophisticated + * optimizations. We don't really need to optimize for the "big" case, we just + * need to support the bigger-than-a-machine-word case. + */ + static void divide(int[] out, int[] inL, int[] inR) { + /** + * Before we mutate anything, compute lengths for D2. We'll compute sizeR from the + * leading-zeroes computation in D1. + */ + int sizeL = size(inL); + + /** + * D1 [Normalize] + * + * My understanding of this step is to assure that the divisor (inR) has a 1 in the + * most-significant bit of its most-significant leg ("digit" in the text's terminology). + */ + int shiftBits = lz(inR); + + int truncR = shiftBits / Integer.SIZE; + int sizeR = inR.length - truncR; + + if (sizeR == 1) { + /** + * Never mind all this. Just do the simple algorithm (Exercise 16, in TAOCP Vol. 2, + * Section 4.3.1). We actually can't use the full multi-precision algorithm, because + * the adjustment of qHat in step D3 assumes the size of the divisor is >= 2 legs. + */ + divideMpSp(out, inL, sizeL, inR[0]); + return; + } + + int shift = shiftBits % Integer.SIZE; + + shl(inL, shift); + shl(inR, shift); + + /** + * D2 [Initialize j] + * + * What should be an easy step here is complicated by the fact that every + * operand has to (conventionally) have equal size is Sleigh. Thus, we need to seek out + * the most-significant leg with a non-zero value for each, and then compute j. Probably + * need to avoid an off-by-one error here, too. + * + * dividend has size m + n "sizeL" (text calls dividend u_{m+n-1}...u_0) + * + * divisor has size n "sizeR" (text calls divisor v_{n-1}...v_0}) + * + * Thus m = sizeL - sizeR + */ + for (int j = sizeL - sizeR; j >= 0; j--) { // step and test are D7 + /** + * D3 [Calculate q\^] + */ + // NOTE That inL is over-provisioned by 1, so we're good to index m+n + long qHat = (inL[sizeR + j] & MASK_I2UL) << Integer.SIZE; + qHat |= inL[sizeR + j - 1] & MASK_I2UL; + long rHat = qHat; + long vNm1 = inR[sizeR - 1] & MASK_I2UL; // v_{n-1} + qHat /= vNm1; + rHat %= vNm1; + + do { + if (qHat == 1L << Integer.SIZE || Long.compareUnsigned( + qHat * (inR[sizeR - 2] & MASK_I2UL), + (rHat << Integer.SIZE) + (inL[sizeR + j - 2] & MASK_I2UL)) > 0) { + qHat--; + rHat += vNm1; + } + else { + break; + } + } + while (Long.compareUnsigned(rHat, 1L << Integer.SIZE) < 0); + + /** + * D4 [Multiply and subtract] + * + * NOTE: borrow will become -1 if a borrow is needed, so add it to each subsequent + * leg and use signed shift. + */ + long borrow = 0; + for (int i = 0; i < sizeR - 1; i++) { + borrow = (inL[j + i] & MASK_I2UL) - qHat * (inR[i] & MASK_I2UL) + borrow; + inL[j + i] = (int) borrow; + borrow >>= Integer.SIZE; + } + borrow = (inL[j + sizeR] & MASK_I2UL) + borrow; + inL[j + sizeR] = (int) borrow; + borrow >>= Integer.SIZE; + + /** + * D5 [Test remainder] + */ + if (borrow != 0) { + assert borrow == -1; + /** + * D6 [Add back] + */ + qHat--; + + long carry = 0; + for (int i = 0; i < sizeR; i++) { + carry += (inL[j + i] & MASK_I2UL) + inR[i]; + inL[j + i] = (int) carry; + carry >>>= Integer.SIZE; + } + } + out[j] = (int) qHat; // Completion of D5 + + /** + * D7 [Loop on j] + * + * The step and test of the for loop that ends here implements D7 + */ + } + /** + * D8 [Unnormalize] + */ + shr(inL, shift); + } + + static void neg(int[] legs, int count) { + long carry = 1; + for (int i = 0; i < count; i++) { + carry += (~legs[i] & MASK_I2UL); + legs[i] = (int) carry; + carry >>>= Integer.SIZE; + } + } + + static void sdivide(int[] out, int[] inL, int[] inR) { + // NOTE: inL is over-provisioned by 1 + boolean signL = inL[inL.length - 2] < 0; + boolean signR = inR[inR.length - 1] < 0; + if (signL) { + neg(inL, inL.length - 1); + } + if (signR) { + neg(inR, inR.length); + } + divide(out, inL, inR); + if (signL != signR) { + neg(out, out.length); + } + if (signL) { + neg(inL, inL.length - 1); + } + } + } + + /** + * The implementation of {@link PcodeOp#INT_DIV} on mp-ints. + *

+ * All arrays are in little-endian order. While this directly implements + * {@link PcodeOp#INT_DIV}, it is also used for {@link PcodeOp#INT_REM}, + * {@link PcodeOp#INT_SDIV}, and {@link PcodeOp#INT_SREM}. + * + * @param out the array allocated to receive the quotient + * @param inL the array of dividend input legs, over-provisioned by 1, which will also receive + * the remainder + * @param inR the array of divisor input legs + */ + static void mpIntDivide(int[] out, int[] inL, int[] inR) { + MpDivPrivate.divide(out, inL, inR); + } + + /** + * The implementation of {@link PcodeOp#INT_SDIV} on mp-ints. + *

+ * All arrays are in little-endian order. While this directly implements + * {@link PcodeOp#INT_SDIV}, it is also used for {@link PcodeOp#INT_SREM}. + * + * @param out the array allocated to receive the quotient + * @param inL the array of dividend input legs, over-provisioned by 1, which will also receive + * the remainder + * @param inR the array of divisor input legs + */ + static void mpIntSignedDivide(int[] out, int[] inL, int[] inR) { + MpDivPrivate.sdivide(out, inL, inR); + } + /** * Get the language for the given string language ID * 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 index bd17bcf1b7..61129df9a7 100644 --- 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 @@ -17,16 +17,17 @@ package ghidra.pcode.emu.jit.gen.type; import static ghidra.pcode.emu.jit.gen.GenConsts.*; -import org.objectweb.asm.*; +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.gen.op.IntSExtOpGen; import ghidra.pcode.emu.jit.op.JitBinOp; import ghidra.program.model.pcode.PcodeOp; @@ -61,6 +62,21 @@ import ghidra.program.model.pcode.PcodeOp; * */ 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. @@ -72,12 +88,24 @@ public interface TypeConversions extends Opcodes { * * @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 checkGenIntMask(JitType from, IntJitType to, MethodVisitor mv) { + static void checkGenIntExt(JitType from, IntJitType to, Ext ext, MethodVisitor mv) { if (to.size() < from.size() && to.size() < Integer.BYTES) { - mv.visitLdcInsn(-1 >>> (Integer.SIZE - to.size() * Byte.SIZE)); - mv.visitInsn(IAND); + 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); + } + } } } @@ -86,11 +114,12 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { - checkGenIntMask(from, to, mv); + static IntJitType generateIntToInt(IntJitType from, IntJitType to, Ext ext, MethodVisitor mv) { + checkGenIntExt(from, to, ext, mv); return to; } @@ -99,12 +128,14 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { + static IntJitType generateLongToInt(LongJitType from, IntJitType to, Ext ext, + MethodVisitor mv) { mv.visitInsn(L2I); - checkGenIntMask(from, to, mv); + checkGenIntExt(from, to, ext, mv); return to; } @@ -130,10 +161,12 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { + 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; @@ -146,7 +179,7 @@ public interface TypeConversions extends Opcodes { mv.visitInsn(POP); // [...,legN] } - checkGenIntMask(from, to, mv); + checkGenIntExt(from, to, ext, mv); return to; } @@ -158,16 +191,17 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { + static IntJitType generateToInt(JitType from, IntJitType to, Ext ext, MethodVisitor mv) { return switch (from) { - case IntJitType iFrom -> generateIntToInt(iFrom, to, mv); // in case of mask - case LongJitType lFrom -> generateLongToInt(lFrom, to, mv); + 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, mv); + case MpIntJitType mpFrom -> generateMpIntToInt(mpFrom, to, ext, mv); default -> throw new AssertionError(); }; } @@ -183,12 +217,24 @@ public interface TypeConversions extends Opcodes { * * @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 checkGenLongMask(JitType from, LongJitType to, MethodVisitor mv) { + static void checkGenLongExt(JitType from, LongJitType to, Ext ext, MethodVisitor mv) { if (to.size() < from.size()) { - mv.visitLdcInsn(-1L >>> (Long.SIZE - to.size() * Byte.SIZE)); - mv.visitInsn(LAND); + 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); + } + } } } @@ -200,27 +246,37 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { - mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "toUnsignedLong", - MDESC_INTEGER__TO_UNSIGNED_LONG, false); + 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. - checkGenLongMask(from, to, mv); + checkGenLongExt(from, to, ext, mv); return to; } /** - * Emit bytecode to convert one p-code in (in a JVM long) to another + * 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, MethodVisitor mv) { - checkGenLongMask(from, to, mv); + static LongJitType generateLongToLong(LongJitType from, LongJitType to, Ext ext, + MethodVisitor mv) { + checkGenLongExt(from, to, ext, mv); return to; } @@ -246,12 +302,14 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { + static LongJitType generateMpIntToLong(MpIntJitType from, LongJitType to, Ext ext, + MethodVisitor mv) { if (from.legsAlloc() == 1) { - generateIntToLong(IntJitType.forSize(from.size()), to, mv); + generateIntToLong(IntJitType.forSize(from.size()), to, ext, mv); return to; } // Remove all but the 2 least-significant legs @@ -265,7 +323,7 @@ public interface TypeConversions extends Opcodes { // [...,legN-1,legN] } mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "conv2IntToLong", - MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, false); + MDESC_JIT_COMPILED_PASSAGE__CONV_OFFSET2_TO_LONG, true); return to; } @@ -277,16 +335,17 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { + static LongJitType generateToLong(JitType from, LongJitType to, Ext ext, MethodVisitor mv) { return switch (from) { - case IntJitType iFrom -> generateIntToLong(iFrom, to, mv); - case LongJitType lFrom -> generateLongToLong(lFrom, to, mv); // in case of mask + 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, mv); + case MpIntJitType mpFrom -> generateMpIntToLong(mpFrom, to, ext, mv); default -> throw new AssertionError(); }; } @@ -369,39 +428,63 @@ public interface TypeConversions extends Opcodes { * * @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, MethodVisitor mv) { + static MpIntJitType generateIntToMpInt(IntJitType from, MpIntJitType to, Ext ext, + MethodVisitor mv) { if (to.legsAlloc() == 1) { - checkGenIntMask(from, IntJitType.forSize(to.size()), mv); + checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv); return to; } // Insert as many more significant legs as needed - for (int i = 1; i < to.legsAlloc(); i++) { - mv.visitLdcInsn(0); - mv.visitInsn(SWAP); + // 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 its int a JVM long to multi-precision int. + * 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(LongJitType from, MpIntJitType to, MethodVisitor mv) { + static MpIntJitType generateLongToMpInt(JitCodeGenerator gen, LongJitType from, MpIntJitType to, + Ext ext, MethodVisitor mv) { if (to.legsAlloc() == 1) { mv.visitInsn(L2I); - checkGenIntMask(from, IntJitType.forSize(to.size()), mv); + checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv); return to; } if (from.size() <= Integer.BYTES) { mv.visitInsn(L2I); - generateIntToMpInt(IntJitType.forSize(from.size()), to, mv); + generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv); return to; } // Convert, then insert as many more significant legs as needed @@ -413,29 +496,53 @@ public interface TypeConversions extends Opcodes { mv.visitLdcInsn(Integer.SIZE); mv.visitInsn(LUSHR); mv.visitInsn(L2I); - /** This is the upper leg, which may need masking */ - checkGenIntMask(IntJitType.forSize(from.size() - Integer.BYTES), - IntJitType.forSize(to.partialSize()), mv); - // [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) { - mv.visitLdcInsn(0); - // [msl:INT,lsl:INT,0] - for (int i = 2; i < to.legsAlloc(); i++) { - // [msl:INT,lsl:INT,0] - mv.visitInsn(DUP_X2); - // [0,msl:INT,lsl:INT,0] + /** 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)); + } } - // [...,0,msl:INT,lsl:INT,0] + + // [val:LONG,msl:INT] + mv.visitInsn(DUP_X2); + // [msl:INT,val:LONG,msl:INT] mv.visitInsn(POP); - // [...,0,msl:INT,lsl:INT] + // [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; } @@ -446,22 +553,23 @@ public interface TypeConversions extends Opcodes { * @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, MethodVisitor mv) { + 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()), mv); + generateMpIntToInt(from, IntJitType.forSize(to.size()), ext, mv); return to; } if (from.legsAlloc() == 1) { - generateIntToMpInt(IntJitType.forSize(from.size()), to, mv); + generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv); return to; } @@ -469,42 +577,44 @@ public interface TypeConversions extends Opcodes { int legsIn = from.legsAlloc(); int legsOut = to.legsAlloc(); int localsCount = Integer.min(legsIn, legsOut); - int firstIndex = gen.getAllocationModel().nextFreeLocal(); - Label localsStart = new Label(); - Label localsEnd = new Label(); - mv.visitLabel(localsStart); - for (int i = 0; i < localsCount; i++) { - mv.visitLocalVariable("temp" + i, Type.getDescriptor(int.class), null, localsStart, - localsEnd, firstIndex + i); - mv.visitVarInsn(ISTORE, firstIndex + i); - } - // Add or remove legs - int toAdd = legsOut - legsIn; - for (int i = 0; i < toAdd; i++) { - mv.visitLdcInsn(0); - } - int toRemove = legsIn - legsOut; - for (int i = 0; i < toRemove; i++) { - mv.visitInsn(POP); - } + try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", localsCount)) { + for (int i = 0; i < localsCount; i++) { + mv.visitVarInsn(ISTORE, temp.idx(i)); + } - // Start pushing them back, but the most significant may need masking - int idx = firstIndex + localsCount; - idx--; - mv.visitVarInsn(ILOAD, idx); - if (to.size() < from.size()) { - checkGenIntMask( - from, // already checked size, so anything greater - IntJitType.forSize(to.partialSize()), mv); - } - // push the rest back - for (int i = 0; i < localsCount; i++) { - idx--; - mv.visitVarInsn(ILOAD, idx); - } + // 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); + } - mv.visitLabel(localsEnd); + // 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; } @@ -518,17 +628,18 @@ public interface TypeConversions extends Opcodes { * @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, - MethodVisitor mv) { + Ext ext, MethodVisitor mv) { return switch (from) { - case IntJitType iFrom -> generateIntToMpInt(iFrom, to, mv); - case LongJitType lFrom -> generateLongToMpInt(lFrom, to, mv); + 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, mv); + case MpIntJitType mpFrom -> generateMpIntToMpInt(gen, mpFrom, to, ext, mv); default -> throw new AssertionError(); }; } @@ -543,16 +654,18 @@ public interface TypeConversions extends Opcodes { * @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, MethodVisitor mv) { + static JitType generate(JitCodeGenerator gen, JitType from, JitType to, Ext ext, + MethodVisitor mv) { return switch (to) { - case IntJitType iTo -> generateToInt(from, iTo, mv); - case LongJitType lTo -> generateToLong(from, lTo, mv); + 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, mv); + case MpIntJitType mpTo -> generateToMpInt(gen, from, mpTo, ext, mv); default -> throw new AssertionError(); }; } @@ -695,9 +808,6 @@ public interface TypeConversions extends Opcodes { /** * Emit code to extend a signed value of the given type to fill its host JVM type. * - *

- * This is implemented in the same manner as {@link IntSExtOpGen int_sext}. - * * @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. @@ -727,21 +837,6 @@ public interface TypeConversions extends Opcodes { return type.ext(); } - /** - * Convert a signed {@link IntJitType#I4 int4} to {@link LongJitType#I8 int8}. - * - *

- * Note that if conversion from a smaller int type is needed, the generator must first call - * {@link #generateSExt(JitType, MethodVisitor)}. - * - * @param mv the method visitor - * @return the resulting type ({@link LongJitType#I8 int8}) - */ - static LongJitType generateSExtIntToLong(MethodVisitor mv) { - mv.visitInsn(I2L); - return LongJitType.I8; - } - /** * 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. @@ -769,59 +864,38 @@ public interface TypeConversions extends Opcodes { * 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 forceUniformZExt(JitType myType, JitType otherType, MethodVisitor mv) { - return switch (myType.ext()) { - case IntJitType mt -> switch (otherType.ext()) { + 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, mv); - case MpIntJitType ot -> generateIntToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); + 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; - case MpIntJitType ot -> generateLongToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); + // FIXME: Would be nice to allow non-uniform mp-int sizes + case MpIntJitType ot -> generateLongToMpInt(gen, mt, ot, ext, mv); default -> throw new AssertionError(); }; - case MpIntJitType mt -> mt; // Other may need up-conversion - default -> throw new AssertionError(); - }; - } - - /** - * Do the same as {@link #forceUniformZExt(JitType, JitType, MethodVisitor)}, but with signed - * values. - * - * @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 mv the method visitor - * @return the new type of the operand - */ - static JitType forceUniformSExt(JitType myType, JitType otherType, MethodVisitor mv) { - JitType myExtType = generateSExt(myType, mv); - return switch (myExtType) { - case IntJitType mt -> switch (otherType.ext()) { // Don't extend other, yet - case IntJitType ot -> mt; - case LongJitType ot -> generateSExtIntToLong(mv); - case MpIntJitType ot -> generateIntToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); - default -> throw new AssertionError(); - }; - case LongJitType mt -> switch (otherType.ext()) { + // 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; - case MpIntJitType ot -> generateLongToMpInt(mt, MpIntJitType.forSize(mt.size()), - mv); + case LongJitType ot -> mt; // Other operand needs up-conversion + case MpIntJitType ot -> generateMpIntToMpInt(gen, mt, ot, ext, mv); default -> throw new AssertionError(); }; - case MpIntJitType mt -> mt; // Other may need up-conversion default -> throw new AssertionError(); }; } 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 0a5c1939b9..4eb302017c 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 @@ -22,6 +22,7 @@ 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.var.JitConstVal; /** @@ -41,7 +42,7 @@ public enum ConstValGen implements ValGen { @Override public JitType generateValReadCode(JitCodeGenerator gen, JitConstVal v, JitTypeBehavior typeReq, - MethodVisitor rv) { + Ext ext, MethodVisitor rv) { JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); switch (type) { case IntJitType t -> rv.visitLdcInsn(v.value().intValue()); 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 9b50afd829..3e9724e682 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 @@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor; 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.var.JitDirectMemoryVar; /** @@ -33,7 +34,7 @@ public enum DirectMemoryVarGen implements MemoryVarGen { @Override public void generateVarWriteCode(JitCodeGenerator gen, JitDirectMemoryVar v, JitType type, - MethodVisitor rv) { + Ext ext, MethodVisitor rv) { 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 59ef446a35..c9f841a575 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 @@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor; 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.var.JitFailVal; /** @@ -34,8 +35,8 @@ public enum FailValGen implements ValGen { } @Override - public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, - JitTypeBehavior typeReq, MethodVisitor rv) { + public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, JitTypeBehavior typeReq, + Ext ext, MethodVisitor rv) { throw new AssertionError(); } } 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 6842fbdbf5..01d4dca5e8 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 @@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor; 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.var.JitInputVar; /** @@ -32,7 +33,7 @@ public enum InputVarGen implements LocalVarGen { GEN; @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, + public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, Ext ext, MethodVisitor rv) { 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 e7fe36c0cb..394cb08ea4 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 @@ -20,6 +20,7 @@ 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.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitLocalOutVar; /** @@ -31,8 +32,8 @@ public enum LocalOutVarGen implements LocalVarGen { @Override public void generateVarWriteCode(JitCodeGenerator gen, JitLocalOutVar v, JitType type, - MethodVisitor rv) { + Ext ext, MethodVisitor rv) { VarHandler handler = gen.getAllocationModel().getHandler(v); - handler.generateStoreCode(gen, type, rv); + handler.generateStoreCode(gen, type, ext, rv); } } 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 b324240632..a2f840ae79 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 @@ -22,6 +22,7 @@ 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.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.var.JitVarnodeVar; /** @@ -41,11 +42,11 @@ public interface LocalVarGen extends VarGen { } @Override - default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, + 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, rv); + handler.generateLoadCode(gen, type, ext, rv); return type; } } 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 80fc3a10f9..24eb73ce3d 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 @@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor; 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.var.JitMemoryOutVar; /** @@ -29,7 +30,7 @@ public enum MemoryOutVarGen implements MemoryVarGen { GEN; @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, + public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, Ext ext, MethodVisitor rv) { VarGen.generateValWriteCodeDirect(gen, v, type, rv); } 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 d28aa4d94a..8f441c5401 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 @@ -21,6 +21,7 @@ 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.gen.JitCodeGenerator; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; import ghidra.pcode.emu.jit.var.JitVarnodeVar; @@ -41,7 +42,7 @@ public interface MemoryVarGen extends VarGen { } @Override - default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, + default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) { return VarGen.generateValReadCodeDirect(gen, v, typeReq, rv); } 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 c5063b2748..26c608f7f6 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 @@ -25,6 +25,7 @@ import org.objectweb.asm.Opcodes; 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.op.JitPhiOp; import ghidra.pcode.emu.jit.var.JitMissingVar; @@ -61,7 +62,7 @@ public enum MissingVarGen implements VarGen { @Override public JitType generateValReadCode(JitCodeGenerator gen, JitMissingVar v, - JitTypeBehavior typeReq, MethodVisitor rv) { + JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) { // [...] rv.visitTypeInsn(NEW, NAME_ASSERTION_ERROR); // [...,error:NEW] @@ -79,7 +80,7 @@ public enum MissingVarGen implements VarGen { } @Override - public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, + public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, Ext ext, MethodVisitor rv) { throw new AssertionError(); } 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 e83ac01118..4c1ed9f6db 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 @@ -24,6 +24,7 @@ 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.type.TypeConversions; +import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext; import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.var.*; import ghidra.program.model.pcode.Varnode; @@ -143,9 +144,10 @@ public interface ValGen { * @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 */ - JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, + JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext, MethodVisitor rv); } 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 678ec85c43..a5be31c50f 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 @@ -30,6 +30,7 @@ import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.op.CBranchOpGen; import ghidra.pcode.emu.jit.gen.op.CallOtherOpGen; 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.var.*; import ghidra.program.model.address.Address; @@ -307,7 +308,8 @@ public interface VarGen extends ValGen { * @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 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 generateVarWriteCode(JitCodeGenerator gen, V v, JitType type, MethodVisitor rv); + void generateVarWriteCode(JitCodeGenerator gen, V v, JitType type, Ext ext, MethodVisitor rv); } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java index b4a6efe62e..a58af8eca9 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/op/JitCallOtherDefOp.java @@ -16,6 +16,7 @@ package ghidra.pcode.emu.jit.op; import java.util.List; +import java.util.Objects; import ghidra.pcode.emu.jit.analysis.JitDataFlowState.MiniDFState; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; @@ -39,6 +40,10 @@ public record JitCallOtherDefOp(PcodeOp op, JitOutVar out, JitTypeBehavior type, PcodeUseropDefinition userop, List args, List inputTypes, MiniDFState dfState) implements JitCallOtherOpIf, JitDefOp { + public JitCallOtherDefOp { + Objects.requireNonNull(type); + } + @Override public boolean canBeRemoved() { return JitCallOtherOpIf.super.canBeRemoved() && JitDefOp.super.canBeRemoved(); diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index a8004c20a8..455547713b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -20,6 +20,7 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.*; +import java.math.BigInteger; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -81,7 +82,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra opdef.posLib = pos; } }, - OUTPUT(OpOutput.class, Varnode.class) { + OUTPUT(OpOutput.class, Varnode.class, int[].class) { @Override int getPos(AnnotatedPcodeUseropDefinition opdef) { return opdef.posOut; @@ -115,11 +116,11 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } private final Class annotCls; - private final Class paramCls; + private final List> allowedClsList; - private ParamAnnotProc(Class annotCls, Class paramCls) { + private ParamAnnotProc(Class annotCls, Class... paramCls) { this.annotCls = annotCls; - this.paramCls = paramCls; + this.allowedClsList = List.of(paramCls); } abstract int getPos(AnnotatedPcodeUseropDefinition opdef); @@ -130,7 +131,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra return p.getAnnotation(annotCls) != null; } - Type getArgumentType(Type opType) { + static Type parameterize(Class paramCls, Type opType) { TypeVariable[] typeParams = paramCls.getTypeParameters(); if (typeParams.length == 0) { return paramCls; @@ -141,6 +142,25 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra throw new AssertionError(); } + String nameAllowedArgumentTypes(Type opType) { + return allowedClsList.stream() + .map(cls -> parameterize(cls, opType).toString()) + .collect(Collectors.joining(",")); + } + + record MatchedClassWithArgs(Class paramCls, Map, Type> typeArgs) { + static MatchedClassWithArgs find(Type paramType, List> allowed) { + for (Class cls : allowed) { + Map, Type> typeArgs = + TypeUtils.getTypeArguments(paramType, cls); + if (typeArgs != null) { + return new MatchedClassWithArgs(cls, typeArgs); + } + } + return null; + } + } + void processParameterPerAnnot(AnnotatedPcodeUseropDefinition opdef, Type declClsOpType, int i, Parameter p) { if (getPos(opdef) != -1) { @@ -148,20 +168,21 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra "Can only have one parameter with @" + annotCls.getSimpleName()); } Type pType = p.getParameterizedType(); - Map, Type> typeArgs = TypeUtils.getTypeArguments(pType, paramCls); - if (typeArgs == null) { + MatchedClassWithArgs match = MatchedClassWithArgs.find(pType, allowedClsList); + if (match == null) { throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + - annotCls.getSimpleName() + " must acccept " + getArgumentType(declClsOpType)); + annotCls.getSimpleName() + " must acccept " + + nameAllowedArgumentTypes(declClsOpType)); } - if (typeArgs.isEmpty()) { + if (match.typeArgs.isEmpty()) { // Nothing } - else if (typeArgs.size() == 1) { - Type declMthOpType = typeArgs.get(paramCls.getTypeParameters()[0]); + else if (match.typeArgs.size() == 1) { + Type declMthOpType = match.typeArgs.get(match.paramCls.getTypeParameters()[0]); if (!Objects.equals(declClsOpType, declMthOpType)) { throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + annotCls.getSimpleName() + " must acccept " + - getArgumentType(declClsOpType)); + nameAllowedArgumentTypes(declClsOpType)); } } else { @@ -324,6 +345,14 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra return canInline; } + @Override + public Class getOutputType() { + if (posOut == -1) { + return method.getReturnType(); + } + return method.getParameterTypes()[posOut]; + } + @Override public Method getJavaMethod() { return method; @@ -421,6 +450,23 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } } + record IntArrayUseropInputParam(int position) implements UseropInputParam { + @Override + public Object convert(Varnode vn, PcodeExecutor executor) { + PcodeExecutorStatePiece state = executor.getState(); + PcodeArithmetic arithmetic = executor.getArithmetic(); + BigInteger value = + arithmetic.toBigInteger(state.getVar(vn, executor.getReason()), Purpose.OTHER); + int[] result = new int[(vn.getSize() + 3) / 4]; + // This is terribly slow + for (int i = 0; i < result.length; i++) { + result[i] = value.intValue(); + value = value.shiftRight(Integer.SIZE); + } + return result; + } + } + record FloatUseropInputParam(int position) implements UseropInputParam { @Override public Object convert(Varnode vn, PcodeExecutor executor) { @@ -482,6 +528,9 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra else if (pType == long.class) { paramsIn.add(new LongUseropInputParam(i)); } + else if (pType == int[].class) { + paramsIn.add(new IntArrayUseropInputParam(i)); + } else if (pType == float.class) { paramsIn.add(new FloatUseropInputParam(i)); } @@ -735,7 +784,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * An annotation to receive the output varnode into a parameter * *

- * The annotated parameter must have type {@link Varnode}. + * The annotated parameter must have type {@link Varnode} (or {@code int[]} for direct + * invocation when the output is a multi-precision integer}). */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index cf299100af..817fa9f6ea 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -21,6 +21,7 @@ import java.util.*; import org.apache.commons.lang3.reflect.TypeUtils; import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput; import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop; import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; import ghidra.program.model.pcode.PcodeOp; @@ -219,6 +220,17 @@ public interface PcodeUseropLibrary { */ boolean canInlinePcode(); + /** + * If this userop is defined as a java callback, get the type of the output + * + *

+ * If the method has a {@code @}{@link OpOutput} annotation, this is the type of the output + * parameter. Otherwise, this is the method's return type. + * + * @return the output type + */ + Class getOutputType(); + /** * If this userop is defined as a java callback, get the method * diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java index 5245b6fb47..051236009e 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java @@ -221,6 +221,11 @@ public class SleighPcodeUseropDefinition implements PcodeUseropDefinition return true; } + @Override + public Class getOutputType() { + return void.class; + } + @Override public Method getJavaMethod() { return null; diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java index db70edd4f8..fe44999160 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/opbehavior/OpBehaviorPopcount.java @@ -4,9 +4,9 @@ * 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. @@ -27,38 +27,12 @@ public class OpBehaviorPopcount extends UnaryOpBehavior { @Override public long evaluateUnary(int sizeout, int sizein, long val) { - val = (val & 0x5555555555555555L) + ((val >>> 1) & 0x5555555555555555L); - val = (val & 0x3333333333333333L) + ((val >>> 2) & 0x3333333333333333L); - val = (val & 0x0f0f0f0f0f0f0f0fL) + ((val >>> 4) & 0x0f0f0f0f0f0f0f0fL); - val = (val & 0x00ff00ff00ff00ffL) + ((val >>> 8) & 0x00ff00ff00ff00ffL); - val = (val & 0x0000ffff0000ffffL) + ((val >>> 16) & 0x0000ffff0000ffffL); - int res = (int) (val & 0xff); - res += (int) ((val >> 32) & 0xff); - return res; + return Long.bitCount(val); } @Override public BigInteger evaluateUnary(int sizeout, int sizein, BigInteger unsignedIn1) { - int bitcount = 0; - while (sizein >= 8) { - bitcount += evaluateUnary(1, 8, unsignedIn1.longValue()); - sizein -= 8; - if (sizein == 0) { - break; - } - unsignedIn1 = unsignedIn1.shiftRight(64); - } - if (sizein > 0) { - long mask = sizein * 8 - 1; - bitcount += evaluateUnary(1, 8, unsignedIn1.longValue() & mask); - } - if (sizeout == 1) { - bitcount &= 0xff; - } - else if (sizeout == 2) { - bitcount &= 0xffff; - } - return BigInteger.valueOf(bitcount); + return BigInteger.valueOf(unsignedIn1.bitCount()); } } diff --git a/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/JitMpIntPerformanceExperiment.java b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/JitMpIntPerformanceExperiment.java new file mode 100644 index 0000000000..f298d0ff63 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/JitMpIntPerformanceExperiment.java @@ -0,0 +1,323 @@ +/* ### + * 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; + +import java.io.File; +import java.lang.invoke.MethodHandles; +import java.math.BigInteger; + +import org.junit.*; + +import generic.test.AbstractGenericTest; +import ghidra.GhidraTestApplicationLayout; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.framework.Application; +import ghidra.framework.ApplicationConfiguration; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.DecodePcodeExecutionException; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.util.DefaultLanguageService; + +@Ignore("For developer workstation") +public class JitMpIntPerformanceExperiment { + public static final int N = 100_000_000; + public static final BigInteger MASK_16_BYTES = + BigInteger.ONE.shiftLeft(128).subtract(BigInteger.ONE); + private static SleighLanguage toy; + + @BeforeClass + public static void setUp() throws Exception { + Application.initializeApplication( + new GhidraTestApplicationLayout(new File(AbstractGenericTest.getTestDirectoryPath())), + new ApplicationConfiguration()); + + toy = (SleighLanguage) DefaultLanguageService.getLanguageService() + .getLanguage(new LanguageID("Toy:BE:64:default")); + Assemblers.getAssembler(toy); + } + + @Test + public void testSpeedBigInteger() { + BigInteger previous = BigInteger.ZERO; + BigInteger current = BigInteger.ONE; + + for (int i = 0; i < N; i++) { + BigInteger next = previous.add(current); + next = next.and(MASK_16_BYTES); + previous = current; + current = next; + } + + System.out.println("fib(%d) = %s".formatted(N, current.toString(16))); + } + + @Test + public void testSpeedIntArray() { + int[] previous = new int[] { 0, 0, 0, 0 }; + int[] current = new int[] { 1, 0, 0, 0 }; + int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + doArrAdd(next, previous, current); + int[] temp = previous; + previous = current; + current = next; + next = temp; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + @Test + public void testSpeedIntArrayFinalLoop() { + final int[] previous = new int[] { 0, 0, 0, 0 }; + final int[] current = new int[] { 1, 0, 0, 0 }; + final int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + doArrAdd(next, previous, current); + for (int j = 0; j < 4; j++) { + previous[j] = current[j]; + current[j] = next[j]; + } + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + @Test + public void testSpeedIntArrayFinalArrayCopy() { + final int[] previous = new int[] { 0, 0, 0, 0 }; + final int[] current = new int[] { 1, 0, 0, 0 }; + final int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + doArrAdd(next, previous, current); + System.arraycopy(current, 0, previous, 0, 4); + System.arraycopy(next, 0, current, 0, 4); + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + private static void doArrAdd(final int[] result, final int[] a, final int[] b) { + long t = 0; + for (int i = 0; i < 4; i++) { + t += Integer.toUnsignedLong(a[i]); + t += Integer.toUnsignedLong(b[i]); + result[i] = (int) t; + t >>>= 32; + } + } + + @Test + public void testSpeedIntArrayInlined() { + int[] previous = new int[] { 0, 0, 0, 0 }; + int[] current = new int[] { 1, 0, 0, 0 }; + int[] next = new int[4]; + + for (int i = 0; i < N; i++) { + long t = 0; + for (int j = 0; j < 4; j++) { + t += Integer.toUnsignedLong(previous[j]); + t += Integer.toUnsignedLong(current[j]); + next[j] = (int) t; + t >>>= 32; + } + + int[] temp = previous; + previous = current; + current = next; + next = temp; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + current[3], current[2], current[1], current[0])); + } + + @Test + public void testSpeedScalarized() { + int prev0 = 0, prev1 = 0, prev2 = 0, prev3 = 0; + int curr0 = 1, curr1 = 0, curr2 = 0, curr3 = 0; + int next0, next1, next2, next3; + + for (int i = 0; i < N; i++) { + long t = 0; + t += Integer.toUnsignedLong(prev0); + t += Integer.toUnsignedLong(curr0); + next0 = (int) t; + t >>>= 32; + t += Integer.toUnsignedLong(prev1); + t += Integer.toUnsignedLong(curr1); + next1 = (int) t; + t >>>= 32; + t += Integer.toUnsignedLong(prev2); + t += Integer.toUnsignedLong(curr2); + next2 = (int) t; + t >>>= 32; + t += Integer.toUnsignedLong(prev3); + t += Integer.toUnsignedLong(curr3); + next3 = (int) t; + + prev0 = curr0; + prev1 = curr1; + prev2 = curr2; + prev3 = curr3; + + curr0 = next0; + curr1 = next1; + curr2 = next2; + curr3 = next3; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + curr3, curr2, curr1, curr0)); + } + + static final long LONG_MASK = 0xffffffffL; + + @Test + public void testSpeedScalarizedAnd() { + int prev0 = 0, prev1 = 0, prev2 = 0, prev3 = 0; + int curr0 = 1, curr1 = 0, curr2 = 0, curr3 = 0; + int next0, next1, next2, next3; + + for (int i = 0; i < N; i++) { + long t = 0; + t += prev0 & LONG_MASK; + t += curr0 & LONG_MASK; + next0 = (int) t; + t >>>= 32; + t += prev1 & LONG_MASK; + t += curr1 & LONG_MASK; + next1 = (int) t; + t >>>= 32; + t += prev2 & LONG_MASK; + t += curr2 & LONG_MASK; + next2 = (int) t; + t >>>= 32; + t += prev3 & LONG_MASK; + t += curr3 & LONG_MASK; + next3 = (int) t; + + prev0 = curr0; + prev1 = curr1; + prev2 = curr2; + prev3 = curr3; + + curr0 = next0; + curr1 = next1; + curr2 = next2; + curr3 = next3; + } + + System.out.println("fib(%d) = %08x%08x%08x%08x".formatted(N, + curr3, curr2, curr1, curr0)); + } + + @Test + public void testSpeedPlainEmu() { + String sleigh = """ + counter:4 = 0; + prev:16 = 0; + curr:16 = 1; + + next:16 = prev + curr; + prev = curr; + curr = next; + counter = counter + 1; + if (counter < 0x%08x) goto ; + r0 = curr(0); + r1 = curr(8); + goto 0xdeadbeef; + """.formatted(N); + + PcodeEmulator emu = new PcodeEmulator(toy); + PcodeThread thread = emu.newThread(); + + Address address = toy.getDefaultSpace().getAddress(0x00400000); + thread.inject(address, sleigh); + thread.overrideCounter(address); + thread.reInitialize(); + + try { + thread.run(); + } + catch (DecodePcodeExecutionException e) { + if (e.getProgramCounter().getOffset() != 0xdeadbeef) { + throw e; + } + } + System.out.println("fib(%d) = %016x%016x".formatted(N, + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r1"), Reason.INSPECT), + Purpose.INSPECT), + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r0"), Reason.INSPECT), + Purpose.INSPECT))); + } + + @Test + public void testSpeedJitEmu() { + String sleigh = """ + counter:4 = 0; + prev:16 = 0; + curr:16 = 1; + + next:16 = prev + curr; + prev = curr; + curr = next; + counter = counter + 1; + if (counter < 0x%08x) goto ; + r0 = curr(0); + r1 = curr(8); + goto 0xdeadbeef; + """.formatted(N); + + JitPcodeEmulator emu = + new JitPcodeEmulator(toy, new JitConfiguration(), MethodHandles.lookup()); + JitPcodeThread thread = emu.newThread(); + + Address address = toy.getDefaultSpace().getAddress(0x00400000); + thread.inject(address, sleigh); + thread.overrideCounter(address); + thread.reInitialize(); + + try { + thread.run(); + } + catch (DecodePcodeExecutionException e) { + if (e.getProgramCounter().getOffset() != 0xdeadbeef) { + throw e; + } + } + System.out.println("fib(%d) = %016x%016x".formatted(N, + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r1"), Reason.INSPECT), + Purpose.INSPECT), + thread.getArithmetic() + .toLong(thread.getState().getVar(toy.getRegister("r0"), Reason.INSPECT), + Purpose.INSPECT))); + } +} 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 942d401777..b371774838 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 @@ -164,7 +164,7 @@ public class Varnode { if (spaceID != varnode.spaceID) { return false; } - if (isConstant() || isUnique() || isHash()) { + if (isConstant() || isHash()) { // this is not really a valid use case return offset == varnode.getOffset(); } 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 index ad88075fbd..cb739a372f 100644 --- 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 @@ -51,7 +51,6 @@ import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.pcode.floatformat.FloatFormat; -import ghidra.pcode.opbehavior.OpBehaviorBoolAnd; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; @@ -78,7 +77,7 @@ public class JitCodeGeneratorTest extends AbstractJitTest { try (FileOutputStream out = new FileOutputStream(tmp)) { out.write(classbytes); } - new ProcessBuilder("javap", "-c", tmp.getPath()).inheritIO().start().waitFor(); + new ProcessBuilder("javap", "-c", "-l", tmp.getPath()).inheritIO().start().waitFor(); } record Translation(PcodeProgram program, MethodNode init, MethodNode run, JitPcodeThread thread, @@ -232,6 +231,22 @@ public class JitCodeGeneratorTest extends AbstractJitTest { 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, @@ -243,6 +258,12 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """, 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 { @@ -267,7 +288,42 @@ public class JitCodeGeneratorTest extends AbstractJitTest { } } - record Case(String name, String init, List evals) {} + 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()); @@ -282,15 +338,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { plainThread.getExecutor().execute(tr.program, plainThread.getUseropLibrary()); assertEquals("Mismatch of PC.", plainThread.getCounter().getOffset(), tr.runClean()); - for (String e : c.evals) { + for (Eval e : c.evals) { PcodeExpression expr = - SleighProgramCompiler.compileExpression(tr.program.getLanguage(), e); - BigInteger expected = plainThread.getArithmetic() + SleighProgramCompiler.compileExpression(tr.program.getLanguage(), e.expr); + BigInteger plnResult = plainThread.getArithmetic() .toBigInteger(expr.evaluate(plainThread.getExecutor()), Purpose.INSPECT); - BigInteger actual = tr.thread.getArithmetic() + BigInteger jitResult = tr.thread.getArithmetic() .toBigInteger(expr.evaluate(tr.thread.getExecutor()), Purpose.INSPECT); - assertEquals("For case '%s': Mismatch of '%s'.".formatted(c.name, e), - expected, actual); + + 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)); } } } @@ -417,18 +481,6 @@ public class JitCodeGeneratorTest extends AbstractJitTest { assertEquals(1.25f, Double.longBitsToDouble(tr.getLongVnVal(temp)), 0); } - @Test - @Ignore("MpInt is TODO") - public void test3LeggedInt() { - TODO(); - } - - @Test - @Ignore("MpInt is TODO") - public void test2LeggedWithResidue() { - TODO(); - } - @Test public void testReadMemMappedRegBE() throws Exception { Translation tr = translateSleigh(ID_TOYBE64, """ @@ -691,6 +743,33 @@ public class JitCodeGeneratorTest extends AbstractJitTest { 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, """ @@ -747,6 +826,33 @@ public class JitCodeGeneratorTest extends AbstractJitTest { 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. * @@ -808,7 +914,8 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( - new Case("only", "", List.of("r0")))); + new Case("only", "", List.of( + ev("r0", "0xbeef"))))); } @Test @@ -819,7 +926,8 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r0 = 0xdead; """), List.of( - new Case("only", "", List.of("r0")))); + new Case("only", "", List.of( + ev("r0", "0xbeef"))))); } @Test @@ -831,8 +939,10 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( - new Case("take", "r1=1;", List.of("r0")), - new Case("fall", "r1=0;", List.of("r0")))); + new Case("take", "r1=1;", List.of( + ev("r0", "0xbeef"))), + new Case("fall", "r1=0;", List.of( + ev("r0", "0xdead"))))); } @Test @@ -843,8 +953,27 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r0 = 0xdead; """), List.of( - new Case("take", "r1=1;", List.of("r0")), - new Case("fall", "r1=0;", List.of("r0")))); + 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 @@ -855,24 +984,37 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( new Case("f", """ - r1 =0; - r7l =0; - """, List.of("r0", "r6")), + r1 = 0; + r7l = 0; + """, + List.of( + ev("r0", "1"), + ev("r6", "1"))), new Case("t", """ - r1 =1; - r7l =1; - """, List.of("r0", "r6")) - /*, new Case("T", """ - r1 =0x400000000; - r7l =4; - """, List.of("r0", "r6"))*/)); + 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"))))); } - /** - * TODO: The last case (commented out) here actually fails because {@link OpBehaviorBoolAnd}, - * from the standard emulator, does bitwise AND instead of boolean AND. I do not know which - * should be corrected. - */ @Test public void testBoolAndOpGen() throws Exception { runEquivalenceTest(translateSleigh(ID_TOYBE64, """ @@ -887,33 +1029,83 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =0; r5l =0; r7l =0; r8 =0; r10l=0; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")) - /*, new Case("tT", """ - r1 =100; r2 =0x400000000; - r4 =100; r5l =4; - r7l =100; r8 =0x400000000; - r10l=100; r11l=4; + 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("r0", "r3", "r6", "r9"))*/)); + 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 @@ -930,33 +1122,83 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =0; r5l =0; r7l =0; r8 =0; r10l=0; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")) - /*, new Case("tT", """ - r1 =100; r2 =0x400000000; - r4 =100; r5l =4; - r7l =100; r8 =0x400000000; - r10l=100; r11l=4; + 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("r0", "r3", "r6", "r9"))*/)); + 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 @@ -973,33 +1215,83 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =0; r5l =0; r7l =0; r8 =0; r10l=0; r11l=0; - """, List.of("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")) - /*, new Case("tT", """ - r1 =100; r2 =0x400000000; - r4 =100; r5l =4; - r7l =100; r8 =0x400000000; - r10l=100; r11l=4; + 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("r0", "r3", "r6", "r9"))*/)); + 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 @@ -1016,11 +1308,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))))); } /** @@ -1047,11 +1345,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", nNaN_D), + ev("r6l", nNaN_F))))); } @Test @@ -1068,11 +1372,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", -0.0d), + ev("r6", -0.0f))))); } @Test @@ -1089,11 +1399,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("p", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot5, f0dot5), List.of("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(dn0dot5, fn0dot5), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))))); } @Test @@ -1122,35 +1438,59 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("+0.25", """ r1 =0x%x; r7l =0x%x; - """.formatted(d0dot25, f0dot25), List.of("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(dn1dot0, fn1dot0), + List.of( + ev("r0", -1.0d), + ev("r6", -1.0f))))); } @Test @@ -1165,7 +1505,10 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1l =0x%x; r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of("r0", "r6")))); + """.formatted(f0dot5, d0dot5), + List.of( + ev("r0", 0.5d), + ev("r6", 0.5f))))); } @Test @@ -1181,7 +1524,10 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1l =1; r7 =2; - """, List.of("r0", "r6")))); + """, + List.of( + ev("r0", 1.0d), + ev("r6", 2.0f))))); } @Test @@ -1204,19 +1550,34 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4l =0x%x; r7 =0x%x; r10l=0x%x; - """.formatted(d1dot0, f1dot0, d1dot0, f1dot0), List.of("r0", "r6")), + """.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("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(dn0dot5, dn0dot5, dn0dot5, fn0dot5), + List.of( + ev("r0", "0"), + ev("r3", "0"), + ev("r6", "0"), + ev("r9", "0"))))); } @Test @@ -1236,11 +1597,17 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("num", """ r1l =0x%x; r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of("r0", "r6")), + """.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("r0", "r6")))); + """.formatted(fNaN, dNaN), + List.of( + ev("r0", "1"), + ev("r6", "1"))))); } @Test @@ -1253,9 +1620,12 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( new Case("num", """ - r1l =0x%x; - r7 =0x%x; - """.formatted(f0dot5, d0dot5), List.of("r0", "r6")))); + r1 =0x%x; + r7l =0x%x; + """.formatted(d0dot5, f0dot5), + List.of( + ev("r0", -0.5d), + ev("r6l", -0.5f))))); } @Test @@ -1273,7 +1643,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 0.75d), + ev("r9", 0.75f))))); } @Test @@ -1291,7 +1663,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 0.25d), + ev("r9", 0.25f))))); } @Test @@ -1309,7 +1683,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 0.125d), + ev("r9", 0.125f))))); } @Test @@ -1327,7 +1703,9 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot5, d0dot25, f0dot5, f0dot25), - List.of("r0", "r9")))); + List.of( + ev("r0", 2.0d), + ev("r9", 2.0f))))); } @Test @@ -1345,17 +1723,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + 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("r0", "r9")), + 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("r0", "r9")))); + List.of( + ev("r0", "0"), + ev("r9", "0"))))); } @Test @@ -1373,17 +1757,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + 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("r0", "r9")), + 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("r0", "r9")))); + List.of( + ev("r0", "1"), + ev("r9", "1"))))); } @Test @@ -1401,17 +1791,23 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + 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("r0", "r9")), + 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("r0", "r9")))); + List.of( + ev("r0", "0"), + ev("r9", "0"))))); } @Test @@ -1429,51 +1825,115 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r1 =0x%x; r2 =0x%x; r10l=0x%x; r11l=0x%x; """.formatted(d0dot25, d0dot5, f0dot25, f0dot5), - List.of("r0", "r9")), + 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("r0", "r9")), + 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("r0", "r9")))); + List.of( + ev("r0", "0"), + ev("r9", "0"))))); } @Test public void testInt2CompOpGen() throws Exception { runEquivalenceTest(translateSleigh(ID_TOYBE64, """ - r0 = -r1; + r0 = -r1; r6l = -r7l; """), List.of( new Case("pos", """ - r1l =4; - r7 =4; - """, List.of("r0", "r6")), + r1 =4; + r7l =4; + """, + List.of( + ev("r0", "-4"), + ev("r6l", "-4"))), new Case("neg", """ - r1l =-4; - r7 =-4; - """, List.of("r0", "r6")))); + 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; + r0 = ~r1; r6l = ~r7l; """), List.of( new Case("pos", """ - r1l =4; - r7 =4; - """, List.of("r0", "r6")), + r1 =4; + r7l =4; + """, + List.of( + ev("r0", "-5"), + ev("r6l", "-5"))), new Case("neg", """ - r1l =-4; - r7 =-4; - """, List.of("r0", "r6")))); + 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 @@ -1484,10 +1944,36 @@ public class JitCodeGeneratorTest extends AbstractJitTest { List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + """, + List.of( + ev("r0", "4"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + """, + 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 @@ -1498,10 +1984,36 @@ public class JitCodeGeneratorTest extends AbstractJitTest { List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + """, + List.of( + ev("r0", "4"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + """, + 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 @@ -1509,14 +2021,48 @@ public class JitCodeGeneratorTest extends AbstractJitTest { // 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; - """, List.of("r0")), + r3 =4; + """, + List.of( + ev("r0", "29"), + ev("r2", "21"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + 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 @@ -1528,10 +2074,37 @@ public class JitCodeGeneratorTest extends AbstractJitTest { List.of( new Case("pos", """ r1l =4; - """, List.of("r0")), + """, + List.of( + ev("r0", "1"))), new Case("neg", """ r1l =-4; - """, List.of("r0")))); + """, + 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 @@ -1544,7 +2117,164 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x%x; r4l=0x12345678; - """.formatted(LONG_CONST), List.of("r0")))); + """.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 @@ -1557,7 +2287,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =2; r2 =2; r10l=2; r11l=2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1570,7 +2324,34 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =2; r2 =2; r10l=2; r11l=2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1583,7 +2364,32 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =2; r2 =2; r10l=2; r11l=2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1596,19 +2402,116 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "2"), + ev("r9", "2"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0x7ffffffffffffffd"), + ev("r9", "0x7ffffffd"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1621,19 +2524,66 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "2"), + ev("r9l", "2"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "-2"), + ev("r9l", "-2"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "-2"), + ev("r9l", "-2"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1646,19 +2596,66 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "5"), + ev("r9l", "5"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1671,19 +2668,66 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("pp", """ r1 =5; r2 =2; r10l=5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("pn", """ r1 =5; r2 =-2; r10l=5; r11l=-2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9l", "1"))), new Case("np", """ r1 =-5; r2 =2; r10l=-5; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "-1"), + ev("r9l", "-1"))), new Case("nn", """ r1 =-5; r2 =-2; r10l=-5; r11l=-2; - """, List.of("r0", "r9")))); + """, + 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 @@ -1696,7 +2740,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x3; r2 =0x5; r10l=0x3; r11l=0x5; - """, List.of("r0", "r9")))); + """, + 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 @@ -1709,7 +2777,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x3; r2 =0x5; r10l=0x3; r11l=0x5; - """, List.of("r0", "r9")))); + """, + 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 @@ -1722,7 +2814,31 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("only", """ r1 =0x3; r2 =0x5; r10l=0x3; r11l=0x5; - """, List.of("r0", "r9")))); + """, + 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 @@ -1735,23 +2851,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + 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 @@ -1764,23 +2930,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + 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 @@ -1793,23 +3009,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + 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 @@ -1822,23 +3088,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + 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 @@ -1851,23 +3167,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + 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 @@ -1880,23 +3246,73 @@ public class JitCodeGeneratorTest extends AbstractJitTest { new Case("lt", """ r1 =1; r2 =2; r10l=1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("slt", """ r1 =-1; r2 =2; r10l=-1; r11l=2; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "1"), + ev("r9", "1"))), new Case("eq", """ r1 =1; r2 =1; r10l=1; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("gt", """ r1 =2; r2 =1; r10l=2; r11l=1; - """, List.of("r0", "r9")), + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("sgt", """ r1 =2; r2 =-1; r10l=2; r11l=-1; - """, List.of("r0", "r9")))); + """, + 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 @@ -1907,30 +3323,86 @@ public class JitCodeGeneratorTest extends AbstractJitTest { """), List.of( new Case("f", """ - r1 =0x1000000000000000; r2 =0x0100000000000000; - r10l=0x10000000; r11l=0x01000000; - """, List.of("r0", "r9")), + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("t", """ - r1 =0x1000000000000000; r2 =0x1000000000000000; - r10l=0x10000000; r11l=0x10000000; - """, List.of("r0", "r9")))); + 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 = carry(r1, r2); - r9l = carry(r10l, r11l); + r0 = scarry(r1, r2); + r9l = scarry(r10l, r11l); """), List.of( new Case("f", """ - r1 =0x1000000000000000; r2 =0x0100000000000000; - r10l=0x10000000; r11l=0x01000000; - """, List.of("r0", "r9")), + r1 =0x8000000000000000; r2 =0x4000000000000000; + r10l=0x80000000; r11l=0x40000000; + """, + List.of( + ev("r0", "0"), + ev("r9", "0"))), new Case("t", """ - r1 =0x0100000000000000; r2 =0x0100000000000000; - r10l=0x01000000; r11l=0x01000000; - """, List.of("r0", "r9")))); + 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 @@ -1940,14 +3412,42 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r9l = sborrow(r10l, r11l); """), List.of( - new Case("f", """ - r1 =0x1000000000000000; r2 =0x0100000000000000; - r10l=0x10000000; r11l=0x01000000; - """, List.of("r0", "r9")), new Case("t", """ - r1 =0x1100000000000000; r2 =0x0100000000000000; - r10l=0x11000000; r11l=0x01000000; - """, List.of("r0", "r9")))); + 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 @@ -1964,31 +3464,116 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =100; r5l =4; r7l =100; r8 =4; r10l=100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")))); + """, + 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 @@ -2005,31 +3590,124 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =100; r5l =4; r7l =100; r8 =4; r10l=100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")))); + """, + 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 @@ -2046,31 +3724,148 @@ public class JitCodeGeneratorTest extends AbstractJitTest { r4 =100; r5l =4; r7l =100; r8 =4; r10l=100; r11l=4; - """, List.of("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")), + """, + 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("r0", "r3", "r6", "r9")))); + """, + 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 diff --git a/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/tgt/JitRuntimeLibraryTest.java b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/tgt/JitRuntimeLibraryTest.java new file mode 100644 index 0000000000..8f10b58460 --- /dev/null +++ b/Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/tgt/JitRuntimeLibraryTest.java @@ -0,0 +1,122 @@ +/* ### + * 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.tgt; + +import static org.junit.Assert.assertEquals; + +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.commons.lang3.ArrayUtils; +import org.junit.Test; + +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.MpDivPrivate; +import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.MpShiftPrivate; + +public class JitRuntimeLibraryTest { + + int[] intsLE(int... legs) { + ArrayUtils.reverse(legs); + return legs; + } + + String mpToString(int[] legs) { + List strs = IntStream.of(legs).mapToObj(i -> "%08x".formatted(i)).toList(); + return strs.reversed().stream().collect(Collectors.joining(":")); + } + + void assertMpEquals(int[] expected, int[] actual) { + assertEquals(mpToString(expected), mpToString(actual)); + } + + int[] out(int size, Consumer func) { + int[] out = new int[size]; + func.accept(out); + return out; + } + + @Test + public void testMpShiftPrivateShl() { + assertMpEquals(intsLE(0x89abcdef, 0xfedcba98, 0x76543210, 0x00000000), + out(4, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 32))); + assertMpEquals(intsLE(0x9abcdeff, 0xedcba987, 0x65432100, 0x00000000), + out(4, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0xedcba987, 0x65432100, 0x00000000), + out(3, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0x00000000, 0x9abcdeff, 0xedcba987, 0x65432100, 0x00000000), + out(5, o -> MpShiftPrivate.shl(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + } + + @Test + public void testMpShiftPrivateUshr() { + assertMpEquals(intsLE(0x00000000, 0x01234567, 0x89abcdef, 0xfedcba98), + out(4, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 32))); + assertMpEquals(intsLE(0x00000000, 0x00123456, 0x789abcde, 0xffedcba9), + out(4, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0x00123456, 0x789abcde, 0xffedcba9), + out(3, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + assertMpEquals(intsLE(0x00000000, 0x00000000, 0x00123456, 0x789abcde, 0xffedcba9), + out(5, o -> MpShiftPrivate.ushr(o, + intsLE(0x01234567, 0x89abcdef, 0xfedcba98, 0x76543210), 36))); + } + + @Test + public void testMpDivPrivateLz() { + assertEquals(0, MpDivPrivate.lz(intsLE(-1))); + assertEquals(1, MpDivPrivate.lz(intsLE(Integer.MAX_VALUE))); + assertEquals(64, MpDivPrivate.lz(intsLE(0, 0, -1))); + } + + @Test + public void testMpDivPrivateShl() { + int[] ints = intsLE(0xffffffff, 0x12345678); + MpDivPrivate.shl(ints, 4); + assertMpEquals(intsLE(0xfffffff1, 0x23456780), ints); + } + + @Test + public void testMpDivPrivateShr() { + int[] ints = intsLE(0x12345678, 0xffffffff); + MpDivPrivate.shr(ints, 4); + assertMpEquals(intsLE(0x01234567, 0x8fffffff), ints); + } + + @Test + public void testMpDivPrivateNeg() { + int[] ints; + + ints = intsLE(0x00000000, 0x00000000); + MpDivPrivate.neg(ints, 2); + assertMpEquals(intsLE(0x00000000, 0x00000000), ints); + + ints = intsLE(0x80000000, 0x00000000); + MpDivPrivate.neg(ints, 2); + assertMpEquals(intsLE(0x80000000, 0x00000000), ints); + + ints = intsLE(0xffffffff, 0xffff0000); + MpDivPrivate.neg(ints, 2); + assertMpEquals(intsLE(0x00000000, 0x00010000), ints); + } +}