GP-6158: Allows static methods to be marked @PcodeOp in UseropLibrary

This commit is contained in:
Dan
2026-01-15 13:25:25 +00:00
parent c3cf370203
commit e8ff64a59e
7 changed files with 150 additions and 32 deletions

View File

@@ -17,8 +17,7 @@ package ghidra.pcode.emu.jit.gen.op;
import static ghidra.pcode.emu.jit.gen.GenConsts.*;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -163,6 +162,12 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
};
}
static <E> List<E> tail(List<E> list) {
return list.subList(1, list.size());
}
record PlacedParam<N>(Emitter<N> em, List<JitVal> args, List<Parameter> params) {}
/**
* Emit code to implement the Direct strategy (see the class documentation)
*
@@ -244,7 +249,33 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
};
}
<N extends Next> ObjInv<?, LIB, N, ?> doInv(Emitter<N> em, List<JitVal> args,
<N extends Next> PlacedParam<? extends Ent<N, ?>> placeNextParam(Emitter<N> em,
List<JitVal> args, List<Parameter> params) {
Parameter param = params.getFirst();
if (param == outputParameter) {
var emNext = em.emit(Op::aload, localOut);
return new PlacedParam<>(emNext, args, tail(params));
}
JitVal arg = args.getFirst();
var emNext = doReadArg(em, arg, param);
return new PlacedParam<>(emNext, tail(args), tail(params));
}
<N extends Next> Inv<?, N, Bot> doInvVirtual(Emitter<N> em, List<JitVal> args,
List<Parameter> params) {
var emLib = em
.emit(useropField::genLoad, localThis, gen)
.emit(Op::invokeinterface, T_PCODE_USEROP_DEFINITION, "getDefiningLibrary",
MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY)
.step(Inv::takeObjRef)
.step(Inv::ret)
.emit(Op::checkcast, libType);
var inv = doInvVirtualRec(emLib, args, params)
.step(Inv::takeQObjRef);
return inv;
}
<N extends Next> ObjInv<?, LIB, N, ?> doInvVirtualRec(Emitter<N> em, List<JitVal> args,
List<Parameter> params) {
if (params.isEmpty()) {
/**
@@ -254,20 +285,28 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
return Op.invokevirtual(em, libType, method.getName(), MthDesc.reflect(method),
false);
}
Parameter param = params.getFirst();
if (param == outputParameter) {
var emOut = em.emit(Op::aload, localOut);
var inv = doInv(emOut, args, params.subList(1, params.size()));
return Inv.takeQArg(inv);
}
JitVal arg = args.getFirst();
// TODO: Annotation/attribute to describe signedness?
// TODO: Or a way to receive the byte size
var emRead = doReadArg(em, arg, param);
var inv =
doInv(emRead, args.subList(1, args.size()), params.subList(1, params.size()));
PlacedParam<? extends Ent<N, ?>> next = placeNextParam(em, args, params);
ObjInv<?, LIB, ? extends Ent<N, ?>, ?> inv =
doInvVirtualRec(next.em, next.args, next.params);
return Inv.takeQArg(inv);
}
<N extends Next> Inv<?, N, ?> doInvStaticRec(Emitter<N> em, List<JitVal> args,
List<Parameter> params) {
if (params.isEmpty()) {
return Op.invokestatic(em, libType, method.getName(), MthDesc.reflect(method),
false);
}
PlacedParam<? extends Ent<N, ?>> next = placeNextParam(em, args, params);
Inv<?, ? extends Ent<N, ?>, ?> inv =
doInvStaticRec(next.em, next.args, next.params);
return Inv.takeQArg(inv);
}
<N extends Next> Inv<?, N, ?> doInvStatic(Emitter<N> em, List<JitVal> args,
List<Parameter> params) {
return doInvStaticRec(em, args, params);
}
};
var tryCatchBlock = Misc.tryCatch(em, Lbl.create(),
@@ -275,16 +314,9 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
GenConsts.T_THROWABLE);
em = tryCatchBlock.em();
var emLib = em
.emit(useropField::genLoad, localThis, gen)
.emit(Op::invokeinterface, T_PCODE_USEROP_DEFINITION, "getDefiningLibrary",
MDESC_PCODE_USEROP_DEFINITION__GET_DEFINING_LIBRARY)
.step(Inv::takeObjRef)
.step(Inv::ret)
.emit(Op::checkcast, libType);
var inv = rec
.doInv(emLib, op.args(), Arrays.asList(parameters))
.step(Inv::takeQObjRef);
var inv = Modifier.isStatic(method.getModifiers())
? rec.doInvStatic(em, op.args(), Arrays.asList(parameters))
: rec.doInvVirtual(em, op.args(), Arrays.asList(parameters));
if (outputParameter != null) {
if (outMpType != null && op instanceof JitCallOtherDefOp defOp) {
@@ -300,7 +332,7 @@ public enum CallOtherOpGen implements OpGen<JitCallOtherOpIf> {
// TODO: Can annotation specify signedness of return value?
var write = new Object() {
public <T extends BPrim<?>, JT extends SimpleJitType<T, JT>> Emitter<Bot> doWrite(
Inv<?, Bot, Bot> inv, Class<?> returnType) {
Inv<?, Bot, ?> inv, Class<?> returnType) {
JT type = SimpleJitType.forJavaType(returnType);
return inv
.step(Inv::retQ, type.bType())

View File

@@ -825,6 +825,25 @@ public interface Methods {
return (Inv) inv;
}
/**
* Pop an argument and a parameter without checking
* <p>
* NOTE: This should only be used with {@link MthDesc#reflect(Method)}. When dealing with a
* parameter list whose length is only known at runtime, recursion should be favored, so
* that each argument pushed by the emitter is provably paired with a parameter denoted by
* calling this method.
*
* @param <MR> the return type
* @param <SN1> the new remaining stack contents
* @param <SN0> the current stack contents having the popped argument on top
* @param inv the invocation object
* @return the invocation object with remaining parameters and stack contents
*/
public static <MR extends BType, SN1 extends Next, SN0 extends Ent<SN1, ?>> Inv<MR, SN1, ?>
takeQArg(Inv<MR, SN0, ?> inv) {
return (Inv) inv;
}
/**
* Pop a polymorphic reference argument and match/check it against the next (right-most
* unmatched parameter)
@@ -976,7 +995,7 @@ public interface Methods {
* @param inv the invocation object
* @return the emitter typed with the resulting stack
*/
public static <SN extends Next> Emitter<SN> retQVoid(Inv<?, SN, Bot> inv) {
public static <SN extends Next> Emitter<SN> retQVoid(Inv<?, SN, ?> inv) {
return inv.em;
}
@@ -1007,7 +1026,7 @@ public interface Methods {
* value
*/
public static <RT extends BNonVoid, SN extends Next> Emitter<Ent<SN, RT>>
retQ(Inv<?, SN, Bot> inv, RT returnType) {
retQ(Inv<?, SN, ?> inv, RT returnType) {
return (Emitter) inv.em;
}

View File

@@ -263,7 +263,13 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
this.method = method;
this.library = library;
try {
this.handle = lookup.unreflect(method).bindTo(library);
MethodHandle unbound = lookup.unreflect(method);
if (Modifier.isStatic(method.getModifiers())) {
this.handle = unbound;
}
else {
this.handle = lookup.unreflect(method).bindTo(library);
}
}
catch (IllegalAccessException e) {
throw new IllegalArgumentException(

View File

@@ -31,6 +31,7 @@ import ghidra.GhidraTestApplicationLayout;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration;
import ghidra.pcode.exec.PcodeArithmetic.Purpose;
import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason;
import ghidra.pcode.utils.Utils;
import ghidra.program.model.lang.Register;
@@ -276,6 +277,22 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGTest {
assertRegVarnode(r0, library.outVar);
}
@Test
public void testStaticMethod() throws Exception {
var library = new TestUseropLibrary() {
@PcodeUserop
private static int __testop(int a, int b) {
return (int) Math.pow(a, b);
}
};
PcodeExecutor<byte[]> executor = createBytesExecutor();
Register r0 = executor.getLanguage().getRegister("r0");
executeSleigh(executor, library, "r0 = __testop(4:4, 5:4);");
assertEquals(1024, executor.getArithmetic()
.toLong(executor.getState().getVar(r0, Reason.INSPECT), Purpose.INSPECT));
}
@Test
public void testKitchenSink() throws Exception {
var library = new TestUseropLibrary() {

View File

@@ -238,6 +238,11 @@ public abstract class AbstractJitCodeGeneratorTest extends AbstractJitTest {
return 2 * a + b;
}
@PcodeUserop(functional = true)
public static long func_st_userop(long a, long b) {
return 3 * a + b;
}
@PcodeUserop(functional = true)
public void func_mpUserop(@OpOutput int[] out, int[] a, int[] b) {
gotFuncUseropCall = true;
@@ -254,6 +259,20 @@ public abstract class AbstractJitCodeGeneratorTest extends AbstractJitTest {
}
}
@PcodeUserop(functional = true)
public static void func_st_mpUserop(@OpOutput int[] out, int[] a, int[] b) {
if (out == null) {
return;
}
out[0] = b[0];
out[1] = a[0];
for (int i = 0; i < 8; i++) {
out[0] |= out[0] << 8;
out[1] |= out[1] << 8;
}
}
@PcodeUserop(canInline = true)
public void sleigh_userop(@OpExecutor PcodeExecutor<byte[]> executor,
@OpLibrary PcodeUseropLibrary<byte[]> library,

View File

@@ -497,6 +497,17 @@ public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGen
assertEquals(0, tr.getLongRegVal("r0"));
}
@Test
public void testCallOtherFuncJavaDefStatic() throws Exception {
Translation tr = translateSleigh(getLanguageID(), """
r0 = func_st_userop(6:8, 2:8);
""");
assertFalse(tr.library().gotFuncUseropCall);
tr.runFallthrough();
assertFalse(tr.library().gotFuncUseropCall);
assertEquals(20, tr.getLongRegVal("r0"));
}
@Test
public void testCallOtherFuncJavaDefMpInt() throws Exception {
Translation tr = translateSleigh(getLanguageID(), """
@@ -524,6 +535,20 @@ public abstract class AbstractToyJitCodeGeneratorTest extends AbstractJitCodeGen
assertEquals(0, tr.getLongRegVal("r0"));
}
@Test
public void testCallOtherFuncJavaDefStaticMpInt() throws Exception {
Translation tr = translateSleigh(getLanguageID(), """
temp1:9 = zext(6:8);
temp2:9 = zext(2:8);
temp0:9 = func_st_mpUserop(temp1, temp2);
r0 = temp0(0);
""");
assertFalse(tr.library().gotFuncUseropCall);
tr.runFallthrough();
assertFalse(tr.library().gotFuncUseropCall);
assertEquals(0x0606060602020202L, tr.getLongRegVal("r0"));
}
/**
* Test that the emulator doesn't throw until the userop is actually encountered at run time.
*

View File

@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -185,7 +185,7 @@ unsigned long parse_int(char *a, char *name,
char *e;
unsigned long val = strtoul(a, &e, 10);
if (*e != 0 || val < min || max < val) {
fprintf(stderr, "Invalid %s: %s. Must be an integer between %d and %d.\n",
fprintf(stderr, "Invalid %s: %s. Must be an integer between %ld and %ld.\n",
name, a, min, max);
exit(-1);
}