Grasmin
Groovy AST Transformation to allow writing Jasmin code (JVM bytecode) directly on groovy files
Install / Use
/learn @renatoathaydes/GrasminREADME
Grasmin
Write Jasmin (JVM assembly) code directly in your Groovy files
Grasmin allows you to write Jasmin code (which is basically JVM assembly bytecode instructions)
directly on your Groovy files by annotating methods, or even entire classes, with the @JasminCode annotation.
This annotation is a Groovy AST Transformation, which allows manipulation of source code during the compilation process.
For example, you could write Hello World as follows:
class Hello {
@JasminCode
static void main(args) {
"""
.limit stack 2
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hello World!"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
return
"""
}
}
The @JasminCode annotation supports a single parameter, outputDebugFile. You may provide a value for it, with the
path to a file, if you want Grasmin to write the contents of the Jasmin file used to generate the byte-code to it for
debugging purposes.
For example:
@JasminCode( outputDebugFile = 'jasmin-files/hello.j' )
Most JVM instructions should work with Jasmin code. Let me know if you have any issues.
Using Grasmin
Currently, you need to clone this repo and build it with Gradle:
gradlew install
gradlew(Gradle wrapper) will download Gradle automatically if you don't already have it, then build Grasmin.
This will put the following artifact in your local Maven repo:
group = 'com.athaydes.grasmin'
name = 'grasmin-core'
version = 0.1
If you don't use Maven and Gradle, just do gradlew jar to create a jar in the build/libs folder.
The Jasmin Jar can be found on SourceForge.
Annotating classes
When you annotate a class with @JasminCode, everything
in the class will be converted into a Jasmin file, which will then be used directly by Jasmin to generate the bytecode.
That means that all methods in the class must be written in Jasmin code!
For better performance, this is the recommended approach.
You can expect the generated bytecode to be effectively what you wrote, except for the following small convenient exceptions Grasmin provides:
- class variables (including static variables) can be initialized directly as you would do in Java or Groovy:
String myString = "Hello Grasmin!"
static final int myInt = 10
- you do not need to explicitly declare a default constructor. It will be automatically generated for you.
Notice that if you declare a default constructor, it is your responsibility to initialize non-static variables as Grasmin would have done it in the automatically generated default constructor otherwise.
Here's a simple example of a class annotated with @JasminCode:
@JasminCode( outputDebugFile = 'exampleJasminClass.j' )
class ExampleJasminClass {
private String name
private int i
public static final String staticString = 'a-string'
public static final int staticInt = 123
// default constructor is declared, so must initialize the instance variables here!
ExampleJasminClass() {
"""\
.limit stack 2
aload_0
invokenonvirtual java/lang/Object/<init>()V
aload_0
ldc "John"
putfield grasmin/test_target/ExampleJasminClass/name Ljava/lang/String;
aload_0
bipush 55
putfield grasmin/test_target/ExampleJasminClass/i I
return"""
}
// concatenate two Strings, returning the result
String concat( String a, String b ) {
"""\
.limit locals 3
.limit stack 2
aload_1
aload_2
invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String;
areturn"""
// a.concat( b )
}
// simple getter
public String getName() {
"""\
aload_0
getfield grasmin/test_target/ExampleJasminClass/name Ljava/lang/String;
areturn"""
}
// int getter returns with ireturn
public int getI() {
"""\
aload_0
getfield grasmin/test_target/ExampleJasminClass/i I
ireturn"""
111 // ignored
}
}
And the following is the output produced by javap -c on the class file generated by the above class declaration:
Compiled from "grasmin.test_target.ExampleJasminClass.j"
public class grasmin.test_target.ExampleJasminClass {
public static final java.lang.String staticString;
public static final int staticInt;
public static {};
Code:
0: ldc #15 // String a-string
2: putstatic #38 // Field staticString:Ljava/lang/String;
5: ldc #22 // int 123
7: putstatic #6 // Field staticInt:I
10: return
public grasmin.test_target.ExampleJasminClass();
Code:
0: aload_0
1: invokespecial #35 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #14 // String John
7: putfield #7 // Field name:Ljava/lang/String;
10: aload_0
11: bipush 55
13: putfield #28 // Field i:I
16: return
public java.lang.String concat(java.lang.String, java.lang.String);
Code:
0: aload_1
1: aload_2
2: invokevirtual #10 // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
5: areturn
public java.lang.String getName();
Code:
0: aload_0
1: getfield #7 // Field name:Ljava/lang/String;
4: areturn
public int getI();
Code:
0: aload_0
1: getfield #28 // Field i:I
4: ireturn
}
Notice that because the class file is generated solely based on your declaration, no Groovy features can be used with annotated classes (in fact, you do not need to have Groovy in the classpath to run classes that do not directly use Groovy code in the JasminCode).
Annotating methods
Any method, in an otherwise normal Groovy class, can be annotated with @JasminCode.
Annotated methods can have any signature.
If your method needs to return a particular type, just add a dummy value as the last statement of your method to
make your IDE compiler happy... during Groovy compilation, Grasmin will only use the first statement of your
method (which should be a String or a property that evaluates to a String), ignoring any subsequent statements (which
will not even be present in the compiled class file).
The following method returns the sum of two integers (notice the use of both @JasminCode and @GroovyStatic -
you almost always want to use them together on methods to avoid the cost of calling a method via Groovy's normal
dynamic method dispatch):
class Hello {
@CompileStatic
@JasminCode
int sum( int a, int b ) {
"""
.limit stack 2
.limit locals 2
iload_0
iload_1
iadd
ireturn
"""
0 // ignored, but makes the IDE happy
}
}
Keep reading below to see exactly what the generated byte-code is for this example.
How it works
Annotated classes
When you annotate a class with @JasminCode, during the semantics analysis phase, Grasmin will basically replace the
normal Groovy class file which would have normally been generated by groovyc with one created by Jasmin based on a j
file created by interpreting only the first statement of each method as being Jasmin code, ie. a String (or an
expression which evaluates to a String) containing Jasmin code.
As mentioned earlier, Grasmin will also:
- initialize any static variables declared in the normal Groovy syntax.
- initialize instance variables which have an initial value in an automatically generated default constructor, if no default constructor was declared.
Notice that, because you actually declare the class in a normal Groovy file, you can use it in your Java or Groovy project with full support from the IDE, even though at run-time the actual implementation of the class will be turned into the byte-code instructions provided by the Strings in the first statement of each method.
Annotated methods
Grasmin turns any method annotated with @JasminCode into a call to a static method (called run) of a class created by
Jasmin from the Jasmin code provided in the annotated method.
The example above (in the sum function) produces the following class, as output by javap:
Compiled from "Hello.groovy"
public class com.athaydes.grasmin.Hello implements groovy.lang.GroovyObject {
// ... lots of Java/Groovy boilerplate
public int sum(int, int);
Code:
0: iload_1
1: iload_2
2: invokestatic #121 // Method com_athaydes_grasmin_Hello_sum.run:(II)I
5: ireturn
6: ldc #35 // int 0
8: ireturn
// ... more boilerplate
}
the two last lines in
sumare dead-code, probably introduced by the Groovy compiler as a default return value
And the new class produced with Jasmin to hold the implementation of sum:
Compiled from "com_athaydes_grasmin_Hello_sum.j"
public class com_athaydes_grasmin_Hello_sum {
public static int run(int, int);
Code:
0: iload_0
1: iload_1
2: iadd
3: ireturn
}
The above is the whole output of javap (without the verbose key).
The j file mentioned in the first line is the temporary file used by Grasmin as input for Jasmin.
Future work and performance
Unfortunately, delegating a method call to a static method of an external class does not seem to be very efficient for a short algorithm, at least, so gains in perfo
Related Skills
qqbot-channel
348.2kQQ 频道管理技能。查询频道列表、子频道、成员、发帖、公告、日程等操作。使用 qqbot_channel_api 工具代理 QQ 开放平台 HTTP 接口,自动处理 Token 鉴权。当用户需要查看频道、管理子频道、查询成员、发布帖子/公告/日程时使用。
docs-writer
100.2k`docs-writer` skill instructions As an expert technical writer and editor for the Gemini CLI project, you produce accurate, clear, and consistent documentation. When asked to write, edit, or revie
model-usage
348.2kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
Design
Campus Second-Hand Trading Platform \- General Design Document (v5.0 \- React Architecture \- Complete Final Version)1\. System Overall Design 1.1. Project Overview This project aims t
