GP-5214: Add support for multi-precision integer operations in the JIT-accelerated emulator.

This commit is contained in:
Dan 2025-09-10 16:32:13 +00:00
parent 13ffa3a4a8
commit df90de2367
87 changed files with 5278 additions and 956 deletions

View file

@ -191,6 +191,11 @@ public interface EmuSyscallLibrary<T> extends PcodeUseropLibrary<T> {
return false; return false;
} }
@Override
public Class<?> getOutputType() {
return void.class;
}
@Override @Override
public PcodeUseropLibrary<?> getDefiningLibrary() { public PcodeUseropLibrary<?> getDefiningLibrary() {
return syslib; return syslib;

View file

@ -145,6 +145,11 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
return false; return false;
} }
@Override
public Class<?> getOutputType() {
return void.class;
}
@Override @Override
public Method getJavaMethod() { public Method getJavaMethod() {
return null; return null;

View file

@ -15,14 +15,14 @@
*/ */
package ghidra.pcode.emu.jit.analysis; 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 static org.objectweb.asm.Opcodes.*;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.commons.collections4.iterators.ReverseListIterator;
import org.objectweb.asm.*; import org.objectweb.asm.*;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; 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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; 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;
import ghidra.pcode.emu.jit.var.*; import ghidra.pcode.emu.jit.var.*;
import ghidra.program.model.address.*; import ghidra.program.model.address.*;
@ -258,6 +259,13 @@ public class JitAllocationModel {
generateLoadCode(rv); generateLoadCode(rv);
VarGen.generateValWriteCodeDirect(gen, type, vn, 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 gen the code generator
* @param type the p-code type of the value expected on the JVM stack by the proceeding * @param type the p-code type of the value expected on the JVM stack by the proceeding
* bytecode * 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 * @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. * 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 gen the code generator
* @param type the p-code type of the value produced on the JVM stack by the preceding * @param type the p-code type of the value produced on the JVM stack by the preceding
* bytecode * 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 * @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 @Override
default void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { default void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext,
MethodVisitor rv) {
local().generateLoadCode(rv); local().generateLoadCode(rv);
TypeConversions.generate(gen, this.type(), type, rv); TypeConversions.generate(gen, this.type(), type, ext, rv);
} }
@Override @Override
default void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { default void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext,
TypeConversions.generate(gen, type, this.type(), rv); MethodVisitor rv) {
TypeConversions.generate(gen, type, this.type(), ext, rv);
local().generateStoreCode(rv); local().generateStoreCode(rv);
} }
} }
@ -387,9 +399,9 @@ public class JitAllocationModel {
* shifted to the right to place it into position. * shifted to the right to place it into position.
* *
* @param local the local variable allocated to this part * @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) { private JitType chooseLargerType(JitType t1, JitType t2) {
return t1.size() > t2.size() ? t1 : t2; return t1.size() > t2.size() ? t1 : t2;
} }
@ -405,15 +417,17 @@ public class JitAllocationModel {
* @param gen the code generator * @param gen the code generator
* @param type the p-code type of the value expected on the stack by the proceeding * @param type the p-code type of the value expected on the stack by the proceeding
* bytecode, which may be to load additional parts * 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 * @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 * @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. * 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); local.generateLoadCode(rv);
JitType tempType = chooseLargerType(local.type, type); JitType tempType = chooseLargerType(local.type, type);
TypeConversions.generate(gen, local.type, tempType, rv); TypeConversions.generate(gen, local.type, tempType, ext, rv);
if (shift > 0) { if (shift > 0) {
switch (tempType) { switch (tempType) {
case IntJitType t -> { case IntJitType t -> {
@ -440,7 +454,7 @@ public class JitAllocationModel {
default -> throw new AssertionError(); 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 gen the code generator
* @param type the p-code type of the value expected on the stack by the proceeding * @param type the p-code type of the value expected on the stack by the proceeding
* bytecode, which may be to load additional parts * 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 * @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 * @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. * 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); JitType tempType = chooseLargerType(local.type, type);
TypeConversions.generate(gen, type, tempType, rv); TypeConversions.generate(gen, type, tempType, ext, rv);
switch (tempType) { switch (tempType) {
case IntJitType t -> { case IntJitType t -> {
if (shift > 0) { if (shift > 0) {
@ -483,9 +499,9 @@ public class JitAllocationModel {
rv.visitInsn(LUSHR); 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) { switch (local.type) {
case IntJitType t -> { case IntJitType t -> {
int mask = -1 >>> (Integer.SIZE - Byte.SIZE * type.size()); int mask = -1 >>> (Integer.SIZE - Byte.SIZE * type.size());
@ -524,6 +540,34 @@ public class JitAllocationModel {
} }
} }
public record MultiLocalPart(List<MultiLocalSub> 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 * The handler for a variable allocated in a composition of locals
* *
@ -537,6 +581,7 @@ public class JitAllocationModel {
*/ */
public record MultiLocalVarHandler(List<MultiLocalPart> parts, JitType type) public record MultiLocalVarHandler(List<MultiLocalPart> parts, JitType type)
implements VarHandler { implements VarHandler {
@Override @Override
public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) { public void generateInitCode(JitCodeGenerator gen, MethodVisitor iv) {
// Generator calls local inits directly // Generator calls local inits directly
@ -549,31 +594,23 @@ public class JitAllocationModel {
} }
@Override @Override
public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext,
parts.get(0).generateLoadCode(gen, this.type, rv); MethodVisitor rv) {
for (MultiLocalPart part : parts.subList(1, parts.size())) { for (MultiLocalPart part : parts) {
part.generateLoadCode(gen, this.type, rv); part.generateLoadCode(gen, ext, rv);
switch (this.type) { // TODO: Optimize case where last sub of cur is first sub of next
case IntJitType t -> rv.visitInsn(IOR);
case LongJitType t -> rv.visitInsn(LOR);
default -> throw new AssertionError();
} }
} TypeConversions.generate(gen, this.type, type, ext, rv);
TypeConversions.generate(gen, this.type, type, rv);
} }
@Override @Override
public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext,
TypeConversions.generate(gen, type, this.type, rv); MethodVisitor rv) {
for (MultiLocalPart part : parts.subList(1, parts.size()).reversed()) { TypeConversions.generate(gen, type, this.type, ext, rv);
switch (this.type) { for (MultiLocalPart part : parts.reversed()) {
case IntJitType t -> rv.visitInsn(DUP); part.generateStoreCode(gen, ext, rv);
case LongJitType t -> rv.visitInsn(DUP2); // TODO: Optimize case where last sub of cur is first sub of next
default -> throw new AssertionError();
} }
part.generateStoreCode(gen, this.type, rv);
}
parts.get(0).generateStoreCode(gen, this.type, rv);
} }
} }
@ -599,12 +636,14 @@ public class JitAllocationModel {
} }
@Override @Override
public void generateLoadCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { public void generateLoadCode(JitCodeGenerator gen, JitType type, Ext ext,
MethodVisitor rv) {
throw new AssertionError(); throw new AssertionError();
} }
@Override @Override
public void generateStoreCode(JitCodeGenerator gen, JitType type, MethodVisitor rv) { public void generateStoreCode(JitCodeGenerator gen, JitType type, Ext ext,
MethodVisitor rv) {
throw new AssertionError(); throw new AssertionError();
} }
} }
@ -711,6 +750,15 @@ public class JitAllocationModel {
index()); 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. * Generate a load of this variable onto the JVM stack.
* *
@ -816,9 +864,7 @@ public class JitAllocationModel {
} }
@Override @Override
public void generateDeclCode(MethodVisitor mv, String nameThis, Label startLocals, public void generateInitCode(MethodVisitor mv, String nameThis) {
Label endLocals) {
super.generateDeclCode(mv, nameThis, startLocals, endLocals);
mv.visitLdcInsn(0); mv.visitLdcInsn(0);
mv.visitVarInsn(ISTORE, index()); 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 JitDataFlowModel dfm;
private final JitVarScopeModel vsm; private final JitVarScopeModel vsm;
private final JitTypeModel tm; private final JitTypeModel tm;
@ -871,12 +965,13 @@ public class JitAllocationModel {
private final Map<JitVal, VarHandler> handlers = new HashMap<>(); private final Map<JitVal, VarHandler> handlers = new HashMap<>();
private final Map<Varnode, VarHandler> handlersPerVarnode = new HashMap<>(); private final Map<Varnode, VarHandler> handlersPerVarnode = new HashMap<>();
private final NavigableMap<Address, JvmLocal> locals = new TreeMap<>(); private final NavigableMap<Address, JvmLocal> locals = new TreeMap<>();
private final Deque<JvmTempAlloc> tempAllocs = new LinkedList<>();
/** /**
* Construct the allocation model. * Construct the allocation model.
* *
* @param context the analysis context * @param context the analysis context
* @param dfm the data flow moel * @param dfm the data flow model
* @param vsm the variable scope model * @param vsm the variable scope model
* @param tm the type 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
* *
* <p> * <p>
* This should be used by operator code generators <em>after</em> all the * These indices are reserved only within the scope of the {@code try-with-resources} block
* {@link JitBytesPcodeExecutorState state} bypassing local variables have been allocated. The * creating the allocation. If the {@code primitiveType} is a {@code long} or {@code double},
* variables should be scoped to that operator only, so that the ids used are freed for the next * then the number of actual indices allocated is multiplied by 2, such that the total number of
* operator. * variables is given by {@code count}.
* <p>
* This should be used by operator code generators <em>after</em> 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.
* <p>
* <b>NOTE:</b> 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() { public JvmTempAlloc allocateTemp(MethodVisitor mv, String prefix, Class<?> primitiveType,
return nextLocal; 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 * @param desc the (whole) variable's descriptor
* @return the allocated JVM locals from most to least significant * @return the allocated JVM locals from most to least significant
*/ */
private List<JvmLocal> genFreeLocals(String name, List<SimpleJitType> types, private List<JvmLocal> genFreeLocals(String name, List<? extends SimpleJitType> types,
VarDesc desc) { VarDesc desc) {
JvmLocal[] result = new JvmLocal[types.size()]; JvmLocal[] result = new JvmLocal[types.size()];
Iterable<SimpleJitType> it = language.isBigEndian() Iterable<? extends SimpleJitType> it = language.isBigEndian() ? types : types.reversed();
? types
: () -> new ReverseListIterator<SimpleJitType>(types);
long offset = desc.offset; long offset = desc.offset;
int i = 0; int i = 0;
for (SimpleJitType t : it) { for (SimpleJitType t : it) {
@ -1022,20 +1167,55 @@ public class JitAllocationModel {
* locals. * locals.
*/ */
private VarHandler createComplicatedHandler(Varnode vn) { private VarHandler createComplicatedHandler(Varnode vn) {
Entry<Address, JvmLocal> leftEntry = locals.floorEntry(vn.getAddress()); JitType type = JitTypeBehavior.INTEGER.type(vn.getSize());
assert overlapsLeft(leftEntry.getValue().vn, vn); NavigableMap<Varnode, MultiLocalPart> legs =
Address min = leftEntry.getKey(); new TreeMap<>(Comparator.comparing(Varnode::getAddress));
NavigableMap<Address, JvmLocal> sub = locals.subMap(min, true, maxAddr(vn), true); switch (endian) {
case BIG -> {
List<MultiLocalPart> parts = new ArrayList<>(); Address address = vn.getAddress();
for (JvmLocal local : sub.values()) { for (SimpleJitType legType : type.legTypes()) {
int offset = (int) switch (endian) { Varnode legVn = new Varnode(address, legType.size());
case BIG -> maxAddr(leftEntry.getValue().vn).subtract(maxAddr(vn)); legs.put(legVn, new MultiLocalPart(new ArrayList<>(), legType));
case LITTLE -> vn.getAddress().subtract(leftEntry.getKey()); address = address.add(legType.size());
};
parts.add(new MultiLocalPart(local, offset));
} }
return new MultiLocalVarHandler(parts, JitTypeBehavior.INTEGER.type(vn.getSize())); }
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);
}
}
}
Entry<Address, JvmLocal> firstEntry = locals.floorEntry(vn.getAddress());
assert overlapsLeft(firstEntry.getValue().vn, vn);
Address min = firstEntry.getKey();
NavigableMap<Address, JvmLocal> 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<Varnode, MultiLocalPart> 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<MultiLocalPart> parts = List.copyOf(legs.values());
return new MultiLocalVarHandler(switch (endian) {
case BIG -> parts;
case LITTLE -> parts.reversed();
}, type);
} }
/** /**

View file

@ -79,6 +79,10 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
return endian; 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 <em>varnode</em>. * Remove {@code amt} bytes from the right of the <em>varnode</em>.
* *
@ -94,10 +98,14 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
* @return the resulting value * @return the resulting value
*/ */
public JitVal truncFromRight(Varnode in1Vn, int amt, JitVal in1) { 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); 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 <em>varnode</em>. * Remove {@code amt} bytes from the left of the <em>varnode</em>.
* *
@ -113,7 +121,7 @@ public class JitDataFlowArithmetic implements PcodeArithmetic<JitVal> {
* @return the resulting value * @return the resulting value
*/ */
public JitVal truncFromLeft(Varnode in1Vn, int amt, JitVal in1) { 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); return subpiece(outVn, endian.isBigEndian() ? 0 : amt, in1);
} }

View file

@ -224,30 +224,40 @@ public class JitDataFlowState implements PcodeExecutorState<JitVal> {
*/ */
protected List<JitVal> doGetDefinitions(NavigableMap<Long, JitVal> map, AddressSpace space, protected List<JitVal> doGetDefinitions(NavigableMap<Long, JitVal> map, AddressSpace space,
long offset, int size) { long offset, int size) {
long end = offset + size;
List<JitVal> result = new ArrayList<>(); List<JitVal> result = new ArrayList<>();
Entry<Long, JitVal> preEntry = map.lowerEntry(offset); Entry<Long, JitVal> preEntry = map.lowerEntry(offset);
long cursor = offset; long cursor = offset;
if (preEntry != null) { if (preEntry != null) {
if (endOf(preEntry) > offset) { if (endOf(preEntry) > offset) { // Do I intersect the lower entry?
JitVal preVal = preEntry.getValue(); JitVal preVal = preEntry.getValue();
Varnode preVn = new Varnode(space.getAddress(preEntry.getKey()), preVal.size()); Varnode preVn = new Varnode(space.getAddress(preEntry.getKey()), preVal.size());
int shave = (int) (offset - preEntry.getKey()); int shaveLeft = (int) (offset - preEntry.getKey());
JitVal truncVal = arithmetic.truncFromLeft(preVn, shave, preVal); 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); cursor = endOf(preEntry);
result.add(truncVal); result.add(truncVal);
} }
} }
long end = offset + size; }
for (Entry<Long, JitVal> entry : map.subMap(offset, end).entrySet()) { for (Entry<Long, JitVal> entry : map.subMap(offset, end).entrySet()) {
if (entry.getKey() > cursor) { if (entry.getKey() > cursor) {
result.add(new JitMissingVar( result.add(new JitMissingVar(
new Varnode(space.getAddress(cursor), (int) (entry.getKey() - cursor)))); 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(); JitVal postVal = entry.getValue();
Varnode postVn = new Varnode(space.getAddress(entry.getKey()), postVal.size()); Varnode postVn = new Varnode(space.getAddress(entry.getKey()), postVal.size());
int shave = (int) (endOf(entry) - end); int shave = (int) (endOf(entry) - end);
JitVal truncVal = arithmetic.truncFromRight(postVn, shave, postVal); JitVal truncVal = arithmetic.truncFromRight(postVn, shave, postVal);
// NOTE: No need to check for contained here. Would have been caught above.
cursor = end; cursor = end;
result.add(truncVal); result.add(truncVal);
break; break;

View file

@ -152,17 +152,13 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
* Get the type behavior from the userop's Java method * Get the type behavior from the userop's Java method
* *
* <p> * <p>
* 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 {@link JitTypeBehavior#ANY}.
* *
* @return the type behavior * @return the type behavior
*/ */
private JitTypeBehavior getReturnType() { private JitTypeBehavior getOutputTypeBehavior() {
Method method = decOp.getJavaMethod(); return JitTypeBehavior.forJavaType(getOutputType());
if (method == null) {
return JitTypeBehavior.ANY;
}
return JitTypeBehavior.forJavaType(method.getReturnType());
} }
/** /**
@ -210,8 +206,8 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
} }
else { else {
JitOutVar out = dfm.generateOutVar(outVn); JitOutVar out = dfm.generateOutVar(outVn);
dfm.notifyOp(new JitCallOtherDefOp(op, out, getReturnType(), decOp, inVals, inTypes, dfm.notifyOp(new JitCallOtherDefOp(op, out, getOutputTypeBehavior(), decOp, inVals,
state.captureState())); inTypes, state.captureState()));
state.setVar(outVn, out); state.setVar(outVn, out);
} }
} }
@ -236,6 +232,11 @@ public class JitDataFlowUseropLibrary implements PcodeUseropLibrary<JitVal> {
return decOp.canInlinePcode(); return decOp.canInlinePcode();
} }
@Override
public Class<?> getOutputType() {
return decOp.getOutputType();
}
@Override @Override
public Method getJavaMethod() { public Method getJavaMethod() {
return decOp.getJavaMethod(); return decOp.getJavaMethod();

View file

@ -21,6 +21,8 @@ import java.util.*;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import ghidra.lifecycle.Unfinished;
/** /**
* The p-code type of an operand. * The p-code type of an operand.
* *
@ -124,6 +126,9 @@ public interface JitType {
* @return this type as an int * @return this type as an int
*/ */
SimpleJitType asInt(); SimpleJitType asInt();
@Override
SimpleJitType ext();
} }
/** /**
@ -201,6 +206,11 @@ public interface JitType {
public IntJitType asInt() { public IntJitType asInt() {
return this; return this;
} }
@Override
public List<IntJitType> legTypes() {
return List.of(this);
}
} }
/** /**
@ -278,6 +288,11 @@ public interface JitType {
public LongJitType asInt() { public LongJitType asInt() {
return this; return this;
} }
@Override
public List<LongJitType> legTypes() {
return List.of(this);
}
} }
/** /**
@ -326,6 +341,11 @@ public interface JitType {
public IntJitType asInt() { public IntJitType asInt() {
return IntJitType.I4; return IntJitType.I4;
} }
@Override
public List<FloatJitType> legTypes() {
return List.of(this);
}
} }
/** /**
@ -374,10 +394,20 @@ public interface JitType {
public LongJitType asInt() { public LongJitType asInt() {
return LongJitType.I8; return LongJitType.I8;
} }
@Override
public List<DoubleJitType> legTypes() {
return List.of(this);
}
} }
/** /**
* <b>WIP</b>: The p-code types for integers of size 9 and greater. * The p-code types for integers of size 9 and greater.
*
* <p>
* 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 * @param size the size in bytes
*/ */
@ -432,22 +462,14 @@ public interface JitType {
return size % Integer.BYTES; return size % Integer.BYTES;
} }
/** @Override
* Get the p-code type that describes the part of the variable in each leg public List<IntJitType> legTypes() {
*
* <p>
* 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<SimpleJitType> legTypes() {
IntJitType[] types = new IntJitType[legsAlloc()]; IntJitType[] types = new IntJitType[legsAlloc()];
int i = 0; int i = 0;
if (partialSize() != 0) { if (partialSize() != 0) {
types[i++] = IntJitType.forSize(partialSize()); types[i++] = IntJitType.forSize(partialSize());
} }
for (; i < legsWhole(); i++) { for (; i < types.length; i++) {
types[i] = IntJitType.I4; types[i] = IntJitType.I4;
} }
return Arrays.asList(types); return Arrays.asList(types);
@ -492,6 +514,11 @@ public interface JitType {
public MpFloatJitType ext() { public MpFloatJitType ext() {
return this; return this;
} }
@Override
public List<SimpleJitType> legTypes() {
return Unfinished.TODO("MpFloat");
}
} }
/** /**
@ -526,4 +553,15 @@ public interface JitType {
* @return the extended type * @return the extended type
*/ */
JitType ext(); JitType ext();
/**
* Get the p-code type that describes the part of the variable in each leg
*
* <p>
* 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<? extends SimpleJitType> legTypes();
} }

View file

@ -159,6 +159,9 @@ public enum JitTypeBehavior {
if (cls == long.class) { if (cls == long.class) {
return INTEGER; return INTEGER;
} }
if (cls == int[].class) {
return INTEGER;
}
if (cls == float.class) { if (cls == float.class) {
return FLOAT; return FLOAT;
} }

View file

@ -222,6 +222,7 @@ public class JitTypeModel {
* @param c the number of votes cast * @param c the number of votes cast
*/ */
private void vote(JitTypeBehavior candidate, int c) { private void vote(JitTypeBehavior candidate, int c) {
Objects.requireNonNull(candidate);
if (candidate == JitTypeBehavior.ANY || candidate == JitTypeBehavior.COPY) { if (candidate == JitTypeBehavior.ANY || candidate == JitTypeBehavior.COPY) {
return; return;
} }

View file

@ -128,6 +128,11 @@ public class DecoderUseropLibrary extends AnnotatedPcodeUseropLibrary<Object> {
return rtOp.canInlinePcode(); return rtOp.canInlinePcode();
} }
@Override
public Class<?> getOutputType() {
return rtOp.getOutputType();
}
@Override @Override
public Method getJavaMethod() { public Method getJavaMethod() {
return rtOp.getJavaMethod(); return rtOp.getJavaMethod();

View file

@ -91,7 +91,7 @@ public interface GenConsts {
Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class)); Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(String.class));
public static final String MDESC_INTEGER__BIT_COUNT = public static final String MDESC_INTEGER__BIT_COUNT =
Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE); 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); Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE);
public static final String MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS = public static final String MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS =
Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE); 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 = public static final String MDESC_JIT_COMPILED_PASSAGE__INVOKE_USEROP =
Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PcodeUseropDefinition.class), Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(PcodeUseropDefinition.class),
Type.getType(Varnode.class), Type.getType(Varnode[].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 = public static final String MDESC_JIT_COMPILED_PASSAGE__READ_INTX =
Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(byte[].class), Type.INT_TYPE); Type.getMethodDescriptor(Type.INT_TYPE, Type.getType(byte[].class), Type.INT_TYPE);
public static final String MDESC_JIT_COMPILED_PASSAGE__READ_LONGX = 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); Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE);
public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_LONG_RAW = public static final String MDESC_JIT_COMPILED_PASSAGE__S_CARRY_LONG_RAW =
Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); 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 = public static final String MDESC_JIT_COMPILED_PASSAGE__WRITE_INTX =
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(byte[].class), Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(byte[].class),
Type.INT_TYPE); Type.INT_TYPE);
@ -182,10 +188,23 @@ public interface GenConsts {
Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE); Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.INT_TYPE);
public static final String MDESC_$LONG_BINOP = public static final String MDESC_$LONG_BINOP =
Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); 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 = public static final String MDESC_$SHIFT_JJ =
Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE); Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.LONG_TYPE);
public static final String MDESC_$SHIFT_JI = public static final String MDESC_$SHIFT_JI =
Type.getMethodDescriptor(Type.LONG_TYPE, Type.LONG_TYPE, Type.INT_TYPE); 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 = public static final String MDESC_$SHIFT_IJ =
Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.LONG_TYPE); Type.getMethodDescriptor(Type.INT_TYPE, Type.INT_TYPE, Type.LONG_TYPE);
public static final String MDESC_$SHIFT_II = public static final String MDESC_$SHIFT_II =

View file

@ -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.EntryPoint;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage.ExitSlot;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassageClass; 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.ValGen;
import ghidra.pcode.emu.jit.gen.var.VarGen; import ghidra.pcode.emu.jit.gen.var.VarGen;
import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition;
@ -216,9 +217,6 @@ public class JitCodeGenerator {
private final MethodVisitor initMv; private final MethodVisitor initMv;
private final MethodVisitor runMv; 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 * 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 v the value to read
* @param typeReq the required type of the value * @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 * @return the actual type of the value on the stack
*/ */
public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq) { public JitType generateValReadCode(JitVal v, JitTypeBehavior typeReq, Ext ext) {
return ValGen.lookup(v).generateValReadCode(this, v, typeReq, runMv); return ValGen.lookup(v).generateValReadCode(this, v, typeReq, ext, runMv);
} }
/** /**
@ -611,9 +610,10 @@ public class JitCodeGenerator {
* *
* @param v the variable to write * @param v the variable to write
* @param type the actual type of the value on the stack * @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) { public void generateVarWriteCode(JitVar v, JitType type, Ext ext) {
VarGen.lookup(v).generateVarWriteCode(this, v, type, runMv); VarGen.lookup(v).generateVarWriteCode(this, v, type, ext, runMv);
} }
/** /**
@ -858,20 +858,9 @@ public class JitCodeGenerator {
*/ */
protected void generateRunCode() { protected void generateRunCode() {
runMv.visitCode(); runMv.visitCode();
final Label startLocals = new Label();
runMv.visitLabel(startLocals); 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" * 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); RunFixedLocal.BLOCK_ID.generateLoadCode(runMv);
// [blockId] // [blockId]
@ -929,6 +922,22 @@ public class JitCodeGenerator {
for (ExceptionHandler handler : excHandlers.values()) { for (ExceptionHandler handler : excHandlers.values()) {
handler.generateRunCode(this, runMv); 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(); dest.getParentFile().mkdirs();
try (OutputStream os = new FileOutputStream(dest)) { try (OutputStream os = new FileOutputStream(dest)) {
os.write(bytes); 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 + ")"); Msg.warn(this, "Could not dump class file: " + nameThis + " (" + e + ")");
} }
return bytes; return bytes;
@ -989,7 +999,6 @@ public class JitCodeGenerator {
initMv.visitMaxs(20, 20); initMv.visitMaxs(20, 20);
initMv.visitEnd(); initMv.visitEnd();
runMv.visitLabel(endLocals);
try { try {
runMv.visitMaxs(20, 20); runMv.visitMaxs(20, 20);
} }

View file

@ -15,11 +15,19 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; 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 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
import ghidra.pcode.emu.jit.analysis.JitType.MpIntJitType;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.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; import ghidra.pcode.emu.jit.op.JitBinOp;
/** /**
@ -29,6 +37,134 @@ import ghidra.pcode.emu.jit.op.JitBinOp;
*/ */
public interface BinOpGen<T extends JitBinOp> extends OpGen<T> { public interface BinOpGen<T extends JitBinOp> extends OpGen<T> {
/**
* 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:
*
* <pre>
* void method(int[] out, int[] inL, int[] inR);
* </pre>
*
* <p>
* 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
* <p>
* 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 * Emit code between reading the left and right operands
* *
@ -79,12 +215,12 @@ public interface BinOpGen<T extends JitBinOp> extends OpGen<T> {
*/ */
@Override @Override
default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { 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())); JitType rType = op.rType().resolve(gen.getTypeModel().typeOf(op.r()));
lType = afterLeft(gen, op, lType, rType, rv); 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; assert checkRType == rType;
JitType outType = generateBinOpRunCode(gen, op, block, lType, rType, rv); JitType outType = generateBinOpRunCode(gen, op, block, lType, rType, rv);
gen.generateVarWriteCode(op.out(), outType); gen.generateVarWriteCode(op.out(), outType, Ext.ZERO);
} }
} }

View file

@ -17,14 +17,16 @@ package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitBinOp; import ghidra.pcode.emu.jit.op.JitBinOp;
/** /**
@ -32,7 +34,11 @@ import ghidra.pcode.emu.jit.op.JitBinOp;
* *
* @param <T> the class of p-code op node in the use-def graph * @param <T> the class of p-code op node in the use-def graph
*/ */
public interface BitwiseBinOpGen<T extends JitBinOp> extends BinOpGen<T> { public interface BitwiseBinOpGen<T extends JitBinOp> extends IntBinOpGen<T> {
@Override
default boolean isSigned() {
return false;
}
/** /**
* The JVM opcode to implement this operator with int operands on the stack. * The JVM opcode to implement this operator with int operands on the stack.
@ -49,7 +55,7 @@ public interface BitwiseBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
int longOpcode(); int longOpcode();
/** /**
* <b>WIP</b>: The implementation for multi-precision ints. * The implementation for multi-precision ints.
* *
* @param gen the code generator * @param gen the code generator
* @param type the type of each operand, including the reuslt * @param type the type of each operand, including the reuslt
@ -66,37 +72,25 @@ public interface BitwiseBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
*/ */
// [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg)
int legCount = type.legsAlloc(); int legCount = type.legsAlloc();
int firstIndex = gen.getAllocationModel().nextFreeLocal(); try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) {
Label start = new Label(); OpGen.generateMpLegsIntoTemp(result, legCount, mv);
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)
}
for (int i = 0; i < legCount; i++) { for (int i = 0; i < legCount; i++) {
// [lleg1,...,llegN:INT] // [lleg1,...,llegN:INT]
mv.visitVarInsn(ILOAD, firstIndex + i); mv.visitVarInsn(ILOAD, result.idx(i));
// [lleg1,...,llegN:INT,rlegN:INT] // [lleg1,...,llegN:INT,rlegN:INT]
mv.visitInsn(intOpcode()); mv.visitInsn(intOpcode());
// [lleg1,...,olegN:INT] // [lleg1,...,olegN:INT]
mv.visitVarInsn(ISTORE, firstIndex + i); mv.visitVarInsn(ISTORE, result.idx(i));
// [lleg1,...] // [lleg1,...]
} }
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 @Override
default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformZExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv);
} }
/** /**
@ -110,7 +104,7 @@ public interface BitwiseBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
@Override @Override
default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType,
JitType rType, MethodVisitor rv) { JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitInsn(intOpcode()); case IntJitType t -> rv.visitInsn(intOpcode());
case LongJitType t -> rv.visitInsn(longOpcode()); case LongJitType t -> rv.visitInsn(longOpcode());
@ -118,6 +112,6 @@ public interface BitwiseBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
case MpIntJitType t -> TODO("MpInt of differing sizes"); case MpIntJitType t -> TODO("MpInt of differing sizes");
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
return lType; return rType;
} }
} }

View file

@ -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. * @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, * 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}. * 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<JitBoolAndOp> { public enum BoolAndOpGen implements BitwiseBinOpGen<JitBoolAndOp> {
/** The generator singleton */ /** The generator singleton */

View file

@ -17,7 +17,6 @@ package ghidra.pcode.emu.jit.gen.op;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.*; import ghidra.pcode.emu.jit.analysis.JitType.*;
@ -36,6 +35,11 @@ public enum BoolNegateOpGen implements UnOpGen<JitBoolNegateOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return false;
}
@Override @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitBoolNegateOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitBoolNegateOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {
@ -48,7 +52,11 @@ public enum BoolNegateOpGen implements UnOpGen<JitBoolNegateOp> {
rv.visitLdcInsn(1L); rv.visitLdcInsn(1L);
rv.visitInsn(LXOR); 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(); default -> throw new AssertionError();
} }
return uType; return uType;

View file

@ -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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.op.BranchOpGen.BranchGen; 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;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitBranchIndOp; import ghidra.pcode.emu.jit.op.JitBranchIndOp;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.lang.RegisterValue;
@ -55,9 +56,9 @@ public enum BranchIndOpGen implements OpGen<JitBranchIndOp> {
JitBlock block, MethodVisitor rv) { JitBlock block, MethodVisitor rv) {
gen.generatePassageExit(block, () -> { gen.generatePassageExit(block, () -> {
// [...] // [...]
JitType targetType = gen.generateValReadCode(op.target(), op.targetType()); JitType targetType = gen.generateValReadCode(op.target(), op.targetType(), Ext.ZERO);
// [...,target:?] // [...,target:?]
TypeConversions.generateToLong(targetType, LongJitType.I8, rv); TypeConversions.generateToLong(targetType, LongJitType.I8, Ext.ZERO, rv);
// [...,target:LONG] // [...,target:LONG]
}, ctx, rv); }, ctx, rv);

View file

@ -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.ExtBranchGen;
import ghidra.pcode.emu.jit.gen.op.BranchOpGen.IntBranchGen; 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;
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;
import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition;
import ghidra.pcode.emu.jit.op.JitCBranchOp; import ghidra.pcode.emu.jit.op.JitCBranchOp;
@ -146,7 +147,7 @@ public enum CBranchOpGen implements OpGen<JitCBranchOp> {
return; return;
} }
JitType cType = gen.generateValReadCode(op.cond(), op.condType()); JitType cType = gen.generateValReadCode(op.cond(), op.condType(), Ext.ZERO);
TypeConversions.generateIntToBool(cType, rv); TypeConversions.generateIntToBool(cType, rv);
switch (op.branch()) { switch (op.branch()) {
case RIntBranch ib -> IntCBranchGen.C_INT.generateCode(gen, op, ib, block, rv); case RIntBranch ib -> IntCBranchGen.C_INT.generateCode(gen, op, ib, block, rv);

View file

@ -19,23 +19,31 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; 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 org.objectweb.asm.*;
import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState;
import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp; import ghidra.pcode.emu.jit.JitPassage.DecodedPcodeOp;
import ghidra.pcode.emu.jit.analysis.*; 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.JitAllocationModel.RunFixedLocal;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.*;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator.RetireMode; import ghidra.pcode.emu.jit.gen.JitCodeGenerator.RetireMode;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; 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;
import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition; import ghidra.pcode.emu.jit.gen.var.VarGen.BlockTransition;
import ghidra.pcode.emu.jit.op.JitCallOtherDefOp; import ghidra.pcode.emu.jit.op.JitCallOtherDefOp;
import ghidra.pcode.emu.jit.op.JitCallOtherOpIf; import ghidra.pcode.emu.jit.op.JitCallOtherOpIf;
import ghidra.pcode.emu.jit.var.JitVal; import ghidra.pcode.emu.jit.var.JitVal;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput;
import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition;
import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.PcodeOp;
import ghidra.program.model.pcode.Varnode; import ghidra.program.model.pcode.Varnode;
@ -153,6 +161,31 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
transition.generateInv(rv); transition.generateInv(rv);
} }
static Parameter findOutputParameter(Parameter[] parameters, Method method) {
List<Parameter> 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) * Emit code to implement the Direct strategy (see the class documentation)
* *
@ -175,6 +208,8 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
rv.visitTryCatchBlock(tryStart, tryEnd, rv.visitTryCatchBlock(tryStart, tryEnd,
gen.requestExceptionHandler((DecodedPcodeOp) op.op(), block).label(), NAME_THROWABLE); gen.requestExceptionHandler((DecodedPcodeOp) op.op(), block).label(), NAME_THROWABLE);
JitAllocationModel am = gen.getAllocationModel();
// [] // []
useropField.generateLoadCode(gen, rv); useropField.generateLoadCode(gen, rv);
// [userop] // [userop]
@ -186,17 +221,66 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
rv.visitTypeInsn(CHECKCAST, owningLibName); rv.visitTypeInsn(CHECKCAST, owningLibName);
// [library:OWNING_TYPE] // [library:OWNING_TYPE]
Parameter[] parameters = method.getParameters(); Parameter[] parameters = method.getParameters();
for (int i = 0; i < op.args().size(); i++) { Parameter outputParameter = findOutputParameter(parameters, method);
JitVal arg = op.args().get(i); if (outputParameter != null && method.getReturnType() != void.class) {
Parameter p = parameters[i]; throw new IllegalArgumentException("""
@%s cannot be applied to any parameter of a method returning non-void. \
JitType type = gen.generateValReadCode(arg, JitTypeBehavior.ANY); It's applied to %s of %s""".formatted(
if (p.getType() == boolean.class) { OpOutput.class.getSimpleName(), outputParameter, method.getName()));
TypeConversions.generateIntToBool(type, rv); }
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 { else {
TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), rv); outMpType = MpIntJitType.forSize(defOp.out().size());
rv.visitLdcInsn(outMpType.legsAlloc());
rv.visitIntInsn(NEWARRAY, T_INT);
} }
rv.visitVarInsn(ASTORE, out.idx(0));
}
else {
outMpType = null;
}
int argIdx = 0;
for (int i = 0; i < parameters.length; i++) {
Parameter p = parameters[i];
if (p == outputParameter) {
rv.visitVarInsn(ALOAD, out.idx(0));
continue;
}
JitVal arg = op.args().get(argIdx++);
// TODO: Should this always be zero extension?
JitType type = gen.generateValReadCode(arg, JitTypeBehavior.ANY, Ext.ZERO);
if (p.getType() == boolean.class) {
TypeConversions.generateIntToBool(type, rv);
continue;
}
if (p.getType() == int[].class) {
MpIntJitType mpType = MpIntJitType.forSize(type.size());
// NOTE: Would be nice to have annotation specify signedness
TypeConversions.generate(gen, type, mpType, Ext.ZERO, rv);
int legCount = mpType.legsAlloc();
try (JvmTempAlloc temp = am.allocateTemp(rv, "temp", legCount)) {
OpGen.generateMpLegsIntoTemp(temp, legCount, rv);
OpGen.generateMpLegsIntoArray(temp, legCount, legCount, rv);
}
continue;
}
// Some primitive/simple type
// TODO: Should this always be zero extension? Can annotation specify?
TypeConversions.generate(gen, type, JitType.forJavaType(p.getType()), Ext.ZERO,
rv);
} }
// [library,params...] // [library,params...]
rv.visitLabel(tryStart); rv.visitLabel(tryStart);
@ -204,13 +288,41 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
Type.getMethodDescriptor(method), false); Type.getMethodDescriptor(method), false);
// [return?] // [return?]
rv.visitLabel(tryEnd); rv.visitLabel(tryEnd);
if (op instanceof JitCallOtherDefOp defOp) { if (outputParameter != null) {
gen.generateVarWriteCode(defOp.out(), JitType.forJavaType(method.getReturnType())); 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) { else if (method.getReturnType() != void.class) {
TypeConversions.generatePop(JitType.forJavaType(method.getReturnType()), rv); TypeConversions.generatePop(JitType.forJavaType(method.getReturnType()), rv);
} }
} }
}
static class ResourceGroup implements AutoCloseable {
private final List<AutoCloseable> resources = new ArrayList<>();
@Override
public void close() throws Exception {
for (AutoCloseable r : resources) {
r.close();
}
}
public <T extends AutoCloseable> T add(T resource) {
resources.add(resource);
return resource;
}
}
/** /**
* Check if the Direct invocation strategy is applicable (see class documentation) * Check if the Direct invocation strategy is applicable (see class documentation)

View file

@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatTestOp;
* *
* @param <T> the class of p-code op node in the use-def graph * @param <T> the class of p-code op node in the use-def graph
*/ */
public interface CompareFloatOpGen<T extends JitFloatTestOp> extends BinOpGen<T> { public interface CompareFloatOpGen<T extends JitFloatTestOp> extends FloatBinOpGen<T> {
/** /**
* The JVM opcode to perform the comparison with float operands on the stack. * The JVM opcode to perform the comparison with float operands on the stack.

View file

@ -20,7 +20,7 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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 <T> the class of p-code op node in the use-def graph * @param <T> the class of p-code op node in the use-def graph
*/ */
public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T> { public interface CompareIntBinOpGen<T extends JitIntTestOp> extends IntBinOpGen<T> {
/** /**
* Whether the comparison of p-code integers is signed * {@inheritDoc}
*
* <p> * <p>
* If the comparison is unsigned, we will emit invocations of * If the comparison is unsigned, we will emit invocations of
* {@link Integer#compareUnsigned(int, int)} or {@link Long#compareUnsigned(long, long)}, * {@link Integer#compareUnsigned(int, int)} or {@link Long#compareUnsigned(long, long)},
@ -49,6 +48,7 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
* *
* @return true if signed, false if not * @return true if signed, false if not
*/ */
@Override
boolean isSigned(); boolean isSigned();
/** /**
@ -58,6 +58,11 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
*/ */
int icmpOpcode(); 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 * Emits bytecode for the JVM int case
* *
@ -69,8 +74,7 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
rv.visitJumpInsn(icmpOpcode(), lblTrue); rv.visitJumpInsn(icmpOpcode(), lblTrue);
} }
else { else {
rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", generateIntCmp("compareUnsigned", rv);
MDESC_INTEGER__COMPARE_UNSIGNED, false);
rv.visitJumpInsn(ifOpcode(), lblTrue); rv.visitJumpInsn(ifOpcode(), lblTrue);
} }
} }
@ -94,7 +98,7 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
/** /**
* The JVM opcode to perform the conditional jump for unsigned or long integers. * The JVM opcode to perform the conditional jump for unsigned or long integers.
* * <p>
* This is emitted <em>after</em> the application of {@link #LCMP} or the comparator method. * This is emitted <em>after</em> the application of {@link #LCMP} or the comparator method.
* *
* @return the opcode * @return the opcode
@ -104,7 +108,34 @@ public interface CompareIntBinOpGen<T extends JitIntTestOp> extends BinOpGen<T>
@Override @Override
default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType, default JitType afterLeft(JitCodeGenerator gen, T op, JitType lType, JitType rType,
MethodVisitor rv) { 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<T extends JitIntTestOp> extends BinOpGen<T>
Label lblTrue = new Label(); Label lblTrue = new Label();
Label lblDone = new Label(); Label lblDone = new Label();
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv);
switch (rType) { switch (rType) {
case IntJitType t -> generateIntJump(lblTrue, rv); case IntJitType t -> generateIntJump(lblTrue, rv);
case LongJitType t -> generateLongJump(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(); default -> throw new AssertionError();
} }
JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out()));

View file

@ -34,6 +34,11 @@ public enum CopyOpGen implements UnOpGen<JitCopyOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return false;
}
@Override @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitCopyOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitCopyOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {

View file

@ -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 * This uses the unary operator generator and emits an invocation of {@link Math#abs(float)} or
* {@link Math#abs(double)}, depending on the type. * {@link Math#abs(double)}, depending on the type.
*/ */
public enum FloatAbsOpGen implements UnOpGen<JitFloatAbsOp> { public enum FloatAbsOpGen implements FloatUnOpGen<JitFloatAbsOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 * This uses the binary operator generator and simply emits {@link #FADD} or {@link #DADD} depending
* on the type. * on the type.
*/ */
public enum FloatAddOpGen implements BinOpGen<JitFloatAddOp> { public enum FloatAddOpGen implements FloatBinOpGen<JitFloatAddOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface FloatBinOpGen<T extends JitFloatBinOp> extends BinOpGen<T> {
@Override
default boolean isSigned() {
return false;
}
}

View file

@ -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)}, * This uses the unary operator generator and emits an invocation of {@link Math#ceil(double)},
* possibly surrounding it with conversions from and to float. * possibly surrounding it with conversions from and to float.
*/ */
public enum FloatCeilOpGen implements UnOpGen<JitFloatCeilOp> { public enum FloatCeilOpGen implements FloatUnOpGen<JitFloatCeilOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 * This uses the binary operator generator and simply emits {@link #FDIV} or {@link #DDIV} depending
* on the type. * on the type.
*/ */
public enum FloatDivOpGen implements BinOpGen<JitFloatDivOp> { public enum FloatDivOpGen implements FloatBinOpGen<JitFloatDivOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatFloat2FloatOp;
* <p> * <p>
* This uses the unary operator generator and emits {@link #F2D} or {@link #D2F}. * This uses the unary operator generator and emits {@link #F2D} or {@link #D2F}.
*/ */
public enum FloatFloat2FloatOpGen implements UnOpGen<JitFloatFloat2FloatOp> { public enum FloatFloat2FloatOpGen implements FloatUnOpGen<JitFloatFloat2FloatOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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)}, * This uses the unary operator generator and emits an invocation of {@link Math#floor(double)},
* possibly surrounding it with conversions from and to float. * possibly surrounding it with conversions from and to float.
*/ */
public enum FloatFloorOpGen implements UnOpGen<JitFloatFloorOp> { public enum FloatFloorOpGen implements FloatUnOpGen<JitFloatFloorOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -36,6 +36,11 @@ public enum FloatInt2FloatOpGen implements UnOpGen<JitFloatInt2FloatOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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) { private JitType gen(MethodVisitor rv, int opcode, JitType type) {
rv.visitInsn(opcode); rv.visitInsn(opcode);
return type; return type;

View file

@ -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 * This uses the binary operator generator and simply emits {@link #FMUL} or {@link #DMUL} depending
* on the type. * on the type.
*/ */
public enum FloatMultOpGen implements BinOpGen<JitFloatMultOp> { public enum FloatMultOpGen implements FloatBinOpGen<JitFloatMultOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 * This uses the unary operator generator and emits an invocation of {@link Float#isNaN(float)} or
* {@link Double#isNaN(double)}, depending on the type. * {@link Double#isNaN(double)}, depending on the type.
*/ */
public enum FloatNaNOpGen implements UnOpGen<JitFloatNaNOp> { public enum FloatNaNOpGen implements FloatUnOpGen<JitFloatNaNOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -31,7 +31,7 @@ import ghidra.pcode.emu.jit.op.JitFloatNegOp;
* <p> * <p>
* This uses the unary operator generator and emits {@link #FNEG} or {@link #DNEG}. * This uses the unary operator generator and emits {@link #FNEG} or {@link #DNEG}.
*/ */
public enum FloatNegOpGen implements UnOpGen<JitFloatNegOp> { public enum FloatNegOpGen implements FloatUnOpGen<JitFloatNegOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 * {@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. * to implement that definition, applying type conversions as needed.
*/ */
public enum FloatRoundOpGen implements UnOpGen<JitFloatRoundOp> { public enum FloatRoundOpGen implements FloatUnOpGen<JitFloatRoundOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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)}, * This uses the unary operator generator and emits an invocation of {@link Math#sqrt(double)},
* possibly surrounding it with conversions from and to float. * possibly surrounding it with conversions from and to float.
*/ */
public enum FloatSqrtOpGen implements UnOpGen<JitFloatSqrtOp> { public enum FloatSqrtOpGen implements FloatUnOpGen<JitFloatSqrtOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 * This uses the binary operator generator and simply emits {@link #FSUB} or {@link #DSUB} depending
* on the type. * on the type.
*/ */
public enum FloatSubOpGen implements BinOpGen<JitFloatSubOp> { public enum FloatSubOpGen implements FloatBinOpGen<JitFloatSubOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 * This uses the unary operator generator and emits {@link #F2I}, {@link #F2L}, {@link #D2I}, or
* {@link #D2L}. * {@link #D2L}.
*/ */
public enum FloatTruncOpGen implements UnOpGen<JitFloatTruncOp> { public enum FloatTruncOpGen implements FloatUnOpGen<JitFloatTruncOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface FloatUnOpGen<T extends JitFloatUnOp> extends UnOpGen<T> {
@Override
default boolean isSigned() {
return false;
}
}

View file

@ -15,13 +15,17 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import java.util.List;
import org.objectweb.asm.MethodVisitor; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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.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; 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 * This uses the unary operator generator and emits {@link #INEG} or {@link #LNEG}, depending on
* type. * type.
*/ */
public enum Int2CompOpGen implements UnOpGen<JitInt2CompOp> { public enum Int2CompOpGen implements IntUnOpGen<JitInt2CompOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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<IntJitType> 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 @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitInt2CompOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitInt2CompOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {
switch (uType) { switch (uType) {
case IntJitType t -> rv.visitInsn(INEG); case IntJitType t -> rv.visitInsn(INEG);
case LongJitType t -> rv.visitInsn(LNEG); case LongJitType t -> rv.visitInsn(LNEG);
case MpIntJitType t -> Unfinished.TODO("MpInt"); case MpIntJitType t -> generateMpInt2Comp(gen, t, rv);
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
return uType; return uType;

View file

@ -15,15 +15,15 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO; import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.*;
import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.*; import ghidra.pcode.emu.jit.analysis.JitType.*;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitIntAddOp; import ghidra.pcode.emu.jit.op.JitIntAddOp;
/** /**
@ -36,12 +36,12 @@ import ghidra.pcode.emu.jit.op.JitIntAddOp;
* <p> * <p>
* NOTE: The multi-precision integer parts of this are a work in progress. * NOTE: The multi-precision integer parts of this are a work in progress.
*/ */
public enum IntAddOpGen implements BinOpGen<JitIntAddOp> { public enum IntAddOpGen implements IntBinOpGen<JitIntAddOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
static void generateMpIntLegAdd(JitCodeGenerator gen, int idx, boolean takesCarry, static void generateMpIntLegAdd(JitCodeGenerator gen, int idx, boolean takesCarry,
boolean givesCarry, MethodVisitor mv) { boolean givesCarry, boolean storesResult, MethodVisitor mv) {
if (takesCarry) { if (takesCarry) {
// [...,llegN:INT,olegN+1:LONG] // [...,llegN:INT,olegN+1:LONG]
mv.visitLdcInsn(32); mv.visitLdcInsn(32);
@ -50,32 +50,39 @@ public enum IntAddOpGen implements BinOpGen<JitIntAddOp> {
mv.visitInsn(DUP2_X1); mv.visitInsn(DUP2_X1);
mv.visitInsn(POP2); mv.visitInsn(POP2);
// [...,carryinN:LONG,llegN:INT] // [...,carryinN:LONG,llegN:INT]
TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv);
// [...,carryinN:LONG,llegN:LONG] // [...,carryinN:LONG,llegN:LONG]
mv.visitInsn(LADD); mv.visitInsn(LADD);
// [...,sumpartN:LONG] // [...,sumpartN:LONG]
} }
else { else {
// [...,legN:INT] // [...,llegN:INT]
TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv);
// [...,sumpartN:LONG] (legN + 0) // [...,sumpartN:LONG] (legN + 0)
} }
mv.visitVarInsn(ILOAD, idx); mv.visitVarInsn(ILOAD, idx);
// [...,sumpartN:LONG,rlegN:INT] // [...,sumpartN:LONG,rlegN:INT]
TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv);
// [...,sumpartN:LONG,rlegN:LONG] // [...,sumpartN:LONG,rlegN:LONG]
mv.visitInsn(LADD); mv.visitInsn(LADD);
// [...,olegN:LONG] // [...,olegN:LONG]
if (storesResult) {
if (givesCarry) { if (givesCarry) {
mv.visitInsn(DUP2); mv.visitInsn(DUP2);
} }
// [...,(olegN:LONG),olegN:LONG] // [...,(olegN:LONG),olegN:LONG]
TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv);
// [...,(olegN:LONG),olegN:INT] // [...,(olegN:LONG),olegN:INT]
/** NB. The store will perform the masking */ /** NB. The store will perform the masking */
mv.visitVarInsn(ISTORE, idx); mv.visitVarInsn(ISTORE, idx);
// [...,(olegN:LONG)] // [...,(olegN:LONG)]
} }
else {
if (!givesCarry) {
mv.visitInsn(POP2);
}
}
}
private void generateMpIntAdd(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) { private void generateMpIntAdd(JitCodeGenerator gen, MpIntJitType type, MethodVisitor mv) {
/** /**
@ -88,47 +95,40 @@ public enum IntAddOpGen implements BinOpGen<JitIntAddOp> {
*/ */
// [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg)
int legCount = type.legsAlloc(); int legCount = type.legsAlloc();
int firstIndex = gen.getAllocationModel().nextFreeLocal(); try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) {
Label start = new Label(); OpGen.generateMpLegsIntoTemp(result, legCount, mv);
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] // [lleg1,...,llegN:INT]
for (int i = 0; i < legCount; i++) { for (int i = 0; i < legCount; i++) {
boolean isLast = i == legCount - 1; boolean isLast = i == legCount - 1;
boolean takesCarry = i != 0; // not first boolean takesCarry = i != 0; // not first
generateMpIntLegAdd(gen, firstIndex + i, takesCarry, !isLast, mv); generateMpIntLegAdd(gen, result.idx(i), takesCarry, !isLast, true, mv);
}
OpGen.generateMpLegsFromTemp(result, legCount, mv);
}
} }
// Push it all back, in reverse order @Override
for (int i = 0; i < legCount; i++) { public boolean isSigned() {
mv.visitVarInsn(ILOAD, firstIndex + legCount - i - 1); return false;
}
mv.visitLabel(end);
} }
@Override @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntAddOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntAddOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformZExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntAddOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntAddOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitInsn(IADD); case IntJitType t -> rv.visitInsn(IADD);
case LongJitType t -> rv.visitInsn(LADD); case LongJitType t -> rv.visitInsn(LADD);
case MpIntJitType t when t.size() == lType.size() -> generateMpIntAdd(gen, t, rv); 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(); default -> throw new AssertionError();
} }
return lType; return rType;
} }
} }

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface IntBinOpGen<T extends JitBinOp> extends BinOpGen<T> {
// Intentionally empty
}

View file

@ -18,13 +18,15 @@ package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO; import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitIntCarryOp; import ghidra.pcode.emu.jit.op.JitIntCarryOp;
/** /**
@ -48,7 +50,7 @@ import ghidra.pcode.emu.jit.op.JitIntCarryOp;
* <p> * <p>
* NOTE: The multi-precision integer parts of this are a work in progress. * NOTE: The multi-precision integer parts of this are a work in progress.
*/ */
public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> { public enum IntCarryOpGen implements IntBinOpGen<JitIntCarryOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@ -59,20 +61,16 @@ public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> {
// [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg)
int legCount = type.legsAlloc(); int legCount = type.legsAlloc();
int remSize = type.partialSize(); int remSize = type.partialSize();
int firstIndex = gen.getAllocationModel().nextFreeLocal();
Label start = new Label(); try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", legCount)) {
Label end = new Label();
mv.visitLabel(start);
for (int i = 0; i < legCount; i++) { for (int i = 0; i < legCount; i++) {
mv.visitLocalVariable("temp" + i, Type.getDescriptor(int.class), null, start, end, mv.visitVarInsn(ISTORE, temp.idx(i));
firstIndex + i);
mv.visitVarInsn(ISTORE, firstIndex + i);
// NOTE: More significant legs have higher indices (reverse of stack) // NOTE: More significant legs have higher indices (reverse of stack)
} }
// [lleg1,...,llegN:INT] // [lleg1,...,llegN:INT]
for (int i = 0; i < legCount; i++) { for (int i = 0; i < legCount; i++) {
boolean takesCarry = i != 0; // not first boolean takesCarry = i != 0; // not first
IntAddOpGen.generateMpIntLegAdd(gen, firstIndex + i, takesCarry, true, mv); IntAddOpGen.generateMpIntLegAdd(gen, temp.idx(i), takesCarry, true, false, mv);
} }
// [olegN:LONG] // [olegN:LONG]
if (remSize == 0) { if (remSize == 0) {
@ -84,10 +82,15 @@ public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> {
mv.visitLdcInsn(remSize * Byte.SIZE); mv.visitLdcInsn(remSize * Byte.SIZE);
} }
mv.visitInsn(LUSHR); mv.visitInsn(LUSHR);
TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv);
mv.visitLdcInsn(1); mv.visitLdcInsn(1);
mv.visitInsn(IAND); mv.visitInsn(IAND);
mv.visitLabel(end); }
}
@Override
public boolean isSigned() {
return false;
} }
@Override @Override
@ -102,7 +105,7 @@ public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> {
* On the other hand, if there is room to capture the carry, we can just add the two * 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. * 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) { switch (lType) {
case IntJitType(int size) when size == Integer.BYTES -> rv.visitInsn(DUP); case IntJitType(int size) when size == Integer.BYTES -> rv.visitInsn(DUP);
case IntJitType lt -> { case IntJitType lt -> {
@ -110,7 +113,8 @@ public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> {
case LongJitType(int size) when size == Long.BYTES -> rv.visitInsn(DUP2); case LongJitType(int size) when size == Long.BYTES -> rv.visitInsn(DUP2);
case LongJitType lt -> { case LongJitType lt -> {
} }
case MpIntJitType lt -> TODO("MpInt"); case MpIntJitType lt -> {
}
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
return lType; return lType;
@ -119,7 +123,7 @@ public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> {
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntCarryOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntCarryOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv);
switch (rType) { switch (rType) {
case IntJitType(int size) when size == Integer.BYTES -> { case IntJitType(int size) when size == Integer.BYTES -> {
// [l,l,r] // [l,l,r]
@ -128,7 +132,7 @@ public enum IntCarryOpGen implements BinOpGen<JitIntCarryOp> {
rv.visitInsn(SWAP); // spare an LDC,XOR rv.visitInsn(SWAP); // spare an LDC,XOR
// [sum,l] // [sum,l]
rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "compareUnsigned", 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 // [cmpU(sum,l)] sum < l iff sign bit is 1
rv.visitLdcInsn(31); rv.visitLdcInsn(31);
rv.visitInsn(IUSHR); rv.visitInsn(IUSHR);

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.MethodVisitor; 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 * {@link Integer#divideUnsigned(int, int)} or {@link Long#divideUnsigned(long, long)} depending on
* the type. * the type.
*/ */
public enum IntDivOpGen implements BinOpGen<JitIntDivOp> { public enum IntDivOpGen implements IntBinOpGen<JitIntDivOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntDivOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntDivOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformZExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, ext(), rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntDivOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntDivOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "divideUnsigned", case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "divideUnsigned",
MDESC_$INT_BINOP, false); MDESC_$INT_BINOP, false);
case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "divideUnsigned", case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "divideUnsigned",
MDESC_$LONG_BINOP, false); 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(); default -> throw new AssertionError();
} }
// TODO: For MpInt case, we should use the outvar's size to cull operations. // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations.
return lType; return rType;
} }
} }

View file

@ -29,6 +29,11 @@ public enum IntLeftOpGen implements ShiftIntBinOpGen<JitIntLeftOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return false;
}
@Override @Override
public String methodName() { public String methodName() {
return "intLeft"; return "intLeft";

View file

@ -15,8 +15,6 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.analysis.JitType.*;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitIntMultOp; 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 * This uses the binary operator generator and simply emits {@link #IMUL} or {@link #LMUL} depending
* on the type. * on the type.
*/ */
public enum IntMultOpGen implements BinOpGen<JitIntMultOp> { public enum IntMultOpGen implements IntBinOpGen<JitIntMultOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return false;
}
/**
* Generate the mp-int multiply code.
* <p>
* <b>NOTE:</b> I'd really like to know how many legs of the input operands are actually
* relevant. Very often, the following idiom is used:
*
* <pre>
* temp: 16 = zext(r1) * zext(r2);
* r0 = temp(0);
* </pre>
* <p>
* 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.
* <p>
* <b>IDEA:</b> 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 @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntMultOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntMultOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformZExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntMultOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntMultOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitInsn(IMUL); case IntJitType t -> rv.visitInsn(IMUL);
case LongJitType t -> rv.visitInsn(LMUL); 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(); default -> throw new AssertionError();
} }
// TODO: For MpInt case, we should use the outvar's size to cull operations. // FIXME: For MpInt case, we should use the operands' (relevant) sizes to cull operations.
return lType; return rType;
} }
} }

View file

@ -17,7 +17,7 @@ package ghidra.pcode.emu.jit.gen.op;
import org.objectweb.asm.MethodVisitor; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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 <code>int negate(n) {return ~n;}</code>. It XORs the input with a register of 1s. * compiler for <code>int negate(n) {return ~n;}</code>. It XORs the input with a register of 1s.
* This uses the unary operator generator and emits the equivalent code. * This uses the unary operator generator and emits the equivalent code.
*/ */
public enum IntNegateOpGen implements UnOpGen<JitIntNegateOp> { public enum IntNegateOpGen implements IntUnOpGen<JitIntNegateOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntNegateOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntNegateOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {
switch (uType) { switch (uType) {
case IntJitType t -> { case IntJitType t -> {
rv.visitInsn(ICONST_M1); rv.visitLdcInsn(-1 >>> (Integer.SIZE - t.size() * Byte.SIZE));
rv.visitInsn(IXOR); rv.visitInsn(IXOR);
} }
case LongJitType t -> { case LongJitType t -> {
rv.visitLdcInsn(-1L); rv.visitLdcInsn(-1L >>> (Long.SIZE - t.size() * Byte.SIZE));
rv.visitInsn(LXOR); rv.visitInsn(LXOR);
} }
case MpIntJitType t -> Unfinished.TODO("MpInt"); case MpIntJitType t -> generateMpIntNegate(gen, t, rv);
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
return uType; return uType;

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.MethodVisitor; 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)} * {@link Integer#remainderUnsigned(int, int)} or {@link Long#remainderUnsigned(long, long)}
* depending on the type. * depending on the type.
*/ */
public enum IntRemOpGen implements BinOpGen<JitIntRemOp> { public enum IntRemOpGen implements IntBinOpGen<JitIntRemOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntRemOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntRemOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformZExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, ext(), rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntRemOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntRemOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "remainderUnsigned", case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "remainderUnsigned",
MDESC_$INT_BINOP, false); MDESC_$INT_BINOP, false);
case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "remainderUnsigned", case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "remainderUnsigned",
MDESC_$LONG_BINOP, false); 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(); default -> throw new AssertionError();
} }
// TODO: For MpInt case, we should use the outvar's size to cull operations. // TODO: For MpInt case, we should use the outvar's size to cull operations.
return lType; return rType;
} }
} }

View file

@ -29,6 +29,11 @@ public enum IntRightOpGen implements ShiftIntBinOpGen<JitIntRightOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return false;
}
@Override @Override
public String methodName() { public String methodName() {
return "intRight"; return "intRight";

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.MethodVisitor; 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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitIntSBorrowOp; 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 * {@link JitCompiledPassage#sBorrowLongRaw(long, long)} depending on the type. We must then emit a
* shift and mask to extract the correct bit. * shift and mask to extract the correct bit.
*/ */
public enum IntSBorrowOpGen implements BinOpGen<JitIntSBorrowOp> { public enum IntSBorrowOpGen implements IntBinOpGen<JitIntSBorrowOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return true;
}
@Override @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntSBorrowOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntSBorrowOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformSExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, Ext.SIGN, rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSBorrowOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSBorrowOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformSExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, Ext.SIGN, rv);
switch (rType) { switch (rType) {
case IntJitType(int size) -> { case IntJitType(int size) -> {
rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sBorrowIntRaw", rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sBorrowIntRaw",
@ -74,7 +79,7 @@ public enum IntSBorrowOpGen implements BinOpGen<JitIntSBorrowOp> {
return IntJitType.I1; return IntJitType.I1;
} }
case MpIntJitType t -> { case MpIntJitType t -> {
return TODO("MpInt"); return IntSCarryOpGen.generateMpIntSCarry(gen, t, "sBorrowMpInt", rv);
} }
default -> throw new AssertionError(); default -> throw new AssertionError();
} }

View file

@ -15,11 +15,12 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.MethodVisitor; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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 * {@link JitCompiledPassage#sCarryLongRaw(long, long)} depending on the type. We must then emit a
* shift and mask to extract the correct bit. * shift and mask to extract the correct bit.
*/ */
public enum IntSCarryOpGen implements BinOpGen<JitIntSCarryOp> { public enum IntSCarryOpGen implements IntBinOpGen<JitIntSCarryOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntSCarryOp op, JitType lType, JitType rType, public boolean isSigned() {
MethodVisitor rv) { return true;
return TypeConversions.forceUniformSExt(lType, rType, rv); }
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 @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSCarryOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSCarryOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformSExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, ext(), rv);
switch (rType) { switch (rType) {
case IntJitType(int size) -> { case IntJitType(int size) -> {
rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sCarryIntRaw", rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "sCarryIntRaw",
@ -74,7 +94,7 @@ public enum IntSCarryOpGen implements BinOpGen<JitIntSCarryOp> {
return IntJitType.I1; return IntJitType.I1;
} }
case MpIntJitType t -> { case MpIntJitType t -> {
return TODO("MpInt"); return generateMpIntSCarry(gen, t, "sCarryMpInt", rv);
} }
default -> throw new AssertionError(); default -> throw new AssertionError();
} }

View file

@ -15,8 +15,6 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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 * This uses the binary operator generator and simply emits {@link #IDIV} or {@link #LDIV} depending
* on the type. * on the type.
*/ */
public enum IntSDivOpGen implements BinOpGen<JitIntSDivOp> { public enum IntSDivOpGen implements IntBinOpGen<JitIntSDivOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntSDivOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntSDivOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformSExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, ext(), rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSDivOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSDivOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformSExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitInsn(IDIV); case IntJitType t -> rv.visitInsn(IDIV);
case LongJitType t -> rv.visitInsn(LDIV); 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(); default -> throw new AssertionError();
} }
// TODO: For MpInt case, we should use the outvar's size to cull operations. // TODO: For MpInt case, we should use the outvar's size to cull operations.

View file

@ -17,12 +17,11 @@ package ghidra.pcode.emu.jit.gen.op;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.*; 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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.op.JitIntSExtOp; 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 IntJitType#I4 int4} to {@link LongJitType#I8 int8} is implemented with by emitting only
* {@link #I2L}. * {@link #I2L}.
*/ */
public enum IntSExtOpGen implements UnOpGen<JitIntSExtOp> { public enum IntSExtOpGen implements IntUnOpGen<JitIntSExtOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return true;
}
@Override @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntSExtOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntSExtOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {
JitType outType = op.type().resolve(gen.getTypeModel().typeOf(op.out())); return uType;
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;
} }
} }

View file

@ -17,7 +17,6 @@ package ghidra.pcode.emu.jit.gen.op;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import ghidra.lifecycle.Unfinished;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.*; 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 * This uses the binary operator generator and simply emits {@link #IREM} or {@link #LREM} depending
* on the type. * on the type.
*/ */
public enum IntSRemOpGen implements BinOpGen<JitIntSRemOp> { public enum IntSRemOpGen implements IntBinOpGen<JitIntSRemOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntSRemOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntSRemOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformSExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, ext(), rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSRemOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSRemOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformSExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, rExt(), rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitInsn(IREM); case IntJitType t -> rv.visitInsn(IREM);
case LongJitType t -> rv.visitInsn(LREM); 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(); default -> throw new AssertionError();
} }
// TODO: For MpInt case, we should use the outvar's size to cull operations. // TODO: For MpInt case, we should use the outvar's size to cull operations.

View file

@ -15,12 +15,7 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; 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.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.op.JitIntSRightOp; import ghidra.pcode.emu.jit.op.JitIntSRightOp;
/** /**
@ -35,14 +30,12 @@ public enum IntSRightOpGen implements ShiftIntBinOpGen<JitIntSRightOp> {
GEN; GEN;
@Override @Override
public String methodName() { public boolean isSigned() {
return "intSRight"; return true;
} }
@Override @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntSRightOp op, JitType lType, JitType rType, public String methodName() {
MethodVisitor rv) { return "intSRight";
TypeConversions.generateSExt(lType, rv);
return lType;
} }
} }

View file

@ -17,13 +17,15 @@ package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; import ghidra.pcode.emu.jit.gen.type.TypeConversions;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitIntSubOp; import ghidra.pcode.emu.jit.op.JitIntSubOp;
/** /**
@ -36,10 +38,15 @@ import ghidra.pcode.emu.jit.op.JitIntSubOp;
* <p> * <p>
* NOTE: The multi-precision integer parts of this are a work in progress. * NOTE: The multi-precision integer parts of this are a work in progress.
*/ */
public enum IntSubOpGen implements BinOpGen<JitIntSubOp> { public enum IntSubOpGen implements IntBinOpGen<JitIntSubOp> {
/** The generator singleton */ /** The generator singleton */
GEN; GEN;
@Override
public boolean isSigned() {
return false;
}
private void generateMpIntLegSub(JitCodeGenerator gen, int idx, boolean takesBorrow, private void generateMpIntLegSub(JitCodeGenerator gen, int idx, boolean takesBorrow,
boolean givesBorrow, MethodVisitor mv) { boolean givesBorrow, MethodVisitor mv) {
if (takesBorrow) { if (takesBorrow) {
@ -50,19 +57,19 @@ public enum IntSubOpGen implements BinOpGen<JitIntSubOp> {
mv.visitInsn(DUP2_X1); mv.visitInsn(DUP2_X1);
mv.visitInsn(POP2); mv.visitInsn(POP2);
// [...,borrowinN:LONG,llegN:INT] // [...,borrowinN:LONG,llegN:INT]
mv.visitInsn(I2L); // yes, signed TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv);
// [...,borrowinN:LONG,llegN:LONG] // [...,borrowinN:LONG,llegN:LONG]
mv.visitInsn(LADD); // Yes, add, because borrow is 0 or -1 mv.visitInsn(LADD); // Yes, add, because borrow is 0 or -1
// [...,diffpartN:LONG] // [...,diffpartN:LONG]
} }
else { else {
// [...,legN:INT] // [...,legN:INT]
TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv);
// [...,diffpartN:LONG] (legN + 0) // [...,diffpartN:LONG] (legN + 0)
} }
mv.visitVarInsn(ILOAD, idx); mv.visitVarInsn(ILOAD, idx);
// [...,diffpartN:LONG,rlegN:INT] // [...,diffpartN:LONG,rlegN:INT]
TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, mv); TypeConversions.generateIntToLong(IntJitType.I4, LongJitType.I8, Ext.ZERO, mv);
// [...,diffpartN:LONG,rlegN:LONG] // [...,diffpartN:LONG,rlegN:LONG]
mv.visitInsn(LSUB); mv.visitInsn(LSUB);
// [...,olegN:LONG] // [...,olegN:LONG]
@ -70,7 +77,7 @@ public enum IntSubOpGen implements BinOpGen<JitIntSubOp> {
mv.visitInsn(DUP2); mv.visitInsn(DUP2);
} }
// [...,(olegN:LONG),olegN:LONG] // [...,(olegN:LONG),olegN:LONG]
TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, mv); TypeConversions.generateLongToInt(LongJitType.I8, IntJitType.I4, Ext.ZERO, mv);
// [...,(olegN:LONG),olegN:INT] // [...,(olegN:LONG),olegN:INT]
/** NB. The store will perform the masking */ /** NB. The store will perform the masking */
mv.visitVarInsn(ISTORE, idx); mv.visitVarInsn(ISTORE, idx);
@ -82,46 +89,34 @@ public enum IntSubOpGen implements BinOpGen<JitIntSubOp> {
* The strategy is to allocate a temp local for each leg of the result. First, we'll pop the * 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 * 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 * 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 * 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. * be dropped (overflow). The result legs are then pushed back to the stack.
*/ */
// [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg) // [lleg1,...,llegN,rleg1,rlegN] (N is least-significant leg)
int legCount = type.legsAlloc(); // include partial int legCount = type.legsAlloc(); // include partial
int firstIndex = gen.getAllocationModel().nextFreeLocal(); try (JvmTempAlloc result = gen.getAllocationModel().allocateTemp(mv, "result", legCount)) {
Label start = new Label(); OpGen.generateMpLegsIntoTemp(result, legCount, mv);
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] // [lleg1,...,llegN:INT]
for (int i = 0; i < legCount; i++) { for (int i = 0; i < legCount; i++) {
boolean isLast = i == legCount - 1; boolean isLast = i == legCount - 1;
boolean takesCarry = i != 0; // not first boolean takesCarry = i != 0; // not first
generateMpIntLegSub(gen, firstIndex + i, takesCarry, !isLast, mv); generateMpIntLegSub(gen, result.idx(i), takesCarry, !isLast, 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 @Override
public JitType afterLeft(JitCodeGenerator gen, JitIntSubOp op, JitType lType, JitType rType, public JitType afterLeft(JitCodeGenerator gen, JitIntSubOp op, JitType lType, JitType rType,
MethodVisitor rv) { MethodVisitor rv) {
return TypeConversions.forceUniformZExt(lType, rType, rv); return TypeConversions.forceUniform(gen, lType, rType, Ext.ZERO, rv);
} }
@Override @Override
public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSubOp op, JitBlock block, public JitType generateBinOpRunCode(JitCodeGenerator gen, JitIntSubOp op, JitBlock block,
JitType lType, JitType rType, MethodVisitor rv) { JitType lType, JitType rType, MethodVisitor rv) {
rType = TypeConversions.forceUniformZExt(rType, lType, rv); rType = TypeConversions.forceUniform(gen, rType, lType, Ext.ZERO, rv);
switch (rType) { switch (rType) {
case IntJitType t -> rv.visitInsn(ISUB); case IntJitType t -> rv.visitInsn(ISUB);
case LongJitType t -> rv.visitInsn(LSUB); case LongJitType t -> rv.visitInsn(LSUB);
@ -129,6 +124,6 @@ public enum IntSubOpGen implements BinOpGen<JitIntSubOp> {
case MpIntJitType t -> TODO("MpInt of differing sizes"); case MpIntJitType t -> TODO("MpInt of differing sizes");
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
return lType; return rType;
} }
} }

View file

@ -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 <T> the class of p-code op node in the use-def graph
*/
public interface IntUnOpGen<T extends JitIntUnOp> extends UnOpGen<T> {
// Intentionally empty
}

View file

@ -35,10 +35,22 @@ import ghidra.pcode.emu.jit.op.JitIntZExtOp;
* Note that this implementation is equivalent to {@link CopyOpGen}, except that differences in * Note that this implementation is equivalent to {@link CopyOpGen}, except that differences in
* operand sizes are expected. * operand sizes are expected.
*/ */
public enum IntZExtOpGen implements UnOpGen<JitIntZExtOp> { public enum IntZExtOpGen implements IntUnOpGen<JitIntZExtOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntZExtOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitIntZExtOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {

View file

@ -26,6 +26,7 @@ import ghidra.pcode.emu.jit.analysis.JitType;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.*; 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.pcode.emu.jit.op.JitLoadOp;
import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Endian;
@ -184,9 +185,9 @@ public enum LoadOpGen implements OpGen<JitLoadOp> {
// [...] // [...]
gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv);
// [...,space] // [...,space]
JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType()); JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO);
// [...,space,offset:?INT/LONG] // [...,space,offset:?INT/LONG]
TypeConversions.generateToLong(offsetType, LongJitType.I8, rv); TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv);
// [...,space,offset:LONG] // [...,space,offset:LONG]
rv.visitLdcInsn(op.out().size()); rv.visitLdcInsn(op.out().size());
// [...,space,offset,size] // [...,space,offset,size]
@ -204,7 +205,7 @@ public enum LoadOpGen implements OpGen<JitLoadOp> {
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
// [...,value] // [...,value]
gen.generateVarWriteCode(op.out(), outType); gen.generateVarWriteCode(op.out(), outType, Ext.ZERO);
// [...] // [...]
} }
} }

View file

@ -15,9 +15,10 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.bouncycastle.util.Bytes;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.MethodVisitor;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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 * {@link Integer#numberOfLeadingZeros(int)} or {@link Long#numberOfLeadingZeros(long)}, depending
* on the type. * on the type.
*/ */
public enum LzCountOpGen implements UnOpGen<JitLzCountOp> { public enum LzCountOpGen implements IntUnOpGen<JitLzCountOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitLzCountOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitLzCountOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {
switch (uType) { switch (uType) {
case IntJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, case IntJitType t -> {
"numberOfLeadingZeros", MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false); rv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "numberOfLeadingZeros",
case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, MDESC_INTEGER__NUMBER_OF_LEADING_ZEROS, false);
"numberOfLeadingZeros", MDESC_LONG__NUMBER_OF_LEADING_ZEROS, false); if (t.size() < Integer.BYTES) {
case MpIntJitType t -> TODO("MpInt"); 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(); default -> throw new AssertionError();
} }
return IntJitType.I4; return IntJitType.I4;

View file

@ -15,14 +15,19 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import org.objectweb.asm.MethodVisitor; import java.io.PrintStream;
import org.objectweb.asm.Opcodes; 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.*;
import ghidra.pcode.emu.jit.analysis.JitAllocationModel.JvmTempAlloc;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; 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;
import ghidra.pcode.emu.jit.op.*; import ghidra.pcode.emu.jit.op.*;
import ghidra.pcode.emu.jit.var.*; import ghidra.pcode.emu.jit.var.*;
@ -562,6 +567,148 @@ public interface OpGen<T extends JitOp> extends Opcodes {
}; };
} }
/**
* Emit bytecode to move all legs from the stack into a temporary allocation
* <p>
* 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
* <p>
* 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
* <p>
* 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
* <p>
* 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. * Emit bytecode into the class constructor.
* *
@ -579,9 +726,9 @@ public interface OpGen<T extends JitOp> extends Opcodes {
* This method must emit the code needed to load any input operands, convert them to the * 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 * appropriate type, perform the actual operation, and then if applicable, store the output
* operand. The implementations should delegate to * operand. The implementations should delegate to
* {@link JitCodeGenerator#generateValReadCode(JitVal, JitTypeBehavior)}, * {@link JitCodeGenerator#generateValReadCode(JitVal, JitTypeBehavior, Ext)},
* {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType)}, and {@link TypeConversions} * {@link JitCodeGenerator#generateVarWriteCode(JitVar, JitType, Ext)}, and
* appropriately. * {@link TypeConversions} appropriately.
* *
* @param gen the code generator * @param gen the code generator
* @param op the p-code op (use-def node) to translate * @param op the p-code op (use-def node) to translate

View file

@ -15,7 +15,6 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; package ghidra.pcode.emu.jit.gen.op;
import static ghidra.lifecycle.Unfinished.TODO;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.MethodVisitor; 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)} * This uses the unary operator generator and emits an invocation of {@link Integer#bitCount(int)}
* or {@link Long#bitCount(long)}, depending on the type. * or {@link Long#bitCount(long)}, depending on the type.
*/ */
public enum PopCountOpGen implements UnOpGen<JitPopCountOp> { public enum PopCountOpGen implements IntUnOpGen<JitPopCountOp> {
/** The generator singleton */ /** The generator singleton */
GEN; 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 @Override
public JitType generateUnOpRunCode(JitCodeGenerator gen, JitPopCountOp op, JitBlock block, public JitType generateUnOpRunCode(JitCodeGenerator gen, JitPopCountOp op, JitBlock block,
JitType uType, MethodVisitor rv) { JitType uType, MethodVisitor rv) {
@ -45,7 +64,7 @@ public enum PopCountOpGen implements UnOpGen<JitPopCountOp> {
MDESC_INTEGER__BIT_COUNT, false); MDESC_INTEGER__BIT_COUNT, false);
case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "bitCount", case LongJitType t -> rv.visitMethodInsn(INVOKESTATIC, NAME_LONG, "bitCount",
MDESC_LONG__BIT_COUNT, false); MDESC_LONG__BIT_COUNT, false);
case MpIntJitType t -> TODO("MpInt"); case MpIntJitType t -> generateMpIntPopCount(gen, t, rv);
default -> throw new AssertionError(); default -> throw new AssertionError();
} }
return IntJitType.I4; return IntJitType.I4;

View file

@ -19,12 +19,14 @@ import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import org.objectweb.asm.MethodVisitor; 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.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.*;
import ghidra.pcode.emu.jit.analysis.JitType.LongJitType;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitIntBinOp; import ghidra.pcode.emu.jit.op.JitIntBinOp;
/** /**
@ -37,7 +39,17 @@ import ghidra.pcode.emu.jit.op.JitIntBinOp;
* *
* @param <T> the class of p-code op node in the use-def graph * @param <T> the class of p-code op node in the use-def graph
*/ */
public interface ShiftIntBinOpGen<T extends JitIntBinOp> extends BinOpGen<T> { public interface ShiftIntBinOpGen<T extends JitIntBinOp> extends IntBinOpGen<T> {
/**
* {@inheritDoc}
* <p>
* 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 * The name of the static method in {@link JitCompiledPassage} to invoke
* *
@ -45,6 +57,89 @@ public interface ShiftIntBinOpGen<T extends JitIntBinOp> extends BinOpGen<T> {
*/ */
String methodName(); 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} * {@inheritDoc}
* *
@ -55,20 +150,49 @@ public interface ShiftIntBinOpGen<T extends JitIntBinOp> extends BinOpGen<T> {
@Override @Override
default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType, default JitType generateBinOpRunCode(JitCodeGenerator gen, T op, JitBlock block, JitType lType,
JitType rType, MethodVisitor rv) { JitType rType, MethodVisitor rv) {
String mdesc = switch (lType) { JitAllocationModel am = gen.getAllocationModel();
return switch (lType) {
case IntJitType lt -> switch (rType) { case IntJitType lt -> switch (rType) {
case IntJitType rt -> MDESC_$SHIFT_II; case IntJitType rt -> {
case LongJitType rt -> MDESC_$SHIFT_IJ; 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(); default -> throw new AssertionError();
}; };
case LongJitType lt -> switch (rType) { case LongJitType lt -> switch (rType) {
case IntJitType rt -> MDESC_$SHIFT_JI; case IntJitType rt -> {
case LongJitType rt -> MDESC_$SHIFT_JJ; 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();
}; };
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
rv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, methodName(), mdesc, true);
return lType.ext();
} }
} }

View file

@ -26,6 +26,7 @@ import ghidra.pcode.emu.jit.analysis.JitType;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.*; 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.pcode.emu.jit.op.JitStoreOp;
import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Endian;
@ -184,11 +185,11 @@ public enum StoreOpGen implements OpGen<JitStoreOp> {
// [...] // [...]
gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv); gen.requestFieldForSpaceIndirect(op.space()).generateLoadCode(gen, rv);
// [...,space] // [...,space]
JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType()); JitType offsetType = gen.generateValReadCode(op.offset(), op.offsetType(), Ext.ZERO);
// [...,space,offset:?] // [...,space,offset:?]
TypeConversions.generateToLong(offsetType, LongJitType.I8, rv); TypeConversions.generateToLong(offsetType, LongJitType.I8, Ext.ZERO, rv);
// [...,space,offset:LONG] // [...,space,offset:LONG]
JitType valueType = gen.generateValReadCode(op.value(), op.valueType()); JitType valueType = gen.generateValReadCode(op.value(), op.valueType(), Ext.ZERO);
// [...,space,offset,value] // [...,space,offset,value]
rv.visitLdcInsn(op.value().size()); rv.visitLdcInsn(op.value().size());
// [...,space,offset,value,size] // [...,space,offset,value,size]

View file

@ -15,12 +15,15 @@
*/ */
package ghidra.pcode.emu.jit.gen.op; 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.JitControlFlowModel.JitBlock;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitSubPieceOp; import ghidra.pcode.emu.jit.op.JitSubPieceOp;
/** /**
@ -42,18 +45,23 @@ public enum SubPieceOpGen implements OpGen<JitSubPieceOp> {
GEN; GEN;
/** /**
* <b>WIP</b>: Assumes the previous (next more significant) leg is on the stack and the current * <b>Assumes the next-more-significant leg (i.e., the one from the previous iteration) is on
* (unshifted) leg is in the given variable. Computes the resulting output leg and puts in into * the stack and the current (unshifted) leg is in the given variable. Computes the resulting
* the given local variable, but leaves a copy of the current unshifted leg on the stack. * 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 rv the method visitor
* @param bitShift the number of <em>bits</em> to shift * @param bitShift the number of <em>bits</em> to shift
* @param index the index of the local variable for the current leg * @param index the index of the local variable for the current leg
* @implNote This <em>cannot</em> 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] // [...,prevLegIn]
rv.visitLdcInsn(Integer.SIZE - bitShift); rv.visitLdcInsn(Integer.SIZE - bitShift);
rv.visitInsn(ISHR); rv.visitInsn(ISHL);
// [...,prevLegIn:SLACK] // [...,prevLegIn:SLACK]
rv.visitVarInsn(ILOAD, index); rv.visitVarInsn(ILOAD, index);
// [...,prevLegIn:SLACK,legIn] // [...,prevLegIn:SLACK,legIn]
@ -68,40 +76,22 @@ public enum SubPieceOpGen implements OpGen<JitSubPieceOp> {
// [...,legIn] // [...,legIn]
} }
@Override private static MpIntJitType generateMpIntSubPiece(JitCodeGenerator gen, JitSubPieceOp op,
public void generateRunCode(JitCodeGenerator gen, JitSubPieceOp op, JitBlock block, MpIntJitType type, MethodVisitor mv) {
MethodVisitor rv) {
JitType vType = gen.generateValReadCode(op.u(), op.uType());
JitType outType;
switch (vType) {
case IntJitType vIType -> {
rv.visitLdcInsn(op.offset() * Byte.SIZE);
rv.visitInsn(IUSHR);
outType = 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()); MpIntJitType outMpType = MpIntJitType.forSize(op.out().size());
int outLegCount = outMpType.legsAlloc(); int outLegCount = outMpType.legsAlloc();
int legsLeft = vMpType.legsAlloc(); int legsLeft = type.legsAlloc();
int popCount = op.offset() / Integer.BYTES; int popCount = op.offset() / Integer.BYTES;
int byteShift = op.offset() % Integer.BYTES; int byteShift = op.offset() % Integer.BYTES;
for (int i = 0; i < popCount; i++) { for (int i = 0; i < popCount; i++) {
rv.visitInsn(POP); mv.visitInsn(POP);
legsLeft--;
} }
int firstIndex = gen.getAllocationModel().nextFreeLocal();
Label start = new Label(); JitAllocationModel am = gen.getAllocationModel();
Label end = new Label(); try (JvmTempAlloc subpieces = am.allocateTemp(mv, "subpiece", outLegCount)) {
rv.visitLabel(start);
for (int i = 0; i < outLegCount; i++) { for (int i = 0; i < outLegCount; i++) {
rv.visitLocalVariable("subpiece" + i, Type.getDescriptor(int.class), null, mv.visitVarInsn(ISTORE, subpieces.idx(i));
start, end, firstIndex + i);
rv.visitVarInsn(ISTORE, firstIndex + i);
// NOTE: More significant legs have higher indices (reverse of stack) // NOTE: More significant legs have higher indices (reverse of stack)
legsLeft--; legsLeft--;
} }
@ -110,44 +100,61 @@ public enum SubPieceOpGen implements OpGen<JitSubPieceOp> {
int curLeg = outLegCount - 1; int curLeg = outLegCount - 1;
if (legsLeft > 0) { if (legsLeft > 0) {
// [...,prevLegIn] // [...,prevLegIn]
generateShiftWithPrevLeg(rv, byteShift * Byte.SIZE, firstIndex + curLeg); generateShiftWithPrevLeg(mv, byteShift * Byte.SIZE, subpieces.idx(curLeg));
// [...,legIn] // [...,legIn]
legsLeft--; legsLeft--;
curLeg--; curLeg--;
} }
else { else {
// [...] // [...]
rv.visitVarInsn(ILOAD, firstIndex + curLeg); mv.visitVarInsn(ILOAD, subpieces.idx(curLeg));
// [...,legIn] // [...,legIn]
rv.visitInsn(DUP); mv.visitInsn(DUP);
// [...,legIn,legIn] // [...,legIn,legIn]
rv.visitLdcInsn(byteShift * Byte.SIZE); mv.visitLdcInsn(byteShift * Byte.SIZE);
rv.visitInsn(IUSHR); mv.visitInsn(IUSHR);
// [...,legIn,legOut] // [...,legIn,legOut]
rv.visitVarInsn(ISTORE, firstIndex + curLeg); mv.visitVarInsn(ISTORE, subpieces.idx(curLeg));
// [...,legIn] // [...,legIn]
curLeg--; curLeg--;
} }
while (curLeg >= 0) { while (curLeg >= 0) {
generateShiftWithPrevLeg(rv, byteShift * Byte.SIZE, firstIndex + curLeg); generateShiftWithPrevLeg(mv, byteShift * Byte.SIZE, subpieces.idx(curLeg));
legsLeft--; legsLeft--;
curLeg--; curLeg--;
} }
} }
while (legsLeft > 0) { while (legsLeft > 0) {
rv.visitInsn(POP); mv.visitInsn(POP);
legsLeft--; legsLeft--;
} }
// NOTE: More significant legs have higher indices // NOTE: More significant legs have higher indices
for (int i = outLegCount - 1; i >= 0; i--) { for (int i = outLegCount - 1; i >= 0; i--) {
rv.visitVarInsn(ILOAD, firstIndex + i); mv.visitVarInsn(ILOAD, subpieces.idx(i));
} }
rv.visitLabel(end);
outType = outMpType;
} }
return outMpType;
}
@Override
public void generateRunCode(JitCodeGenerator gen, JitSubPieceOp op, JitBlock block,
MethodVisitor rv) {
JitType vType = gen.generateValReadCode(op.u(), op.uType(), Ext.ZERO);
JitType outType = switch (vType) {
case IntJitType vIType -> {
rv.visitLdcInsn(op.offset() * Byte.SIZE);
rv.visitInsn(IUSHR);
yield vIType;
}
case LongJitType vLType -> {
rv.visitLdcInsn(op.offset() * Byte.SIZE);
rv.visitInsn(LUSHR);
yield vLType;
}
case MpIntJitType vMpType -> generateMpIntSubPiece(gen, op, vMpType, rv);
default -> throw new AssertionError(); default -> throw new AssertionError();
} };
gen.generateVarWriteCode(op.out(), outType); gen.generateVarWriteCode(op.out(), outType, Ext.ZERO);
} }
} }

View file

@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor;
import ghidra.pcode.emu.jit.analysis.JitControlFlowModel.JitBlock; 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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.op.JitUnOp; import ghidra.pcode.emu.jit.op.JitUnOp;
/** /**
@ -29,6 +30,26 @@ import ghidra.pcode.emu.jit.op.JitUnOp;
*/ */
public interface UnOpGen<T extends JitUnOp> extends OpGen<T> { public interface UnOpGen<T extends JitUnOp> extends OpGen<T> {
/**
* Whether this operator is signed
* <p>
* 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 * Emit code for the unary operator
* *
@ -56,8 +77,8 @@ public interface UnOpGen<T extends JitUnOp> extends OpGen<T> {
*/ */
@Override @Override
default void generateRunCode(JitCodeGenerator gen, T op, JitBlock block, MethodVisitor rv) { 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); JitType outType = generateUnOpRunCode(gen, op, block, uType, rv);
gen.generateVarWriteCode(op.out(), outType); gen.generateVarWriteCode(op.out(), outType, ext());
} }
} }

View file

@ -17,6 +17,8 @@ package ghidra.pcode.emu.jit.gen.tgt;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
@ -1058,6 +1060,35 @@ public interface JitCompiledPassage {
return a; 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 <em>most</em>-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. * The implementation of {@link PcodeOp#INT_SCARRY int_scarry} on JVM ints.
* *
@ -1098,6 +1129,199 @@ public interface JitCompiledPassage {
return r; 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 <em>most</em>-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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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. * The implementation of {@link PcodeOp#INT_LEFT int_left} on JVM longs.
* *
@ -1134,6 +1358,30 @@ public interface JitCompiledPassage {
return val << amt; return val << amt;
} }
/**
* The implementation of {@link PcodeOp#INT_LEFT int_left} on a JVM int with an mp-int shift
* amount.
*
* <p>
* 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. * 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; return val << amt;
} }
/**
* The implementation of {@link PcodeOp#INT_RIGHT int_right} on multi-precision ints.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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. * The implementation of {@link PcodeOp#INT_RIGHT int_right} on JVM longs.
* *
@ -1206,6 +1546,30 @@ public interface JitCompiledPassage {
return val >>> amt; return val >>> amt;
} }
/**
* The implementation of {@link PcodeOp#INT_RIGHT int_right} on a JVM int with an mp-int shift
* amount.
*
* <p>
* 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. * 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; return val >>> amt;
} }
/**
* The implementation of {@link PcodeOp#INT_RIGHT int_sright} on multi-precision ints.
*
* <p>
* 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. * The implementation of {@link PcodeOp#INT_SRIGHT int_sright} on JVM longs.
* *
@ -1318,6 +1710,323 @@ public interface JitCompiledPassage {
return val >> amt; return val >> amt;
} }
static final long MASK_I2UL = 0x0000_0000_ffff_ffffL;
/**
* The implementation of {@link PcodeOp#INT_MULT} on mp-ints.
* <p>
* 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<String> 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 <em>easy</em> 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.
* <p>
* 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.
* <p>
* 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 * Get the language for the given string language ID
* *

View file

@ -17,16 +17,17 @@ package ghidra.pcode.emu.jit.gen.type;
import static ghidra.pcode.emu.jit.gen.GenConsts.*; 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.lifecycle.Unfinished;
import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState; import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState;
import ghidra.pcode.emu.jit.analysis.*; 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.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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.op.BinOpGen; 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.pcode.emu.jit.op.JitBinOp;
import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.PcodeOp;
@ -61,6 +62,21 @@ import ghidra.program.model.pcode.PcodeOp;
* </ul> * </ul>
*/ */
public interface TypeConversions extends Opcodes { 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 * Emit an {@link Opcodes#IAND} to reduce the number of bits to those permitted in an int of the
* given size. * given size.
@ -72,13 +88,25 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @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) { if (to.size() < from.size() && to.size() < Integer.BYTES) {
mv.visitLdcInsn(-1 >>> (Integer.SIZE - to.size() * Byte.SIZE)); int shamt = Integer.SIZE - to.size() * Byte.SIZE;
switch (ext) {
case ZERO -> {
mv.visitLdcInsn(-1 >>> shamt);
mv.visitInsn(IAND); 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 from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @return the destination type
*/ */
static IntJitType generateIntToInt(IntJitType from, IntJitType to, MethodVisitor mv) { static IntJitType generateIntToInt(IntJitType from, IntJitType to, Ext ext, MethodVisitor mv) {
checkGenIntMask(from, to, mv); checkGenIntExt(from, to, ext, mv);
return to; return to;
} }
@ -99,12 +128,14 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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); mv.visitInsn(L2I);
checkGenIntMask(from, to, mv); checkGenIntExt(from, to, ext, mv);
return to; return to;
} }
@ -130,10 +161,12 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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()) { if (to.size() == from.size()) {
// We're done. The one leg on the stack becomes the int // We're done. The one leg on the stack becomes the int
return to; return to;
@ -146,7 +179,7 @@ public interface TypeConversions extends Opcodes {
mv.visitInsn(POP); mv.visitInsn(POP);
// [...,legN] // [...,legN]
} }
checkGenIntMask(from, to, mv); checkGenIntExt(from, to, ext, mv);
return to; return to;
} }
@ -158,16 +191,17 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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) { return switch (from) {
case IntJitType iFrom -> generateIntToInt(iFrom, to, mv); // in case of mask case IntJitType iFrom -> generateIntToInt(iFrom, to, ext, mv); // in case of ext
case LongJitType lFrom -> generateLongToInt(lFrom, to, mv); case LongJitType lFrom -> generateLongToInt(lFrom, to, ext, mv);
case FloatJitType fFrom -> generateFloatToInt(fFrom, to, mv); case FloatJitType fFrom -> generateFloatToInt(fFrom, to, mv);
case DoubleJitType dFrom -> throw new AssertionError("Size mismatch"); 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(); default -> throw new AssertionError();
}; };
} }
@ -183,13 +217,25 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @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()) { if (to.size() < from.size()) {
mv.visitLdcInsn(-1L >>> (Long.SIZE - to.size() * Byte.SIZE)); int shamt = Long.SIZE - to.size() * Byte.SIZE;
switch (ext) {
case ZERO -> {
mv.visitLdcInsn(-1L >>> shamt);
mv.visitInsn(LAND); 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 from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @return the destination type
*/ */
static LongJitType generateIntToLong(IntJitType from, LongJitType to, MethodVisitor mv) { static LongJitType generateIntToLong(IntJitType from, LongJitType to, Ext ext,
mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "toUnsignedLong", MethodVisitor mv) {
switch (ext) {
case ZERO -> mv.visitMethodInsn(INVOKESTATIC, NAME_INTEGER, "toUnsignedLong",
MDESC_INTEGER__TO_UNSIGNED_LONG, false); 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. // In theory, never necessary, unless long is used temporarily with size 1-4.
checkGenLongMask(from, to, mv); checkGenLongExt(from, to, ext, mv);
return to; 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 from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @return the destination type
*/ */
static LongJitType generateLongToLong(LongJitType from, LongJitType to, MethodVisitor mv) { static LongJitType generateLongToLong(LongJitType from, LongJitType to, Ext ext,
checkGenLongMask(from, to, mv); MethodVisitor mv) {
checkGenLongExt(from, to, ext, mv);
return to; return to;
} }
@ -246,12 +302,14 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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) { if (from.legsAlloc() == 1) {
generateIntToLong(IntJitType.forSize(from.size()), to, mv); generateIntToLong(IntJitType.forSize(from.size()), to, ext, mv);
return to; return to;
} }
// Remove all but the 2 least-significant legs // Remove all but the 2 least-significant legs
@ -265,7 +323,7 @@ public interface TypeConversions extends Opcodes {
// [...,legN-1,legN] // [...,legN-1,legN]
} }
mv.visitMethodInsn(INVOKESTATIC, NAME_JIT_COMPILED_PASSAGE, "conv2IntToLong", 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; return to;
} }
@ -277,16 +335,17 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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) { return switch (from) {
case IntJitType iFrom -> generateIntToLong(iFrom, to, mv); case IntJitType iFrom -> generateIntToLong(iFrom, to, ext, mv);
case LongJitType lFrom -> generateLongToLong(lFrom, to, mv); // in case of mask case LongJitType lFrom -> generateLongToLong(lFrom, to, ext, mv); // in case of mask
case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); case FloatJitType fFrom -> throw new AssertionError("Size mismatch");
case DoubleJitType dFrom -> generateDoubleToLong(dFrom, to, mv); 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(); default -> throw new AssertionError();
}; };
} }
@ -369,39 +428,63 @@ public interface TypeConversions extends Opcodes {
* *
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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) { if (to.legsAlloc() == 1) {
checkGenIntMask(from, IntJitType.forSize(to.size()), mv); checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv);
return to; return to;
} }
// Insert as many more significant legs as needed // Insert as many more significant legs as needed
for (int i = 1; i < to.legsAlloc(); i++) { // First, figure out what those additional legs should be
mv.visitLdcInsn(0); // [lsl:INT]
mv.visitInsn(SWAP); 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; 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 from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @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) { if (to.legsAlloc() == 1) {
mv.visitInsn(L2I); mv.visitInsn(L2I);
checkGenIntMask(from, IntJitType.forSize(to.size()), mv); checkGenIntExt(from, IntJitType.forSize(to.size()), ext, mv);
return to; return to;
} }
if (from.size() <= Integer.BYTES) { if (from.size() <= Integer.BYTES) {
mv.visitInsn(L2I); mv.visitInsn(L2I);
generateIntToMpInt(IntJitType.forSize(from.size()), to, mv); generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv);
return to; return to;
} }
// Convert, then insert as many more significant legs as needed // Convert, then insert as many more significant legs as needed
@ -413,9 +496,29 @@ public interface TypeConversions extends Opcodes {
mv.visitLdcInsn(Integer.SIZE); mv.visitLdcInsn(Integer.SIZE);
mv.visitInsn(LUSHR); mv.visitInsn(LUSHR);
mv.visitInsn(L2I); mv.visitInsn(L2I);
/** This is the upper leg, which may need masking */
checkGenIntMask(IntJitType.forSize(from.size() - Integer.BYTES), /** This is the upper leg, which may need extending */
IntJitType.forSize(to.partialSize()), mv); if (to.size() < Long.BYTES) {
checkGenIntExt(IntJitType.forSize(from.size() - Integer.BYTES),
IntJitType.forSize(to.size() - Integer.BYTES), ext, mv);
}
int tempCount = switch (ext) {
case ZERO -> 0;
case SIGN -> 1;
};
try (JvmTempAlloc sign = gen.getAllocationModel().allocateTemp(mv, "sign", tempCount)) {
switch (ext) {
case ZERO -> {
}
case SIGN -> {
mv.visitInsn(DUP);
mv.visitLdcInsn(Integer.SIZE - 1);
mv.visitInsn(ISHR);
mv.visitVarInsn(ISTORE, sign.idx(0));
}
}
// [val:LONG,msl:INT] // [val:LONG,msl:INT]
mv.visitInsn(DUP_X2); mv.visitInsn(DUP_X2);
// [msl:INT,val:LONG,msl:INT] // [msl:INT,val:LONG,msl:INT]
@ -426,16 +529,20 @@ public interface TypeConversions extends Opcodes {
// Now add legs // Now add legs
if (to.legsAlloc() > 2) { if (to.legsAlloc() > 2) {
mv.visitLdcInsn(0); switch (ext) {
// [msl:INT,lsl:INT,0] case ZERO -> mv.visitLdcInsn(0);
for (int i = 2; i < to.legsAlloc(); i++) { case SIGN -> mv.visitVarInsn(ILOAD, sign.idx(0));
// [msl:INT,lsl:INT,0]
mv.visitInsn(DUP_X2);
// [0,msl:INT,lsl:INT,0]
} }
// [...,0,msl:INT,lsl:INT,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); mv.visitInsn(POP);
// [...,0,msl:INT,lsl:INT] // [...,sign:INT,msl:INT,lsl:INT]
}
} }
return to; return to;
} }
@ -446,22 +553,23 @@ public interface TypeConversions extends Opcodes {
* @param gen the code generator * @param gen the code generator
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @return the destination type
*/ */
static MpIntJitType generateMpIntToMpInt(JitCodeGenerator gen, MpIntJitType from, static MpIntJitType generateMpIntToMpInt(JitCodeGenerator gen, MpIntJitType from,
MpIntJitType to, MethodVisitor mv) { MpIntJitType to, Ext ext, MethodVisitor mv) {
if (to.size() == from.size()) { if (to.size() == from.size()) {
// Nothing to convert // Nothing to convert
return to; return to;
} }
// Some special cases to avoid use of local variables: // Some special cases to avoid use of local variables:
if (to.legsAlloc() == 1) { if (to.legsAlloc() == 1) {
generateMpIntToInt(from, IntJitType.forSize(to.size()), mv); generateMpIntToInt(from, IntJitType.forSize(to.size()), ext, mv);
return to; return to;
} }
if (from.legsAlloc() == 1) { if (from.legsAlloc() == 1) {
generateIntToMpInt(IntJitType.forSize(from.size()), to, mv); generateIntToMpInt(IntJitType.forSize(from.size()), to, ext, mv);
return to; return to;
} }
@ -469,42 +577,44 @@ public interface TypeConversions extends Opcodes {
int legsIn = from.legsAlloc(); int legsIn = from.legsAlloc();
int legsOut = to.legsAlloc(); int legsOut = to.legsAlloc();
int localsCount = Integer.min(legsIn, legsOut); int localsCount = Integer.min(legsIn, legsOut);
int firstIndex = gen.getAllocationModel().nextFreeLocal();
Label localsStart = new Label(); try (JvmTempAlloc temp = gen.getAllocationModel().allocateTemp(mv, "temp", localsCount)) {
Label localsEnd = new Label();
mv.visitLabel(localsStart);
for (int i = 0; i < localsCount; i++) { for (int i = 0; i < localsCount; i++) {
mv.visitLocalVariable("temp" + i, Type.getDescriptor(int.class), null, localsStart, mv.visitVarInsn(ISTORE, temp.idx(i));
localsEnd, firstIndex + i);
mv.visitVarInsn(ISTORE, firstIndex + i);
} }
// Add or remove legs // Add or remove legs
int toAdd = legsOut - legsIn; int toAdd = legsOut - legsIn;
for (int i = 0; i < toAdd; i++) { if (toAdd >= 1) {
mv.visitLdcInsn(0); switch (ext) {
case ZERO -> mv.visitLdcInsn(0);
case SIGN -> {
mv.visitVarInsn(ILOAD, temp.idx(localsCount - 1));
mv.visitLdcInsn(Integer.SIZE - 1);
mv.visitInsn(ISHR);
} }
int toRemove = legsIn - legsOut; }
}
for (int i = 1; i < toAdd; i++) {
mv.visitInsn(DUP);
}
int toRemove = -toAdd;
for (int i = 0; i < toRemove; i++) { for (int i = 0; i < toRemove; i++) {
mv.visitInsn(POP); mv.visitInsn(POP);
} }
// Start pushing them back, but the most significant may need masking // Start pushing them back, but the most significant may need extending
int idx = firstIndex + localsCount; mv.visitVarInsn(ILOAD, temp.idx(localsCount - 1));
idx--;
mv.visitVarInsn(ILOAD, idx);
if (to.size() < from.size()) { if (to.size() < from.size()) {
checkGenIntMask( checkGenIntExt(
from, // already checked size, so anything greater from, // already checked size, so anything greater
IntJitType.forSize(to.partialSize()), mv); IntJitType.forSize(to.partialSize()), ext, mv);
} }
// push the rest back // push the rest back
for (int i = 0; i < localsCount; i++) { for (int i = 1; i < localsCount; i++) {
idx--; mv.visitVarInsn(ILOAD, temp.idx(localsCount - i - 1));
mv.visitVarInsn(ILOAD, idx); }
} }
mv.visitLabel(localsEnd);
return to; return to;
} }
@ -518,17 +628,18 @@ public interface TypeConversions extends Opcodes {
* @param gen the code generator * @param gen the code generator
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the destination type * @return the destination type
*/ */
static MpIntJitType generateToMpInt(JitCodeGenerator gen, JitType from, MpIntJitType to, static MpIntJitType generateToMpInt(JitCodeGenerator gen, JitType from, MpIntJitType to,
MethodVisitor mv) { Ext ext, MethodVisitor mv) {
return switch (from) { return switch (from) {
case IntJitType iFrom -> generateIntToMpInt(iFrom, to, mv); case IntJitType iFrom -> generateIntToMpInt(iFrom, to, ext, mv);
case LongJitType lFrom -> generateLongToMpInt(lFrom, to, mv); case LongJitType lFrom -> generateLongToMpInt(gen, lFrom, to, ext, mv);
case FloatJitType fFrom -> throw new AssertionError("Size mismatch"); case FloatJitType fFrom -> throw new AssertionError("Size mismatch");
case DoubleJitType dFrom -> 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(); default -> throw new AssertionError();
}; };
} }
@ -543,16 +654,18 @@ public interface TypeConversions extends Opcodes {
* @param gen the code generator * @param gen the code generator
* @param from the source type * @param from the source type
* @param to the destination type * @param to the destination type
* @param ext whether the extension is signed or not
* @param mv the method visitor * @param mv the method visitor
* @return the resulting (destination) type * @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) { return switch (to) {
case IntJitType iTo -> generateToInt(from, iTo, mv); case IntJitType iTo -> generateToInt(from, iTo, ext, mv);
case LongJitType lTo -> generateToLong(from, lTo, mv); case LongJitType lTo -> generateToLong(from, lTo, ext, mv);
case FloatJitType fTo -> generateToFloat(from, fTo, mv); case FloatJitType fTo -> generateToFloat(from, fTo, mv);
case DoubleJitType dTo -> generateToDouble(from, dTo, 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(); 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. * Emit code to extend a signed value of the given type to fill its host JVM type.
* *
* <p>
* This is implemented in the same manner as {@link IntSExtOpGen int_sext}.
*
* @param type the p-code type * @param type the p-code type
* @param mv the method visitor * @param mv the method visitor
* @return the p-code type that exactly fits the host JVM type, i.e., the resulting p-code type. * @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(); return type.ext();
} }
/**
* Convert a signed {@link IntJitType#I4 int4} to {@link LongJitType#I8 int8}.
*
* <p>
* 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 * 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. * 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 * generateBinOpRunCode} if we're using it. The two resulting types should now be equal, and we
* can examine them and emit the correct bytecodes. * 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 myType the type of an operand, probably in a binary operator
* @param otherType the type of the other operand of 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 * @param mv the method visitor
* @return the new type of the operand * @return the new type of the operand
*/ */
static JitType forceUniformZExt(JitType myType, JitType otherType, MethodVisitor mv) { static JitType forceUniform(JitCodeGenerator gen, JitType myType, JitType otherType,
return switch (myType.ext()) { Ext ext, MethodVisitor mv) {
case IntJitType mt -> switch (otherType.ext()) { // TODO: Why was .ext() being used here (inconsistently, too)
return switch (myType) {
case IntJitType mt -> switch (otherType) {
case IntJitType ot -> mt; case IntJitType ot -> mt;
case LongJitType ot -> generateIntToLong(mt, ot, mv); case LongJitType ot -> generateIntToLong(mt, ot, ext, mv);
case MpIntJitType ot -> generateIntToMpInt(mt, MpIntJitType.forSize(mt.size()), // FIXME: Would be nice to allow non-uniform mp-int sizes
mv); case MpIntJitType ot -> generateIntToMpInt(mt, ot, ext, mv);
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
case LongJitType mt -> switch (otherType) { case LongJitType mt -> switch (otherType) {
case IntJitType ot -> mt; // Other operand needs up-conversion case IntJitType ot -> mt; // Other operand needs up-conversion
case LongJitType ot -> mt; case LongJitType ot -> mt;
case MpIntJitType ot -> generateLongToMpInt(mt, MpIntJitType.forSize(mt.size()), // FIXME: Would be nice to allow non-uniform mp-int sizes
mv); case MpIntJitType ot -> generateLongToMpInt(gen, mt, ot, ext, mv);
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
case MpIntJitType mt -> mt; // Other may need up-conversion // FIXME: Would be nice to allow non-uniform mp-int sizes
default -> throw new AssertionError(); case MpIntJitType mt -> switch (otherType) {
};
}
/**
* 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()) {
case IntJitType ot -> mt; // Other operand needs up-conversion case IntJitType ot -> mt; // Other operand needs up-conversion
case LongJitType ot -> mt; case LongJitType ot -> mt; // Other operand needs up-conversion
case MpIntJitType ot -> generateLongToMpInt(mt, MpIntJitType.forSize(mt.size()), case MpIntJitType ot -> generateMpIntToMpInt(gen, mt, ot, ext, mv);
mv);
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
case MpIntJitType mt -> mt; // Other may need up-conversion
default -> throw new AssertionError(); default -> throw new AssertionError();
}; };
} }

View file

@ -22,6 +22,7 @@ import ghidra.pcode.emu.jit.analysis.JitType;
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.analysis.JitTypeBehavior;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitConstVal; import ghidra.pcode.emu.jit.var.JitConstVal;
/** /**
@ -41,7 +42,7 @@ public enum ConstValGen implements ValGen<JitConstVal> {
@Override @Override
public JitType generateValReadCode(JitCodeGenerator gen, JitConstVal v, JitTypeBehavior typeReq, public JitType generateValReadCode(JitCodeGenerator gen, JitConstVal v, JitTypeBehavior typeReq,
MethodVisitor rv) { Ext ext, MethodVisitor rv) {
JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v));
switch (type) { switch (type) {
case IntJitType t -> rv.visitLdcInsn(v.value().intValue()); case IntJitType t -> rv.visitLdcInsn(v.value().intValue());

View file

@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitDirectMemoryVar; import ghidra.pcode.emu.jit.var.JitDirectMemoryVar;
/** /**
@ -33,7 +34,7 @@ public enum DirectMemoryVarGen implements MemoryVarGen<JitDirectMemoryVar> {
@Override @Override
public void generateVarWriteCode(JitCodeGenerator gen, JitDirectMemoryVar v, JitType type, public void generateVarWriteCode(JitCodeGenerator gen, JitDirectMemoryVar v, JitType type,
MethodVisitor rv) { Ext ext, MethodVisitor rv) {
throw new AssertionError(); throw new AssertionError();
} }
} }

View file

@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor;
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.analysis.JitTypeBehavior;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitFailVal; import ghidra.pcode.emu.jit.var.JitFailVal;
/** /**
@ -34,8 +35,8 @@ public enum FailValGen implements ValGen<JitFailVal> {
} }
@Override @Override
public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, public JitType generateValReadCode(JitCodeGenerator gen, JitFailVal v, JitTypeBehavior typeReq,
JitTypeBehavior typeReq, MethodVisitor rv) { Ext ext, MethodVisitor rv) {
throw new AssertionError(); throw new AssertionError();
} }
} }

View file

@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitInputVar; import ghidra.pcode.emu.jit.var.JitInputVar;
/** /**
@ -32,7 +33,7 @@ public enum InputVarGen implements LocalVarGen<JitInputVar> {
GEN; GEN;
@Override @Override
public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, public void generateVarWriteCode(JitCodeGenerator gen, JitInputVar v, JitType type, Ext ext,
MethodVisitor rv) { MethodVisitor rv) {
throw new AssertionError(); throw new AssertionError();
} }

View file

@ -20,6 +20,7 @@ import org.objectweb.asm.MethodVisitor;
import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler; import ghidra.pcode.emu.jit.analysis.JitAllocationModel.VarHandler;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitLocalOutVar; import ghidra.pcode.emu.jit.var.JitLocalOutVar;
/** /**
@ -31,8 +32,8 @@ public enum LocalOutVarGen implements LocalVarGen<JitLocalOutVar> {
@Override @Override
public void generateVarWriteCode(JitCodeGenerator gen, JitLocalOutVar v, JitType type, public void generateVarWriteCode(JitCodeGenerator gen, JitLocalOutVar v, JitType type,
MethodVisitor rv) { Ext ext, MethodVisitor rv) {
VarHandler handler = gen.getAllocationModel().getHandler(v); VarHandler handler = gen.getAllocationModel().getHandler(v);
handler.generateStoreCode(gen, type, rv); handler.generateStoreCode(gen, type, ext, rv);
} }
} }

View file

@ -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.JitType;
import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitVarnodeVar; import ghidra.pcode.emu.jit.var.JitVarnodeVar;
/** /**
@ -41,11 +42,11 @@ public interface LocalVarGen<V extends JitVarnodeVar> extends VarGen<V> {
} }
@Override @Override
default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext,
MethodVisitor rv) { MethodVisitor rv) {
VarHandler handler = gen.getAllocationModel().getHandler(v); VarHandler handler = gen.getAllocationModel().getHandler(v);
JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v)); JitType type = typeReq.resolve(gen.getTypeModel().typeOf(v));
handler.generateLoadCode(gen, type, rv); handler.generateLoadCode(gen, type, ext, rv);
return type; return type;
} }
} }

View file

@ -19,6 +19,7 @@ import org.objectweb.asm.MethodVisitor;
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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.var.JitMemoryOutVar; import ghidra.pcode.emu.jit.var.JitMemoryOutVar;
/** /**
@ -29,7 +30,7 @@ public enum MemoryOutVarGen implements MemoryVarGen<JitMemoryOutVar> {
GEN; GEN;
@Override @Override
public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, public void generateVarWriteCode(JitCodeGenerator gen, JitMemoryOutVar v, JitType type, Ext ext,
MethodVisitor rv) { MethodVisitor rv) {
VarGen.generateValWriteCodeDirect(gen, v, type, rv); VarGen.generateValWriteCodeDirect(gen, v, type, rv);
} }

View file

@ -21,6 +21,7 @@ import ghidra.pcode.emu.jit.JitBytesPcodeExecutorState;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; import ghidra.pcode.emu.jit.gen.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; import ghidra.pcode.emu.jit.gen.type.TypedAccessGen;
import ghidra.pcode.emu.jit.var.JitVarnodeVar; import ghidra.pcode.emu.jit.var.JitVarnodeVar;
@ -41,7 +42,7 @@ public interface MemoryVarGen<V extends JitVarnodeVar> extends VarGen<V> {
} }
@Override @Override
default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, default JitType generateValReadCode(JitCodeGenerator gen, V v, JitTypeBehavior typeReq, Ext ext,
MethodVisitor rv) { MethodVisitor rv) {
return VarGen.generateValReadCodeDirect(gen, v, typeReq, rv); return VarGen.generateValReadCodeDirect(gen, v, typeReq, rv);
} }

View file

@ -25,6 +25,7 @@ import org.objectweb.asm.Opcodes;
import ghidra.pcode.emu.jit.analysis.JitType; import ghidra.pcode.emu.jit.analysis.JitType;
import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior;
import ghidra.pcode.emu.jit.gen.JitCodeGenerator; 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.op.JitPhiOp;
import ghidra.pcode.emu.jit.var.JitMissingVar; import ghidra.pcode.emu.jit.var.JitMissingVar;
@ -61,7 +62,7 @@ public enum MissingVarGen implements VarGen<JitMissingVar> {
@Override @Override
public JitType generateValReadCode(JitCodeGenerator gen, JitMissingVar v, public JitType generateValReadCode(JitCodeGenerator gen, JitMissingVar v,
JitTypeBehavior typeReq, MethodVisitor rv) { JitTypeBehavior typeReq, Ext ext, MethodVisitor rv) {
// [...] // [...]
rv.visitTypeInsn(NEW, NAME_ASSERTION_ERROR); rv.visitTypeInsn(NEW, NAME_ASSERTION_ERROR);
// [...,error:NEW] // [...,error:NEW]
@ -79,7 +80,7 @@ public enum MissingVarGen implements VarGen<JitMissingVar> {
} }
@Override @Override
public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, public void generateVarWriteCode(JitCodeGenerator gen, JitMissingVar v, JitType type, Ext ext,
MethodVisitor rv) { MethodVisitor rv) {
throw new AssertionError(); throw new AssertionError();
} }

View file

@ -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.JitCodeGenerator;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions; 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.op.*;
import ghidra.pcode.emu.jit.var.*; import ghidra.pcode.emu.jit.var.*;
import ghidra.program.model.pcode.Varnode; import ghidra.program.model.pcode.Varnode;
@ -143,9 +144,10 @@ public interface ValGen<V extends JitVal> {
* @param gen the code generator * @param gen the code generator
* @param v the value to read * @param v the value to read
* @param typeReq the required type of the value * @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 * @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 * @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); MethodVisitor rv);
} }

View file

@ -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.CBranchOpGen;
import ghidra.pcode.emu.jit.gen.op.CallOtherOpGen; import ghidra.pcode.emu.jit.gen.op.CallOtherOpGen;
import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage; import ghidra.pcode.emu.jit.gen.tgt.JitCompiledPassage;
import ghidra.pcode.emu.jit.gen.type.TypeConversions.Ext;
import ghidra.pcode.emu.jit.gen.type.TypedAccessGen; import ghidra.pcode.emu.jit.gen.type.TypedAccessGen;
import ghidra.pcode.emu.jit.var.*; import ghidra.pcode.emu.jit.var.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
@ -307,7 +308,8 @@ public interface VarGen<V extends JitVar> extends ValGen<V> {
* @param v the variable to write * @param v the variable to write
* @param type the p-code type (which also determines the expected JVM type) of the value on the * @param type the p-code type (which also determines the expected JVM type) of the value on the
* stack * 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 * @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);
} }

View file

@ -16,6 +16,7 @@
package ghidra.pcode.emu.jit.op; package ghidra.pcode.emu.jit.op;
import java.util.List; import java.util.List;
import java.util.Objects;
import ghidra.pcode.emu.jit.analysis.JitDataFlowState.MiniDFState; import ghidra.pcode.emu.jit.analysis.JitDataFlowState.MiniDFState;
import ghidra.pcode.emu.jit.analysis.JitTypeBehavior; import ghidra.pcode.emu.jit.analysis.JitTypeBehavior;
@ -39,6 +40,10 @@ public record JitCallOtherDefOp(PcodeOp op, JitOutVar out, JitTypeBehavior type,
PcodeUseropDefinition<Object> userop, List<JitVal> args, List<JitTypeBehavior> inputTypes, PcodeUseropDefinition<Object> userop, List<JitVal> args, List<JitTypeBehavior> inputTypes,
MiniDFState dfState) implements JitCallOtherOpIf, JitDefOp { MiniDFState dfState) implements JitCallOtherOpIf, JitDefOp {
public JitCallOtherDefOp {
Objects.requireNonNull(type);
}
@Override @Override
public boolean canBeRemoved() { public boolean canBeRemoved() {
return JitCallOtherOpIf.super.canBeRemoved() && JitDefOp.super.canBeRemoved(); return JitCallOtherOpIf.super.canBeRemoved() && JitDefOp.super.canBeRemoved();

View file

@ -20,6 +20,7 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*; import java.lang.reflect.*;
import java.math.BigInteger;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -81,7 +82,7 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
opdef.posLib = pos; opdef.posLib = pos;
} }
}, },
OUTPUT(OpOutput.class, Varnode.class) { OUTPUT(OpOutput.class, Varnode.class, int[].class) {
@Override @Override
int getPos(AnnotatedPcodeUseropDefinition<?> opdef) { int getPos(AnnotatedPcodeUseropDefinition<?> opdef) {
return opdef.posOut; return opdef.posOut;
@ -115,11 +116,11 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
} }
private final Class<? extends Annotation> annotCls; private final Class<? extends Annotation> annotCls;
private final Class<?> paramCls; private final List<Class<?>> allowedClsList;
private ParamAnnotProc(Class<? extends Annotation> annotCls, Class<?> paramCls) { private ParamAnnotProc(Class<? extends Annotation> annotCls, Class<?>... paramCls) {
this.annotCls = annotCls; this.annotCls = annotCls;
this.paramCls = paramCls; this.allowedClsList = List.of(paramCls);
} }
abstract int getPos(AnnotatedPcodeUseropDefinition<?> opdef); abstract int getPos(AnnotatedPcodeUseropDefinition<?> opdef);
@ -130,7 +131,7 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
return p.getAnnotation(annotCls) != null; return p.getAnnotation(annotCls) != null;
} }
Type getArgumentType(Type opType) { static Type parameterize(Class<?> paramCls, Type opType) {
TypeVariable<?>[] typeParams = paramCls.getTypeParameters(); TypeVariable<?>[] typeParams = paramCls.getTypeParameters();
if (typeParams.length == 0) { if (typeParams.length == 0) {
return paramCls; return paramCls;
@ -141,6 +142,25 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
throw new AssertionError(); throw new AssertionError();
} }
String nameAllowedArgumentTypes(Type opType) {
return allowedClsList.stream()
.map(cls -> parameterize(cls, opType).toString())
.collect(Collectors.joining(","));
}
record MatchedClassWithArgs(Class<?> paramCls, Map<TypeVariable<?>, Type> typeArgs) {
static MatchedClassWithArgs find(Type paramType, List<Class<?>> allowed) {
for (Class<?> cls : allowed) {
Map<TypeVariable<?>, Type> typeArgs =
TypeUtils.getTypeArguments(paramType, cls);
if (typeArgs != null) {
return new MatchedClassWithArgs(cls, typeArgs);
}
}
return null;
}
}
void processParameterPerAnnot(AnnotatedPcodeUseropDefinition<?> opdef, Type declClsOpType, void processParameterPerAnnot(AnnotatedPcodeUseropDefinition<?> opdef, Type declClsOpType,
int i, Parameter p) { int i, Parameter p) {
if (getPos(opdef) != -1) { if (getPos(opdef) != -1) {
@ -148,20 +168,21 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
"Can only have one parameter with @" + annotCls.getSimpleName()); "Can only have one parameter with @" + annotCls.getSimpleName());
} }
Type pType = p.getParameterizedType(); Type pType = p.getParameterizedType();
Map<TypeVariable<?>, Type> typeArgs = TypeUtils.getTypeArguments(pType, paramCls); MatchedClassWithArgs match = MatchedClassWithArgs.find(pType, allowedClsList);
if (typeArgs == null) { if (match == null) {
throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + 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 // Nothing
} }
else if (typeArgs.size() == 1) { else if (match.typeArgs.size() == 1) {
Type declMthOpType = typeArgs.get(paramCls.getTypeParameters()[0]); Type declMthOpType = match.typeArgs.get(match.paramCls.getTypeParameters()[0]);
if (!Objects.equals(declClsOpType, declMthOpType)) { if (!Objects.equals(declClsOpType, declMthOpType)) {
throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + throw new IllegalArgumentException("Parameter " + p.getName() + " with @" +
annotCls.getSimpleName() + " must acccept " + annotCls.getSimpleName() + " must acccept " +
getArgumentType(declClsOpType)); nameAllowedArgumentTypes(declClsOpType));
} }
} }
else { else {
@ -324,6 +345,14 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
return canInline; return canInline;
} }
@Override
public Class<?> getOutputType() {
if (posOut == -1) {
return method.getReturnType();
}
return method.getParameterTypes()[posOut];
}
@Override @Override
public Method getJavaMethod() { public Method getJavaMethod() {
return method; return method;
@ -421,6 +450,23 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
} }
} }
record IntArrayUseropInputParam(int position) implements UseropInputParam {
@Override
public <T> Object convert(Varnode vn, PcodeExecutor<T> executor) {
PcodeExecutorStatePiece<T, T> state = executor.getState();
PcodeArithmetic<T> 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 { record FloatUseropInputParam(int position) implements UseropInputParam {
@Override @Override
public <T> Object convert(Varnode vn, PcodeExecutor<T> executor) { public <T> Object convert(Varnode vn, PcodeExecutor<T> executor) {
@ -482,6 +528,9 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
else if (pType == long.class) { else if (pType == long.class) {
paramsIn.add(new LongUseropInputParam(i)); paramsIn.add(new LongUseropInputParam(i));
} }
else if (pType == int[].class) {
paramsIn.add(new IntArrayUseropInputParam(i));
}
else if (pType == float.class) { else if (pType == float.class) {
paramsIn.add(new FloatUseropInputParam(i)); paramsIn.add(new FloatUseropInputParam(i));
} }
@ -735,7 +784,8 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
* An annotation to receive the output varnode into a parameter * An annotation to receive the output varnode into a parameter
* *
* <p> * <p>
* 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) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)

View file

@ -21,6 +21,7 @@ import java.util.*;
import org.apache.commons.lang3.reflect.TypeUtils; import org.apache.commons.lang3.reflect.TypeUtils;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpOutput;
import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop; import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop;
import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; import ghidra.pcodeCPort.slghsymbol.UserOpSymbol;
import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.PcodeOp;
@ -219,6 +220,17 @@ public interface PcodeUseropLibrary<T> {
*/ */
boolean canInlinePcode(); boolean canInlinePcode();
/**
* If this userop is defined as a java callback, get the type of the output
*
* <p>
* 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 * If this userop is defined as a java callback, get the method
* *

View file

@ -221,6 +221,11 @@ public class SleighPcodeUseropDefinition<T> implements PcodeUseropDefinition<T>
return true; return true;
} }
@Override
public Class<?> getOutputType() {
return void.class;
}
@Override @Override
public Method getJavaMethod() { public Method getJavaMethod() {
return null; return null;

View file

@ -27,38 +27,12 @@ public class OpBehaviorPopcount extends UnaryOpBehavior {
@Override @Override
public long evaluateUnary(int sizeout, int sizein, long val) { public long evaluateUnary(int sizeout, int sizein, long val) {
val = (val & 0x5555555555555555L) + ((val >>> 1) & 0x5555555555555555L); return Long.bitCount(val);
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;
} }
@Override @Override
public BigInteger evaluateUnary(int sizeout, int sizein, BigInteger unsignedIn1) { public BigInteger evaluateUnary(int sizeout, int sizein, BigInteger unsignedIn1) {
int bitcount = 0; return BigInteger.valueOf(unsignedIn1.bitCount());
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);
} }
} }

View file

@ -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;
<loop>
next:16 = prev + curr;
prev = curr;
curr = next;
counter = counter + 1;
if (counter < 0x%08x) goto <loop>;
r0 = curr(0);
r1 = curr(8);
goto 0xdeadbeef;
""".formatted(N);
PcodeEmulator emu = new PcodeEmulator(toy);
PcodeThread<byte[]> 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;
<loop>
next:16 = prev + curr;
prev = curr;
curr = next;
counter = counter + 1;
if (counter < 0x%08x) goto <loop>;
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)));
}
}

View file

@ -164,7 +164,7 @@ public class Varnode {
if (spaceID != varnode.spaceID) { if (spaceID != varnode.spaceID) {
return false; return false;
} }
if (isConstant() || isUnique() || isHash()) { if (isConstant() || isHash()) {
// this is not really a valid use case // this is not really a valid use case
return offset == varnode.getOffset(); return offset == varnode.getOffset();
} }

View file

@ -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<String> 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<int[]> 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);
}
}