Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Primitive, non boxing, long array based MStack #531

Merged
merged 28 commits into from
Oct 2, 2024

Conversation

andreaTP
Copy link
Collaborator

Supersede #447 this is a complete implementation, already passing all the tests up to the interpreter, can be tested with something like:

 mvn clean install -pl runtime-tests -am

I started, but got a bit confused about the changes necessary in "aot", @electrum or @danielperano I would love some help in finalizing this PR 🙏 and learning a bit more how asm works ...

This is a prerequisite to avoid arrays construction here, the plan is to open up the underlying array so that we can perform operations just using the offset 🤞

@andreaTP andreaTP changed the title Primitive long stack rev 2 Primitive, non boxing, long array based MStack Sep 18, 2024
@andreaTP
Copy link
Collaborator Author

update: I benchmarked the interpreter and got good improvements on this branch, I think it's in the right direction!
Regarding the AOT, the changes seem to be a bit more invasive than expected, it would be great to have some help.

aot/src/main/java/com/dylibso/chicory/aot/AotMachine.java Outdated Show resolved Hide resolved
emitBoxArguments(asm, type.params());
// emitBoxArguments(asm, type.params());
// asm.visitLdcInsn(LONG);
for (int i = 0; i < type.params().size(); i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use slotCount() to compute the slot parameter correctly, as long/double use two slots in JVM bytecode. This annoyance means you always need to know the type you are dealing with.

The incoming parameters to this method are a native Java value: int, long, float, double

aot/src/main/java/com/dylibso/chicory/aot/AotMethods.java Outdated Show resolved Hide resolved
@andreaTP
Copy link
Collaborator Author

I did a little benchmarking on my super noisy machine(take the results with a lot of salt):

Chicory 0.0.12:

With GraalJIT:
Benchmark                                      Mode  Cnt      Score        Error  Units
ChicoryAotTest.chicoryAotTest                  avgt    3   1282.401 ±    51.518  ns/op
ChicoryInterpreterTest.chicoryInterpreterTest  avgt    3  63086.906 ± 18526.425  ns/op

Without GraalJIT:
Benchmark                                      Mode  Cnt      Score        Error  Units
ChicoryAotTest.chicoryAotTest                  avgt    3   1222.858 ±    326.915  ns/op
ChicoryInterpreterTest.chicoryInterpreterTest  avgt    3  87630.869 ± 138021.856  ns/op

---

Current main:

With GraalJIT:
Benchmark                                      Mode  Cnt      Score       Error  Units
ChicoryAotTest.chicoryAotTest                  avgt    3   1237.301 ±   423.694  ns/op
ChicoryInterpreterTest.chicoryInterpreterTest  avgt    3  71274.055 ± 11410.525  ns/op

Without GraalJIT:
Benchmark                                      Mode  Cnt       Score       Error  Units
ChicoryAotTest.chicoryAotTest                  avgt    3    1349.806 ±   945.091  ns/op
ChicoryInterpreterTest.chicoryInterpreterTest  avgt    3  101900.141 ± 38884.734  ns/op

---

Primitive longs (this branch):

With GraalJIT:
Benchmark                                      Mode  Cnt      Score       Error  Units
ChicoryAotTest.chicoryAotTest                  avgt    3   1153.859 ±   984.977  ns/op
ChicoryInterpreterTest.chicoryInterpreterTest  avgt    3  41791.372 ± 43360.065  ns/op

Without GraalJIT:
Benchmark                                      Mode  Cnt      Score       Error  Units
ChicoryAotTest.chicoryAotTest                  avgt    3    899.077 ±   483.678  ns/op
ChicoryInterpreterTest.chicoryInterpreterTest  avgt    3  49629.772 ± 88844.628  ns/op

@andreaTP andreaTP marked this pull request as ready for review September 25, 2024 14:54
@andreaTP
Copy link
Collaborator Author

andreaTP commented Sep 25, 2024

CI is green, ready for final review!

And thanks to @evacchi that paired and kept me sane in getting there with the AOT!

Benchmark from the CI:
Screenshot from 2024-09-25 15-58-32

@evacchi
Copy link
Collaborator

evacchi commented Sep 25, 2024

this is exciting!

@andreaTP
Copy link
Collaborator Author

Begging for review here 🙏 cc. @bhelx @electrum @danielperano

Copy link
Collaborator

@evacchi evacchi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be fine, but I think it would be better to have another pair of eyes on it :)

aot/src/main/java/com/dylibso/chicory/aot/AotUtil.java Outdated Show resolved Hide resolved
default:
throw new IllegalArgumentException("Unsupported ValueType: " + type.name());
}
}

public static Method boxer(ValueType type) {
public static Method convertToLong(ValueType type) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public static void emitJvmToLong(MethodVisitor asm, ValueType type) {
    switch (type) {
        case I32:
        case ExternRef:
        case FuncRef:
            asm.visitInsn(Opcodes.I2L);
            return;
        case I64:
            return;
        case F32:
            emitInvokeStatic(asm, F32_TO_LONG);
            return;
        case F64:
            emitInvokeStatic(asm, F64_TO_LONG);
            return;
    }
}

default:
throw new IllegalArgumentException("Unsupported ValueType: " + type.name());
}
}

public static MethodHandle unboxerHandle(ValueType type) {
public static MethodHandle convertFromLongHandle(ValueType type) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

longToJvmHandle

} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}

public static MethodHandle boxerHandle(ValueType type) {
public static MethodHandle convertToLongHandle(ValueType type) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jvmToLongHandle

@@ -50,6 +50,12 @@
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<!-- Enable better debugging -->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we simply remove the <optional>true</optional> from AOT, so that this is always available? The only downside is the additional size of the asm-util JAR.

Comment on lines 9 to 33
// From long
public static int toInt(long val) {
return (int) val;
}

public static long toLong(long val) {
return val;
}

public static float toFloat(long val) {
return Value.longToFloat(val);
}

public static double toDouble(long val) {
return Value.longToDouble(val);
}

// To Long
public static long asLong(int val) {
return val;
}

public static long asLong(long val) {
return val;
}

public static long asLong(float val) {
return Value.floatToLong(val);
}

public static long asLong(double val) {
return Value.doubleToLong(val);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's name these the same as the method constants. I find the overloads hard to read.

Suggested change
// From long
public static int toInt(long val) {
return (int) val;
}
public static long toLong(long val) {
return val;
}
public static float toFloat(long val) {
return Value.longToFloat(val);
}
public static double toDouble(long val) {
return Value.longToDouble(val);
}
// To Long
public static long asLong(int val) {
return val;
}
public static long asLong(long val) {
return val;
}
public static long asLong(float val) {
return Value.floatToLong(val);
}
public static long asLong(double val) {
return Value.doubleToLong(val);
}
public static int longToI32(long val) {
return (int) val;
}
public static long longToI64(long val) {
return val;
}
public static float longToF32(long val) {
return Value.longToFloat(val);
}
public static double longToF64(long val) {
return Value.longToDouble(val);
}
public static long i32ToLong(int val) {
return val;
}
public static long i64ToLong(long val) {
return val;
}
public static long f32ToLong(float val) {
return Value.floatToLong(val);
}
public static long f64ToLong(double val) {
return Value.doubleToLong(val);
}

I64_2_LONG = ValueConversions.class.getMethod("asLong", long.class);
F32_2_LONG = ValueConversions.class.getMethod("asLong", float.class);
F64_2_LONG = ValueConversions.class.getMethod("asLong", double.class);
EXTREF_2_LONG = ValueConversions.class.getMethod("asLong", int.class);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for these anymore, as they are the same as the i32 version.

try {
return publicLookup().unreflect(unboxer(type));
return publicLookup().unreflect(convertFromLong(type));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we no longer have the Method version of the conversion functions, we'll probably want to add constants for the method handles and make this a switch statement.

arguments.add(new MethodCallExpr(argExpr, "asFloat"));
arguments.add(
new MethodCallExpr(
new NameExpr("Float"),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these use the new methods in Value?

@@ -567,6 +567,7 @@
<ignoredUnusedDeclaredDependencies>
<dependency>com.dylibso.chicory:wasm-corpus</dependency>
<dependency>org.junit.jupiter:junit-jupiter-engine</dependency>
<dependency>org.ow2.asm:asm-util</dependency>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make this a non-optional dependency in AOT

}
if (type.returns().size() > 1) {
return result;
}
result = filterReturnValue(result, boxerHandle(type.returns().get(0)));
return filterReturnValue(result, ValueWrapper.HANDLE);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ValueWrapper is unused now

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now it's a LongArrayWrapper to: long -> long[]

@andreaTP
Copy link
Collaborator Author

andreaTP commented Oct 2, 2024

ok, let's merge this, any follow up comment will be fixed on top.

@andreaTP andreaTP merged commit 3a46c2b into dylibso:main Oct 2, 2024
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants