├── script ├── .gitignore ├── samples │ ├── hello.script │ ├── bignum.script │ ├── fibo_empty.script │ ├── fun.script │ ├── fibo.script │ ├── while.script │ ├── fibo_overflow.script │ ├── gcd.script │ └── block.script ├── build-lib │ ├── tatoo.jar │ └── motocity │ │ └── motocity-tatoo.jar ├── lib │ └── tatoo-runtime.jar ├── benchmarks │ ├── fibo.js │ ├── add1.script │ ├── Fibo.java │ ├── gcd.script │ ├── print_starts.script │ ├── gcd.js │ └── Gcd.java ├── gen-src │ └── com │ │ └── github │ │ └── forax │ │ └── vmboiler │ │ └── sample │ │ └── script │ │ ├── parser │ │ ├── VersionEnum.java │ │ ├── NonTerminalEnum.java │ │ ├── TerminalEnum.java │ │ └── ProductionEnum.java │ │ ├── lexer │ │ ├── RuleEnum.java │ │ └── LexerDataTable.java │ │ └── tools │ │ ├── TerminalEvaluator.java │ │ ├── ToolsDataTable.java │ │ ├── GrammarEvaluator.java │ │ ├── Analyzers.java │ │ └── AnalyzerProcessor.java ├── src │ ├── com │ │ └── github │ │ │ └── forax │ │ │ └── vmboiler │ │ │ └── sample │ │ │ └── script │ │ │ ├── Script.java │ │ │ ├── Binding.java │ │ │ ├── Main.java │ │ │ ├── Visitor.java │ │ │ ├── Fn.java │ │ │ ├── Op.java │ │ │ ├── ConstantPoolPatch.java │ │ │ ├── Expr.java │ │ │ ├── Type.java │ │ │ ├── Linker.java │ │ │ ├── Parser.java │ │ │ ├── TypeInferer.java │ │ │ ├── RT.java │ │ │ └── Generator.java │ └── Main.java ├── build.xml └── script.ebnf ├── lib ├── src.zip └── asm-debug-all-5.0.3.jar ├── .gitignore ├── .project ├── .classpath ├── src └── com │ └── github │ └── forax │ └── vmboiler │ ├── rt │ ├── OptimisticError.java │ └── RT.java │ ├── Type.java │ ├── Value.java │ ├── Var.java │ ├── Constant.java │ └── CodeGen.java ├── test ├── src │ └── test │ │ ├── GCDRT.java │ │ ├── FiboRT.java │ │ ├── ExampleRT.java │ │ ├── GCDGen.java │ │ ├── FiboGen.java │ │ └── Example.java └── getting-started.md ├── .settings └── org.eclipse.jdt.core.prefs ├── README.md └── LICENSE /script/.gitignore: -------------------------------------------------------------------------------- 1 | /script.jar 2 | -------------------------------------------------------------------------------- /lib/src.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/vmboiler/HEAD/lib/src.zip -------------------------------------------------------------------------------- /script/samples/hello.script: -------------------------------------------------------------------------------- 1 | fn (main: 2 | print('hello world')) 3 | -------------------------------------------------------------------------------- /script/samples/bignum.script: -------------------------------------------------------------------------------- 1 | fn (main: 2 | print(2 * 123456789123456789) 3 | ) 4 | -------------------------------------------------------------------------------- /script/build-lib/tatoo.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/vmboiler/HEAD/script/build-lib/tatoo.jar -------------------------------------------------------------------------------- /lib/asm-debug-all-5.0.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/vmboiler/HEAD/lib/asm-debug-all-5.0.3.jar -------------------------------------------------------------------------------- /script/lib/tatoo-runtime.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/vmboiler/HEAD/script/lib/tatoo-runtime.jar -------------------------------------------------------------------------------- /script/samples/fibo_empty.script: -------------------------------------------------------------------------------- 1 | fn (fibo n: 2 | 1836311903) 3 | 4 | fn (main: 5 | print(fibo(45)) 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /script/benchmarks/fibo.js: -------------------------------------------------------------------------------- 1 | function fibo(n) { 2 | return (n < 2)? 1: fibo(n - 1) + fibo(n - 2) 3 | } 4 | 5 | print(fibo(45)) 6 | 7 | -------------------------------------------------------------------------------- /script/build-lib/motocity/motocity-tatoo.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forax/vmboiler/HEAD/script/build-lib/motocity/motocity-tatoo.jar -------------------------------------------------------------------------------- /script/samples/fun.script: -------------------------------------------------------------------------------- 1 | fn (identity x: x) 2 | 3 | fn (main: 4 | a = identity(1) 5 | b = identity('foo') 6 | print(a) 7 | print(b) 8 | ) 9 | -------------------------------------------------------------------------------- /script/benchmarks/add1.script: -------------------------------------------------------------------------------- 1 | fn (add1 n: 2 | n + 1) 3 | 4 | fn (main: 5 | print(add1(1)) 6 | print(add1(2147483647)) 7 | ) 8 | 9 | 10 | -------------------------------------------------------------------------------- /script/samples/fibo.script: -------------------------------------------------------------------------------- 1 | fn (fibo n: 2 | if(n < 2 3 | 1 4 | fibo(n - 1) + fibo(n - 2))) 5 | 6 | fn (main: 7 | print(fibo(45)) 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /script/samples/while.script: -------------------------------------------------------------------------------- 1 | fn (main: 2 | i = 0 3 | while(i < 10 4 | print(i) 5 | i = i + 1 6 | ) 7 | print('done !') 8 | ) 9 | 10 | 11 | -------------------------------------------------------------------------------- /script/samples/fibo_overflow.script: -------------------------------------------------------------------------------- 1 | fn (fibo n: 2 | if(n < 2 3 | 1 4 | fibo(n - 1) + fibo(n - 2))) 5 | 6 | fn (main: 7 | print(fibo(46)) 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /script/samples/gcd.script: -------------------------------------------------------------------------------- 1 | fn (gcd a, b: 2 | while (a != b 3 | if (a > b 4 | a = a - b 5 | b = b - a)) 6 | a) 7 | 8 | fn (main: 9 | print(gcd(3, 9)) 10 | ) 11 | 12 | 13 | -------------------------------------------------------------------------------- /script/samples/block.script: -------------------------------------------------------------------------------- 1 | fn (test: 2 | 'this line should be removed, but not the next one' 3 | 'done' 4 | ) 5 | 6 | fn (main: 7 | test() # call with void as return type ? 8 | 'done' 9 | ) 10 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/parser/VersionEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.parser; 2 | 3 | /** 4 | * This class is generated - please do not edit it 5 | */ 6 | public enum VersionEnum { 7 | DEFAULT 8 | ; 9 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # jar and zips generated 4 | vmboiler-* 5 | 6 | # remove log file and assembly dump file 7 | log* 8 | 9 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 10 | hs_err_pid* 11 | /output/ 12 | -------------------------------------------------------------------------------- /script/benchmarks/Fibo.java: -------------------------------------------------------------------------------- 1 | public class Fibo { 2 | private static int fibo(int n) { 3 | return (n < 2)? 1: fibo(n - 1) + fibo(n - 2); 4 | } 5 | public static void main(String[] args) { 6 | System.out.println(fibo(/*46*/ 45)); 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /script/benchmarks/gcd.script: -------------------------------------------------------------------------------- 1 | fn (gcd a, b: 2 | while (a != b 3 | if (a > b 4 | a = a - b 5 | b = b - a)) 6 | a) 7 | 8 | fn (main: 9 | i = 0 10 | sum = 0 11 | while(i < 100000 12 | sum = sum + gcd(12, 9) 13 | i = i + 1) 14 | print(sum)) 15 | 16 | 17 | -------------------------------------------------------------------------------- /script/benchmarks/print_starts.script: -------------------------------------------------------------------------------- 1 | fn (printStarts n: 2 | i = 0 3 | while(i < n 4 | i = i + 1 5 | ) 6 | i) 7 | 8 | fn (main: 9 | i = 0 10 | sum = 0 11 | while(i < 100000 12 | sum = sum + printStarts(10) 13 | i = i + 1) 14 | print(sum)) 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /script/benchmarks/gcd.js: -------------------------------------------------------------------------------- 1 | function gcd (a, b) { 2 | while (a != b) { 3 | if (a > b) { 4 | a = a - b 5 | } else { 6 | b = b - a 7 | } 8 | } 9 | return a; 10 | } 11 | 12 | function main() { 13 | i = 0 14 | sum = 0 15 | while(i < 100000) { 16 | sum = sum + gcd(12, 9) 17 | i = i + 1 18 | } 19 | print(sum) 20 | } 21 | 22 | main() 23 | 24 | 25 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/parser/NonTerminalEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.parser; 2 | 3 | /** 4 | * This class is generated - please do not edit it 5 | */ 6 | public enum NonTerminalEnum { 7 | script, 8 | fun, 9 | expr, 10 | fun_star_0, 11 | id_star_1, 12 | expr_star_2, 13 | expr_star_3, 14 | expr_star_4, 15 | expr_star_5 16 | ; 17 | } -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Script.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | public class Script { 7 | private final List funs; 8 | 9 | public Script(List funs) { 10 | this.funs = Objects.requireNonNull(funs); 11 | } 12 | 13 | public List funs() { 14 | return funs; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | vmboiler2 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /script/benchmarks/Gcd.java: -------------------------------------------------------------------------------- 1 | class Gcd { 2 | static int gcd (int a, int b) { 3 | while (a != b) { 4 | if (a > b) { 5 | a = a - b; 6 | } else { 7 | b = b - a; 8 | } 9 | } 10 | return a; 11 | } 12 | 13 | public static void main(String[] args) { 14 | int i = 0; 15 | int sum = 0; 16 | while(i < 100000) { 17 | sum = sum + gcd(12, 9); 18 | i = i + 1; 19 | } 20 | System.out.println(sum); 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/parser/TerminalEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.parser; 2 | 3 | /** 4 | * This class is generated - please do not edit it 5 | */ 6 | public enum TerminalEnum { 7 | assign, 8 | colon, 9 | eol, 10 | lpar, 11 | rpar, 12 | add, 13 | sub, 14 | mul, 15 | div, 16 | rem, 17 | eq, 18 | ne, 19 | lt, 20 | le, 21 | gt, 22 | ge, 23 | fn, 24 | if_, 25 | while_, 26 | text, 27 | integer, 28 | id, 29 | __eof__ 30 | ; 31 | } -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/lexer/RuleEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.lexer; 2 | 3 | /** 4 | * This class is generated - please do not edit it 5 | */ 6 | public enum RuleEnum { 7 | assign, 8 | colon, 9 | eol, 10 | lpar, 11 | rpar, 12 | add, 13 | sub, 14 | mul, 15 | div, 16 | rem, 17 | eq, 18 | ne, 19 | lt, 20 | le, 21 | gt, 22 | ge, 23 | fn, 24 | if_, 25 | while_, 26 | text, 27 | integer, 28 | id, 29 | space, 30 | comment; 31 | } 32 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Binding.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.Objects; 4 | 5 | public class Binding { 6 | private Type type; 7 | 8 | public Binding(Type type) { 9 | this.type = Objects.requireNonNull(type); 10 | } 11 | 12 | public Type type() { 13 | return type; 14 | } 15 | 16 | public void type(Type type) { 17 | this.type = type; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "binding(" + type + ')'; 23 | } 24 | } -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Main.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | import java.io.FileReader; 3 | import java.io.InputStreamReader; 4 | import java.io.Reader; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodType; 7 | 8 | public class Main { 9 | public static void main(String[] args) throws Throwable { 10 | Reader reader; 11 | if (args.length>0) { 12 | reader = new FileReader(args[0]); 13 | } else { 14 | reader = new InputStreamReader(System.in); 15 | } 16 | Script script = Parser.parse(reader); 17 | Linker linker = new Linker(script); 18 | MethodHandle main = linker.getCallSite("main", MethodType.methodType(Object.class)).getTarget(); 19 | main.invoke(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Visitor.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.HashMap; 4 | import java.util.function.BiFunction; 5 | 6 | public class Visitor { 7 | private final HashMap, BiFunction> map = new HashMap<>(); 8 | 9 | public Visitor when(Class type, BiFunction function) { 10 | map.put(type, (value, param) -> function.apply(type.cast(value), param)); 11 | return this; 12 | } 13 | 14 | public R call(Object value, P param) { 15 | return map.getOrDefault(value.getClass(), 16 | (v, p) -> { throw new IllegalStateException("no function for " + v.getClass().getName()); }) 17 | .apply(value, param); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/parser/ProductionEnum.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.parser; 2 | 3 | /** 4 | * This class is generated - please do not edit it 5 | */ 6 | public enum ProductionEnum { 7 | fun_star_0_empty, 8 | fun_star_0_rec, 9 | script, 10 | id_star_1_empty, 11 | id_star_1_rec, 12 | expr_star_2_empty, 13 | expr_star_2_rec, 14 | fun, 15 | expr_integer, 16 | expr_text, 17 | expr_star_3_empty, 18 | expr_star_3_rec, 19 | expr_block, 20 | expr_var_access, 21 | expr_var_assignment, 22 | expr_star_4_empty, 23 | expr_star_4_rec, 24 | expr_call, 25 | expr_if, 26 | expr_star_5_empty, 27 | expr_star_5_rec, 28 | expr_while, 29 | expr_mul, 30 | expr_div, 31 | expr_rem, 32 | expr_add, 33 | expr_sub, 34 | expr_eq, 35 | expr_ne, 36 | expr_lt, 37 | expr_le, 38 | expr_gt, 39 | expr_ge 40 | ; 41 | } -------------------------------------------------------------------------------- /script/src/Main.java: -------------------------------------------------------------------------------- 1 | import java.io.FileReader; 2 | import java.io.InputStreamReader; 3 | import java.io.Reader; 4 | import java.lang.invoke.MethodHandle; 5 | import java.lang.invoke.MethodType; 6 | 7 | import com.github.forax.vmboiler.sample.script.Linker; 8 | import com.github.forax.vmboiler.sample.script.Parser; 9 | import com.github.forax.vmboiler.sample.script.Script; 10 | 11 | public class Main { 12 | public static void main(String[] args) throws Throwable { 13 | Reader reader; 14 | if (args.length>0) { 15 | reader = new FileReader(args[0]); 16 | } else { 17 | reader = new InputStreamReader(System.in); 18 | } 19 | Script script = Parser.parse(reader); 20 | Linker linker = new Linker(script); 21 | MethodHandle main = linker.getCallSite("main", MethodType.methodType(Object.class)).getTarget(); 22 | main.invoke(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Fn.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | 6 | import com.github.forax.vmboiler.sample.script.Expr.Block; 7 | import com.github.forax.vmboiler.sample.script.Expr.Parameter; 8 | 9 | public class Fn { 10 | private final String name; 11 | private final List parameters; 12 | private final Block block; 13 | 14 | public Fn(String name, List parameters, Block block) { 15 | this.name = Objects.requireNonNull(name); 16 | this.parameters = Objects.requireNonNull(parameters); 17 | this.block = Objects.requireNonNull(block); 18 | } 19 | 20 | public String name() { 21 | return name; 22 | } 23 | public List parameters() { 24 | return parameters; 25 | } 26 | public Block block() { 27 | return block; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/rt/OptimisticError.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.rt; 2 | 3 | /** 4 | * An exception that must be thrown is the return type 5 | * doesn't allow to store the return value. 6 | */ 7 | public final class OptimisticError extends Error { 8 | private static final long serialVersionUID = 9149565412323824897L; 9 | 10 | private final Object value; 11 | 12 | private OptimisticError(Object value) { 13 | super(null, null, false, false); 14 | this.value = value; 15 | } 16 | 17 | /** 18 | * Returns the return value of the method call. 19 | * @return the return value of the method call. 20 | */ 21 | // called by generated code 22 | public Object value() { 23 | return value; 24 | } 25 | 26 | /** 27 | * Create a new {@link OptimisticError}. 28 | * @param value the return value of the method call. 29 | * @return a newly craeted {@link OptimisticError}. 30 | */ 31 | // called by generated code 32 | public static OptimisticError newOptimisticError(Object value) { 33 | return new OptimisticError(value); 34 | } 35 | } -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Op.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.function.BinaryOperator; 4 | 5 | public enum Op { 6 | add(NUMERIC()), sub(NUMERIC()), mul(NUMERIC()), div(NUMERIC()), rem(NUMERIC()), 7 | eq(TEST()), ne(TEST()), lt(TEST()), le(TEST()), gt(TEST()), ge(TEST()) 8 | ; 9 | 10 | private final BinaryOperator returnType; 11 | 12 | private Op(BinaryOperator returnType) { 13 | this.returnType = returnType; 14 | } 15 | 16 | public BinaryOperator returnTypeOp() { 17 | return returnType; 18 | } 19 | 20 | private static final BinaryOperator TEST() { 21 | return (t1, t2) -> Type.BOOL; 22 | } 23 | 24 | private static final BinaryOperator NUMERIC() { 25 | return (t1, t2) -> { 26 | Type erased1 = t1.erase(); 27 | Type erased2 = t1.erase(); 28 | if (erased1 == Type.INT && erased2 == Type.INT) { 29 | return Type.MIXED_INT; 30 | } 31 | if (erased1 == Type.NUM || erased2 == Type.NUM) { 32 | return Type.NUM.mix(t1.isMixed() || t2.isMixed()); 33 | } 34 | return Type.OBJECT; 35 | }; 36 | } 37 | } -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/ConstantPoolPatch.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.Arrays; 4 | 5 | import org.objectweb.asm.ClassWriter; 6 | 7 | public class ConstantPoolPatch { 8 | private final ClassWriter writer; 9 | private int[] indexes = new int[32]; 10 | private Object[] values = new Object[32]; 11 | private int size; 12 | 13 | public ConstantPoolPatch(ClassWriter writer) { 14 | this.writer = writer; 15 | } 16 | 17 | public String encode(Object o) { 18 | if (size == indexes.length) { 19 | indexes = Arrays.copyOf(indexes, size << 1); 20 | values = Arrays.copyOf(values, size << 1); 21 | } 22 | String mangled = "<>"; 23 | int index = writer.newConst(mangled); 24 | indexes[size] = index; 25 | values[size] = o; 26 | size++; 27 | return mangled; 28 | } 29 | 30 | public Object[] createPatchArray() { 31 | int constantPoolSize = writer.newConst("<>"); 32 | Object[] patches = new Object[constantPoolSize]; 33 | for(int i = 0; i < indexes.length; i++) { 34 | patches[indexes[i]] = values[i]; 35 | } 36 | return patches; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/src/test/GCDRT.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.lang.invoke.CallSite; 4 | import java.lang.invoke.ConstantCallSite; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.invoke.MethodType; 9 | import java.util.Arrays; 10 | 11 | @SuppressWarnings("unused") 12 | public class GCDRT { 13 | private static int sub(int a, int b) { 14 | return a - b; 15 | } 16 | private static boolean ne(int a, int b) { 17 | return a != b; 18 | } 19 | private static boolean gt(int a, int b) { 20 | return a > b; 21 | } 22 | 23 | public static CallSite bsm(Lookup lookup, String name, MethodType methodType) throws Throwable { 24 | System.out.println("GCDRT.bsm " + lookup + " " + name + methodType); 25 | MethodHandle target = MethodHandles.lookup().findStatic(GCDRT.class, name, methodType); 26 | return new ConstantCallSite(target); 27 | } 28 | 29 | public static boolean deopt_args(Lookup lookup, String name, MethodType methodType, Object[] values) throws Throwable { 30 | System.out.println("deopt args " + Arrays.toString(values)); 31 | return false; 32 | } 33 | 34 | public static boolean deopt_ret(Lookup lookup, String name, MethodType methodType, Object value) throws Throwable { 35 | System.out.println("deopt return " + value); 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/src/test/FiboRT.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.lang.invoke.CallSite; 4 | import java.lang.invoke.ConstantCallSite; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.invoke.MethodType; 9 | import java.util.Arrays; 10 | 11 | import com.github.forax.vmboiler.rt.OptimisticError; 12 | 13 | @SuppressWarnings("unused") 14 | public class FiboRT { 15 | private static int add(int a, int b) { 16 | throw OptimisticError.newOptimisticError(a + b); 17 | //return a + b; 18 | } 19 | private static int add(Object a, Object b) { 20 | throw OptimisticError.newOptimisticError(((Integer)a) + (Integer)b); 21 | } 22 | private static int sub(int a, int b) { 23 | return a - b; 24 | } 25 | private static boolean lt(int a, int b) { 26 | return a < b; 27 | } 28 | 29 | public static CallSite bsm(Lookup lookup, String name, MethodType methodType) throws Throwable { 30 | //System.out.println("FiboRT.bsm " + lookup + " " + name + methodType); 31 | Lookup l = (name.equals("fibo"))? lookup: MethodHandles.lookup(); 32 | MethodHandle target = l.findStatic(l.lookupClass(), name, methodType); 33 | return new ConstantCallSite(target); 34 | } 35 | 36 | public static boolean deopt_args(Lookup lookup, String name, MethodType methodType, Object[] values) throws Throwable { 37 | System.out.println("deopt args " + Arrays.toString(values)); 38 | return false; 39 | } 40 | 41 | public static boolean deopt_return(Lookup lookup, String name, MethodType methodType, Object value) throws Throwable { 42 | System.out.println("deopt return " + value); 43 | return false; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.8 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.doc.comment.support=enabled 11 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 12 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 13 | org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error 14 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled 15 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled 16 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled 17 | org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=private 18 | org.eclipse.jdt.core.compiler.problem.missingJavadocComments=error 19 | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled 20 | org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=protected 21 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=all_standard_tags 22 | org.eclipse.jdt.core.compiler.problem.missingJavadocTags=error 23 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled 24 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled 25 | org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=protected 26 | org.eclipse.jdt.core.compiler.source=1.8 27 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/tools/TerminalEvaluator.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.tools; 2 | 3 | 4 | /** 5 | * @param data type passed by the lexer listener. 6 | * 7 | * This class is generated - please do not edit it 8 | */ 9 | public interface TerminalEvaluator { 10 | /** This method is called when the rule integer is recognized by the lexer. 11 | * @param data the data sent by the lexer, in general, the 12 | * {@link fr.umlv.tatoo.runtime.buffer.TokenBuffer#view a view of the token buffer} or the buffer itself. 13 | 14 | * @return the value associated with the terminal spawn for the rule. 15 | */ 16 | public Object integer(D data); 17 | /** This method is called when the rule id is recognized by the lexer. 18 | * @param data the data sent by the lexer, in general, the 19 | * {@link fr.umlv.tatoo.runtime.buffer.TokenBuffer#view a view of the token buffer} or the buffer itself. 20 | 21 | * @return the value associated with the terminal spawn for the rule. 22 | */ 23 | public String id(D data); 24 | /** This method is called when the rule comment is recognized by the lexer. 25 | * @param data the data sent by the lexer, in general, the 26 | * {@link fr.umlv.tatoo.runtime.buffer.TokenBuffer#view a view of the token buffer} or the buffer itself. 27 | */ 28 | public void comment(D data); 29 | /** This method is called when the rule text is recognized by the lexer. 30 | * @param data the data sent by the lexer, in general, the 31 | * {@link fr.umlv.tatoo.runtime.buffer.TokenBuffer#view a view of the token buffer} or the buffer itself. 32 | 33 | * @return the value associated with the terminal spawn for the rule. 34 | */ 35 | public String text(D data); 36 | } 37 | -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/Type.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler; 2 | 3 | /** 4 | * Type of the {@link Value values} used by {@link CodeGen}. 5 | */ 6 | public interface Type { 7 | /** 8 | * Returns true is the current type both an object and a primitive type 9 | * @return true is the current type represent either a primitive or an object. 10 | */ 11 | boolean isMixed(); 12 | 13 | /** 14 | * Returns the descriptor of the type using the class file format. 15 | * If the type is mixed, the VM type is the type of the primitive part of the 16 | * mixed type. 17 | * It can be a {@link #VM_VOID}, {@link #VM_BOOLEAN}, a {@link #VM_BYTE}, 18 | * a {@link #VM_CHAR}, a {@link #VM_SHORT}, a {@link #VM_INT}, 19 | * a {@link #VM_LONG}, a {@link #VM_FLOAT}, a {@link #VM_DOUBLE}, 20 | * a {@link #VM_OBJECT} or a user defined type if it's an object type. 21 | * @return the descriptor of the type using the class file format. 22 | */ 23 | String vmType(); 24 | 25 | /** The descriptor of void. */ 26 | public static final String VM_VOID = "V"; 27 | /** The descriptor of a boolean. */ 28 | public static final String VM_BOOLEAN = "Z"; 29 | /** The descriptor of a byte. */ 30 | public static final String VM_BYTE = "B"; 31 | /** The descriptor of a char. */ 32 | public static final String VM_CHAR = "C"; 33 | /** The descriptor of a short. */ 34 | public static final String VM_SHORT = "S"; 35 | /** The descriptor of an integer. */ 36 | public static final String VM_INT = "I"; 37 | /** The descriptor of a long. */ 38 | public static final String VM_LONG = "J"; 39 | /** The descriptor of a float. */ 40 | public static final String VM_FLOAT = "F"; 41 | /** The descriptor of a double. */ 42 | public static final String VM_DOUBLE = "D"; 43 | /** The descriptor of an object. */ 44 | public static final String VM_OBJECT = "Ljava/lang/Object;"; 45 | } -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/Value.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler; 2 | 3 | import static com.github.forax.vmboiler.Type.VM_DOUBLE; 4 | import static com.github.forax.vmboiler.Type.VM_LONG; 5 | import static com.github.forax.vmboiler.Type.VM_VOID; 6 | import static org.objectweb.asm.Opcodes.*; 7 | 8 | import java.util.Objects; 9 | 10 | import org.objectweb.asm.MethodVisitor; 11 | 12 | /** 13 | * Base class that represents either {@link Var a virtual register} or 14 | * a {@link Constant a side effect free constant}. 15 | * 16 | * Values are used by the {@link CodeGen} operations. 17 | */ 18 | public abstract class Value { 19 | private final Type type; 20 | 21 | Value(Type type) { 22 | this.type = Objects.requireNonNull(type); 23 | } 24 | 25 | /** 26 | * Returns the type of the current value. 27 | * @return the type of the current value. 28 | */ 29 | public Type type() { 30 | return type; 31 | } 32 | 33 | abstract void loadAll(MethodVisitor mv); 34 | 35 | abstract void loadPrimitive(MethodVisitor mv); 36 | 37 | static int loadOpcode(String vmType) { 38 | return LOAD_OPS[vmType.charAt(0) - 'B']; 39 | } 40 | static int storeOpcode(String vmType) { 41 | return STORE_OPS[vmType.charAt(0) - 'B']; 42 | } 43 | static int returnOpcode(String vmType) { 44 | return RETURN_OPS[vmType.charAt(0) - 'B']; 45 | } 46 | static int size(String vmType) { 47 | return (vmType == VM_LONG || vmType == VM_DOUBLE)?2: (vmType == VM_VOID)? 0: 1; 48 | } 49 | 50 | private static final int[] LOAD_OPS = { ILOAD , ILOAD , DLOAD , 0, FLOAD , 0, 0, ILOAD , LLOAD , 0, ALOAD , 0, 0, 0, 0, 0, 0, ILOAD , 0, 0, 0 , 0, 0, 0, ILOAD }; 51 | private static final int[] STORE_OPS = { ISTORE , ISTORE , DSTORE , 0, FSTORE , 0, 0, ISTORE , LSTORE , 0, ASTORE , 0, 0, 0, 0, 0, 0, ISTORE , 0, 0, 0 , 0, 0, 0, ISTORE }; 52 | private static final int[] RETURN_OPS = { IRETURN, IRETURN, DRETURN, 0, FRETURN, 0, 0, IRETURN, LRETURN, 0, ARETURN, 0, 0, 0, 0, 0, 0, IRETURN, 0, 0, RETURN, 0, 0, 0, IRETURN }; 53 | } -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/tools/ToolsDataTable.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.tools; 2 | 3 | import fr.umlv.tatoo.runtime.tools.ToolsTable; 4 | 5 | import java.util.EnumMap; 6 | import java.util.EnumSet; 7 | 8 | import com.github.forax.vmboiler.sample.script.lexer.RuleEnum; 9 | import com.github.forax.vmboiler.sample.script.parser.TerminalEnum; 10 | 11 | public class ToolsDataTable { 12 | public static ToolsTable createToolsTable() { 13 | EnumSet spawns = EnumSet.of(RuleEnum.assign,RuleEnum.mul,RuleEnum.ne,RuleEnum.if_,RuleEnum.gt,RuleEnum.fn,RuleEnum.lpar,RuleEnum.add,RuleEnum.lt,RuleEnum.colon,RuleEnum.div,RuleEnum.ge,RuleEnum.integer,RuleEnum.rpar,RuleEnum.rem,RuleEnum.le,RuleEnum.id,RuleEnum.comment,RuleEnum.eol,RuleEnum.text,RuleEnum.eq,RuleEnum.sub,RuleEnum.while_); 14 | EnumSet discards = EnumSet.allOf(RuleEnum.class); 15 | EnumMap terminal = new EnumMap(RuleEnum.class); 16 | terminal.put(RuleEnum.assign,TerminalEnum.assign); 17 | terminal.put(RuleEnum.mul,TerminalEnum.mul); 18 | terminal.put(RuleEnum.ne,TerminalEnum.ne); 19 | terminal.put(RuleEnum.if_,TerminalEnum.if_); 20 | terminal.put(RuleEnum.gt,TerminalEnum.gt); 21 | terminal.put(RuleEnum.fn,TerminalEnum.fn); 22 | terminal.put(RuleEnum.lpar,TerminalEnum.lpar); 23 | terminal.put(RuleEnum.add,TerminalEnum.add); 24 | terminal.put(RuleEnum.lt,TerminalEnum.lt); 25 | terminal.put(RuleEnum.colon,TerminalEnum.colon); 26 | terminal.put(RuleEnum.div,TerminalEnum.div); 27 | terminal.put(RuleEnum.ge,TerminalEnum.ge); 28 | terminal.put(RuleEnum.integer,TerminalEnum.integer); 29 | terminal.put(RuleEnum.rpar,TerminalEnum.rpar); 30 | terminal.put(RuleEnum.rem,TerminalEnum.rem); 31 | terminal.put(RuleEnum.le,TerminalEnum.le); 32 | terminal.put(RuleEnum.id,TerminalEnum.id); 33 | terminal.put(RuleEnum.eol,TerminalEnum.eol); 34 | terminal.put(RuleEnum.text,TerminalEnum.text); 35 | terminal.put(RuleEnum.eq,TerminalEnum.eq); 36 | terminal.put(RuleEnum.sub,TerminalEnum.sub); 37 | terminal.put(RuleEnum.while_,TerminalEnum.while_); 38 | EnumSet unconditionals = EnumSet.of(RuleEnum.comment,RuleEnum.space); 39 | return new ToolsTable(spawns,discards,unconditionals,terminal); 40 | } 41 | } -------------------------------------------------------------------------------- /script/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /script/script.ebnf: -------------------------------------------------------------------------------- 1 | directives: 2 | autoalias 3 | 4 | imports: 5 | java.util.List 6 | com.github.forax.vmboiler.sample.script.Expr 7 | com.github.forax.vmboiler.sample.script.Fn 8 | 9 | priorities: 10 | mult = 6 left 11 | plus = 5 left 12 | test = 4 left 13 | lpar = 3 left 14 | id = 2 left 15 | assign = 1 right 16 | 17 | tokens: 18 | assign = '=' [assign] 19 | colon= ':' 20 | eol = '\n' 21 | lpar = '\(' [lpar] 22 | rpar = '\)' 23 | add = '\+' [plus] 24 | sub = '-' [plus] 25 | mul = '\*' [mult] 26 | div = '\/' [mult] 27 | rem = '%' [mult] 28 | eq = '==' [test] 29 | ne = '!=' [test] 30 | lt = '<' [test] 31 | le = '<=' [test] 32 | gt = '>' [test] 33 | ge = '>=' [test] 34 | fn = 'fn' 35 | if_ = 'if' 36 | while_ = 'while' 37 | 38 | text = "'[^']*'" 39 | integer = "[0-9]+" 40 | id = "[^ \t\r\n=:,();]+" [id] 41 | 42 | blanks: 43 | space = "( |\t|\r|\n|,|;)+" 44 | 45 | comments: 46 | comment = "#([^\r\n])*(\r)?\n" 47 | 48 | types: 49 | 'id': String 50 | 'integer': Object 51 | 'text': String 52 | expr: Expr 53 | fun: Fn 54 | 55 | starts: 56 | script 57 | 58 | productions: 59 | script = fun* { script } 60 | ; 61 | 62 | fun = 'fn' '(' 'id' 'id'* ':' expr* ')' { fun } 63 | ; 64 | 65 | expr = 'integer' { expr_integer } 66 | | 'text' { expr_text } 67 | | '(' expr* ')' { expr_block } 68 | | 'id' [id] { expr_var_access } 69 | | 'id' '=' expr [assign] { expr_var_assignment } 70 | | 'id' '(' expr* ')' { expr_call } 71 | | 'if' '(' expr expr expr ')' { expr_if } 72 | | 'while' '(' expr expr* ')' { expr_while } 73 | 74 | | expr '*' expr [mult] { expr_mul } 75 | | expr '/' expr [mult] { expr_div } 76 | | expr 'rem' expr [mult] { expr_rem } 77 | | expr '+' expr [plus] { expr_add } 78 | | expr '-' expr [plus] { expr_sub } 79 | | expr '==' expr [test] { expr_eq } 80 | | expr '!=' expr [test] { expr_ne } 81 | | expr '<' expr [test] { expr_lt } 82 | | expr '<=' expr [test] { expr_le } 83 | | expr '>' expr [test] { expr_gt } 84 | | expr '>=' expr [test] { expr_ge } 85 | ; 86 | 87 | -------------------------------------------------------------------------------- /test/src/test/ExampleRT.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.lang.invoke.CallSite; 4 | import java.lang.invoke.ConstantCallSite; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.invoke.MethodType; 9 | import java.math.BigInteger; 10 | import java.util.Arrays; 11 | 12 | import com.github.forax.vmboiler.rt.OptimisticError; 13 | 14 | public class ExampleRT { 15 | @SuppressWarnings("unused") 16 | private static int add(int a, int b) { 17 | try { 18 | return Math.addExact(a, b); 19 | } catch(ArithmeticException e) { 20 | throw OptimisticError.newOptimisticError(add((Object)a, b)); 21 | } 22 | } 23 | @SuppressWarnings("unused") 24 | private static Object add(Object a, Object b) { 25 | return asBigInt(a).add(asBigInt(b)); 26 | } 27 | private static BigInteger asBigInt(Object o) { 28 | if (o instanceof Integer) { 29 | return BigInteger.valueOf((Integer)o); 30 | } 31 | return (BigInteger)o; 32 | } 33 | 34 | private static int convert(Object result) { 35 | if (result instanceof BigInteger) { 36 | throw OptimisticError.newOptimisticError(result); 37 | } 38 | return (Integer)result; 39 | } 40 | 41 | public static CallSite bsm(Lookup lookup, String name, MethodType methodType) throws Throwable { 42 | System.out.println("Example.bsm " + lookup + " " + name + methodType); 43 | boolean exactMatch = 44 | methodType.returnType() == int.class && 45 | methodType.parameterType(0) == int.class && 46 | methodType.parameterType(1) == int.class; 47 | MethodType lookupType; 48 | MethodHandle target = MethodHandles.lookup() 49 | .findStatic(ExampleRT.class, name, exactMatch?methodType: methodType.generic()); 50 | if (!exactMatch) { 51 | target = MethodHandles.filterReturnValue(target, CONVERT).asType(methodType); 52 | } 53 | return new ConstantCallSite(target); 54 | } 55 | 56 | public static boolean deopt_args(Lookup lookup, String name, MethodType methodType, Object[] values, String parameterNames) throws Throwable { 57 | System.out.println("deopt args " + lookup + " " + parameterNames + " " + Arrays.toString(values)); 58 | return false; 59 | } 60 | 61 | public static boolean deopt_ret(Lookup lookup, String name, MethodType methodType, Object value, String parameterNames) throws Throwable { 62 | System.out.println("deopt return " + lookup + " " + value); 63 | return false; 64 | } 65 | 66 | private static final MethodHandle CONVERT; 67 | static { 68 | try { 69 | CONVERT = MethodHandles.lookup().findStatic(ExampleRT.class, "convert", MethodType.methodType(int.class, Object.class)); 70 | } catch (NoSuchMethodException | IllegalAccessException e) { 71 | throw new AssertionError(e); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/Var.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler; 2 | 3 | import static org.objectweb.asm.Opcodes.ALOAD; 4 | import static org.objectweb.asm.Opcodes.ASTORE; 5 | 6 | import org.objectweb.asm.MethodVisitor; 7 | 8 | /** 9 | * A variable with an optional name and a type. 10 | * A variable has also a slot, i.e. an index into the local variable table 11 | * but this slot is intended to be used by {@link CodeGen} only. 12 | * 13 | * This class can be inherited. 14 | * 15 | * @see CodeGen#createVar(Type, java.util.function.Function) 16 | */ 17 | public class Var extends Value { 18 | private /*almost final*/ int slot; 19 | 20 | /** 21 | * Marker value to indicate that the variable is stack allocated. 22 | */ 23 | public static final int STACK_ALLOCATED = -1; 24 | 25 | /** 26 | * Initialize a variable 27 | * @param type the type of the variable. 28 | */ 29 | protected Var(Type type) { 30 | super(type); 31 | this.slot = STACK_ALLOCATED; 32 | } 33 | 34 | void injectSlot(int slot) { 35 | if (this.slot != STACK_ALLOCATED) { 36 | throw new IllegalStateException("the variable already have a slot "); 37 | } 38 | this.slot = slot; 39 | } 40 | 41 | /** 42 | * Returns the slot of the current variable. 43 | * This value is intended to be used by {@link CodeGen} only. 44 | * @return the slot of the current variable or STACK_ALLOCATED 45 | * if the variable is stack allocated. 46 | */ 47 | public int slot() { 48 | return slot; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return "var(" + type() + " " + ((slot == STACK_ALLOCATED)? "stack allocated": slot) + ')'; 54 | } 55 | 56 | @Override 57 | void loadPrimitive(MethodVisitor mv) { 58 | Type type = type(); 59 | if (type.vmType() == Type.VM_VOID) { 60 | return; 61 | } 62 | int slot = this.slot; 63 | if (this.slot == STACK_ALLOCATED) { 64 | // do nothing, the result is already on stack 65 | return; 66 | } 67 | mv.visitVarInsn(loadOpcode(type.vmType()), slot + (type.isMixed()? 1: 0)); 68 | } 69 | 70 | @Override 71 | void loadAll(MethodVisitor mv) { 72 | Type type = type(); 73 | if (type.vmType() == Type.VM_VOID) { 74 | return; 75 | } 76 | int slot = this.slot; 77 | if (type.isMixed()) { 78 | mv.visitVarInsn(loadOpcode(type.vmType()), slot + 1); 79 | mv.visitVarInsn(ALOAD, slot); 80 | return; 81 | } 82 | mv.visitVarInsn(loadOpcode(type.vmType()), slot); 83 | } 84 | 85 | void storePrimitive(MethodVisitor mv) { 86 | Type type = type(); 87 | if (type.vmType() == Type.VM_VOID) { 88 | return; 89 | } 90 | int slot = this.slot; 91 | if (slot == STACK_ALLOCATED) { 92 | // do nothing, the result should stay on stack 93 | return; 94 | } 95 | mv.visitVarInsn(storeOpcode(type.vmType()), slot + (type.isMixed()? 1: 0)); 96 | } 97 | 98 | void storeAll(MethodVisitor mv) { 99 | Type type = type(); 100 | if (type.vmType() == Type.VM_VOID) { 101 | return; 102 | } 103 | int slot = this.slot; 104 | if (type.isMixed()) { 105 | mv.visitVarInsn(ASTORE, slot); 106 | mv.visitVarInsn(storeOpcode(type.vmType()), slot + 1); 107 | return; 108 | } 109 | mv.visitVarInsn(storeOpcode(type.vmType()), slot); 110 | } 111 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | vmboiler 2 | ======== 3 | 4 | A small library on top of ASM that generates optimistically typed bytecodes that aim 5 | to ease the implementation of fast dynamically typed language runtime on top of the JVM. 6 | 7 | [Getting Started](test/getting-started.md) 8 | 9 | FAQ 10 | === 11 | 12 | How it works ? 13 | --- 14 | The boiler provides only 8 operations and ask the runtime developer 15 | to map the language semantics to these operations. 16 | There is only 8 operations because all method call, field access, conversion, etc, 17 | are done using one operation called call that leverage invokedynamic to 18 | specify the exact semantics. 19 | Then the boiler ask that runtime developer to provide type annotations as hints 20 | for the 8 operations. These type annotations can be optimistic indicating that a type 21 | can be either a primitive type or an object type (we call it a mixed type). 22 | At runtime, if a value doesn't fit its primitive type anymore, the boiler 23 | has inserted code that will handle that and called two special methods 24 | indicating if an arguments or a return value of a called as the . 25 | Calling these methods allows a runtime to, by example, trap the value that changed 26 | and change the corresponding type profile and then invalidate the code and re-generate 27 | a new one using the updated profile. 28 | 29 | Why it's not awfully slow ? 30 | --- 31 | The idea is to generate more code than the equivalent code in Java but 32 | in a way that JITs can easily optimized in order to generate an assembly 33 | code that is really close to the code generated for the equivalent code in Java. 34 | 35 | Why the API is register based and not stack based like the Java bytecode ? 36 | --- 37 | De-optimization is triggered by throwing an exception (OptimisticError) 38 | and inside an exception handler, the stack is lost so instead of trying to 39 | reconstruct the stack, it's easier to consider that there is no stack. 40 | 41 | Can you compare it to the way Nashorn do deoptimization ? 42 | --- 43 | Nashorn do deoptimization by continuation, i.e. for every points where 44 | a deoptimization can occur it stores all the variables that will be 45 | necessary to restart and the AST node then when a deopt occur, 46 | the runtime generates a new code from the AST node and transfer all the values 47 | to the corresponding variables. This implies that you need to keep the AST in memory, 48 | that the saved AST node is written in a specific way and that a specific analysis 49 | is implemented to know all the variables that are needed to restart a code. 50 | While one can try to make the AST and the analysis independent from a language semantics, 51 | it's harder than just let the VM do the deoptimization. 52 | 53 | Can you compare it to the way Graal/Truffle do deoptimization ? 54 | --- 55 | Truffle uses a modified Hotspot runtime and its own JIT (Graal). 56 | The idea is that a runtime developer write an interpreter with hints 57 | that helps the partial evaluation done by Graal to generate an 58 | heavily optimized assembly code with the same semantics than the interpreter. 59 | The boiler is a code generator, not an interpreter so the code specialization 60 | can be done when generating instead of as a separate pass at compile time 61 | like Truffle-SOM does. The boiler uses invokedynamic to get the values 62 | of the stack frame instead of asking Hotspot these values so it works 63 | great on all Java VMs that have a decent JIT but will not provide agressive 64 | optimisations as Truffle can do because it doesn't seat on top of Graal. 65 | 66 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Expr.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.List; 4 | import java.util.Objects; 5 | import java.util.Optional; 6 | 7 | public interface Expr { 8 | public class Literal implements Expr { 9 | private final Object constant; 10 | 11 | public Literal(Object constant) { 12 | this.constant = Objects.requireNonNull(constant); 13 | } 14 | 15 | public Object constant() { 16 | return constant; 17 | } 18 | } 19 | 20 | public class Block implements Expr { 21 | private final List exprs; 22 | 23 | public Block(List exprs) { 24 | this.exprs = Objects.requireNonNull(exprs); 25 | } 26 | 27 | public List exprs() { 28 | return exprs; 29 | } 30 | } 31 | 32 | public class Parameter implements Expr { 33 | private final String name; 34 | 35 | public Parameter(String name) { 36 | this.name = name; 37 | } 38 | 39 | public String name() { 40 | return name; 41 | } 42 | } 43 | 44 | public class VarAccess implements Expr { 45 | private final String name; 46 | 47 | public VarAccess(String name) { 48 | this.name = Objects.requireNonNull(name); 49 | } 50 | 51 | public String name() { 52 | return name; 53 | } 54 | } 55 | 56 | public class VarAssignment implements Expr { 57 | private final String name; 58 | private final Expr expr; 59 | 60 | public VarAssignment(String name, Expr expr) { 61 | this.name = Objects.requireNonNull(name); 62 | this.expr = Objects.requireNonNull(expr); 63 | } 64 | 65 | public String name() { 66 | return name; 67 | } 68 | public Expr expr() { 69 | return expr; 70 | } 71 | } 72 | 73 | public class Call implements Expr { 74 | private final Op op; 75 | private final String name; 76 | private final List exprs; 77 | 78 | public Call(Op op, String name, List exprs) { 79 | this.op = op; 80 | this.name = Objects.requireNonNull(name); 81 | this.exprs = Objects.requireNonNull(exprs); 82 | } 83 | 84 | public Optional optionalOp() { 85 | return Optional.ofNullable(op); 86 | } 87 | public String name() { 88 | return name; 89 | } 90 | public List exprs() { 91 | return exprs; 92 | } 93 | } 94 | 95 | public class If implements Expr { 96 | private final Expr condition; 97 | private final Expr truePart; 98 | private final Expr falsePart; 99 | 100 | public If(Expr condition, Expr truePart, Expr falsePart) { 101 | this.condition = Objects.requireNonNull(condition); 102 | this.truePart = Objects.requireNonNull(truePart); 103 | this.falsePart = Objects.requireNonNull(falsePart); 104 | } 105 | 106 | public Expr condition() { 107 | return condition; 108 | } 109 | public Expr truePart() { 110 | return truePart; 111 | } 112 | public Expr falsePart() { 113 | return falsePart; 114 | } 115 | } 116 | 117 | public class While implements Expr { 118 | private final Expr condition; 119 | private final Block body; 120 | 121 | public While(Expr condition, Block body) { 122 | this.condition = Objects.requireNonNull(condition); 123 | this.body = Objects.requireNonNull(body); 124 | } 125 | 126 | public Expr condition() { 127 | return condition; 128 | } 129 | public Block body() { 130 | return body; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Type.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.lang.invoke.MethodType; 4 | import java.util.HashMap; 5 | 6 | public enum Type implements com.github.forax.vmboiler.Type { 7 | VOID(VM_VOID), 8 | BOOL(VM_BOOLEAN), 9 | INT(VM_INT), 10 | MIXED_INT(VM_INT), 11 | NUM(VM_DOUBLE), 12 | MIXED_NUM(VM_DOUBLE), 13 | OBJECT(VM_OBJECT); 14 | 15 | private final String vmType; 16 | 17 | private Type(String vmType) { 18 | this.vmType = vmType; 19 | } 20 | 21 | @Override 22 | public boolean isMixed() { 23 | return this == MIXED_INT || this == MIXED_NUM; 24 | } 25 | 26 | @Override 27 | public String vmType() { 28 | return vmType; 29 | } 30 | 31 | public Type erase() { 32 | if (this == MIXED_INT) { 33 | return INT; 34 | } 35 | if (this == MIXED_NUM) { 36 | return NUM; 37 | } 38 | return this; 39 | } 40 | 41 | public Type mix(boolean mix) { 42 | if (mix == false) { 43 | return this; 44 | } 45 | if (this == INT) { 46 | return MIXED_INT; 47 | } 48 | if (this == NUM) { 49 | return MIXED_NUM; 50 | } 51 | return this; 52 | } 53 | 54 | public static Type merge(Type type1, Type type2) { 55 | if (type1 == type2) { 56 | return type1; 57 | } 58 | Type erased1 = type1.erase(); 59 | Type erased2 = type2.erase(); 60 | if (erased1 == erased2) { 61 | return erased1.mix(true); 62 | } 63 | if (erased1 == INT && erased2 == NUM) { 64 | return NUM.mix(type1.isMixed() || type2.isMixed()); 65 | } 66 | if (erased1 == NUM && erased2 == INT) { 67 | return NUM.mix(type1.isMixed() || type2.isMixed()); 68 | } 69 | return OBJECT; 70 | } 71 | 72 | public static Type getTypeFromValue(Object value) { 73 | if (value instanceof Integer) { 74 | return INT; 75 | } 76 | if (value instanceof Boolean) { 77 | return BOOL; 78 | } 79 | if (value instanceof Double) { 80 | return NUM; 81 | } 82 | return OBJECT; 83 | } 84 | 85 | public static Type getTypeFromClass(Class clazz) { 86 | Type type = CLASS_TO_TYPE_MAP.get(clazz); 87 | if (type == null) { 88 | throw new IllegalArgumentException("no type for class " + clazz.getName()); 89 | } 90 | return type; 91 | } 92 | 93 | public static Class getClassFromType(Type type) { 94 | Class clazz = TYPE_TO_CLASS_MAP.get(type); 95 | if (clazz == null) { 96 | throw new IllegalArgumentException("no class for type " + type); 97 | } 98 | return clazz; 99 | } 100 | 101 | private static Class classFromDesc(String descriptor) { // ugly isn't it ? 102 | if (descriptor.equals("V")) { 103 | return void.class; 104 | } 105 | return MethodType.fromMethodDescriptorString('(' + descriptor + ")V", null).parameterType(0); 106 | } 107 | 108 | private static final HashMap, Type> CLASS_TO_TYPE_MAP; 109 | private static final HashMap> TYPE_TO_CLASS_MAP; 110 | static { 111 | HashMap> typeToClassMap = new HashMap<>(); 112 | HashMap, Type> classToTypeMap = new HashMap<>(); 113 | for(Type type: Type.values()) { 114 | if (type.isMixed()) { 115 | continue; // skip mixed type 116 | } 117 | Class clazz = classFromDesc(type.vmType()); 118 | classToTypeMap.put(clazz, type); 119 | typeToClassMap.put(type, clazz); 120 | } 121 | CLASS_TO_TYPE_MAP = classToTypeMap; 122 | TYPE_TO_CLASS_MAP = typeToClassMap; 123 | } 124 | } -------------------------------------------------------------------------------- /test/src/test/GCDGen.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import static org.objectweb.asm.Opcodes.*; 4 | import static test.GCDGen.Types.BOOL; 5 | import static test.GCDGen.Types.INT; 6 | import static test.GCDGen.Types.INT_MIXED; 7 | 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | import java.lang.invoke.CallSite; 11 | import java.lang.invoke.MethodHandles.Lookup; 12 | import java.lang.invoke.MethodType; 13 | import java.nio.file.Files; 14 | import java.nio.file.Paths; 15 | 16 | import org.objectweb.asm.ClassReader; 17 | import org.objectweb.asm.ClassWriter; 18 | import org.objectweb.asm.Handle; 19 | import org.objectweb.asm.Label; 20 | import org.objectweb.asm.MethodVisitor; 21 | import org.objectweb.asm.util.CheckClassAdapter; 22 | 23 | import com.github.forax.vmboiler.CodeGen; 24 | import com.github.forax.vmboiler.Type; 25 | import com.github.forax.vmboiler.Var; 26 | 27 | public class GCDGen { 28 | public enum Types implements Type { 29 | INT, INT_MIXED, BOOL 30 | ; 31 | @Override 32 | public boolean isMixed() { 33 | return this == INT_MIXED; 34 | } 35 | @Override 36 | public String vmType() { 37 | return (this == BOOL)? Type.VM_BOOLEAN: Type.VM_INT; 38 | } 39 | } 40 | 41 | private static final Object[] EMPTY_ARRAY = new Object[0]; 42 | 43 | private static final String GCD_RT = GCDRT.class.getName().replace('.', '/'); 44 | private static final Handle BSM = new Handle(H_INVOKESTATIC, 45 | GCD_RT, "bsm", 46 | MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class).toMethodDescriptorString()); 47 | private static final Handle DEOPT_ARGS = new Handle(H_INVOKESTATIC, 48 | GCD_RT, "deopt_args", 49 | MethodType.methodType(boolean.class, Lookup.class, String.class, MethodType.class, Object[].class).toMethodDescriptorString()); 50 | private static final Handle DEOPT_RET = new Handle(H_INVOKESTATIC, 51 | GCD_RT, "deopt_ret", 52 | MethodType.methodType(boolean.class, Lookup.class, String.class, MethodType.class, Object.class).toMethodDescriptorString()); 53 | 54 | 55 | public static void main(String[] args) throws IOException { 56 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 57 | writer.visit(V1_8, ACC_PUBLIC|ACC_SUPER, "GCD", null, "java/lang/Object", null); 58 | MethodVisitor mv = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "gcd", "(II)I", null, null); 59 | mv.visitCode(); 60 | CodeGen codeGen = new CodeGen(mv, INT_MIXED); 61 | Var x = codeGen.createVar(INT); 62 | Var y = codeGen.createVar(INT); 63 | Var a = codeGen.createVar(INT_MIXED); 64 | codeGen.move(a, x); 65 | Var b = codeGen.createVar(INT_MIXED); 66 | codeGen.move(b, y); 67 | Label loop = new Label(); 68 | codeGen.label(loop); 69 | Var r0 = codeGen.createVar(BOOL); 70 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 71 | r0, "ne", a, b); 72 | Label end = new Label(); 73 | codeGen.jumpIfFalse(r0, end); 74 | Var r1 = codeGen.createVar(BOOL); 75 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 76 | r1, "gt", a, b); 77 | Label otherwise = new Label(); 78 | codeGen.jumpIfFalse(r1, otherwise); 79 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 80 | a, "sub", a, b); 81 | codeGen.jump(loop); 82 | codeGen.label(otherwise); 83 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 84 | b, "sub", b, a); 85 | codeGen.jump(loop); 86 | codeGen.label(end); 87 | codeGen.ret(a); 88 | codeGen.end(); 89 | mv.visitMaxs(-1, -1); 90 | mv.visitEnd(); 91 | 92 | MethodVisitor main = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "main", 93 | "([Ljava/lang/String;)V", null, null); 94 | main.visitCode(); 95 | main.visitFieldInsn(GETSTATIC, "java/lang/System", "out", 96 | "Ljava/io/PrintStream;"); 97 | main.visitLdcInsn(9); 98 | main.visitLdcInsn(6); 99 | main.visitMethodInsn(INVOKESTATIC, "GCDSample", "gcd", "(II)I", false); 100 | main.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); 101 | main.visitInsn(RETURN); 102 | 103 | main.visitMaxs(-1, -1); 104 | main.visitEnd(); 105 | 106 | writer.visitEnd(); 107 | byte[] array = writer.toByteArray(); 108 | 109 | ClassReader reader = new ClassReader(array); 110 | CheckClassAdapter.verify(reader, true, new PrintWriter(System.out)); 111 | 112 | Files.write(Paths.get("GCD.class"), array); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Linker.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.io.PrintStream; 4 | import java.lang.invoke.CallSite; 5 | import java.lang.invoke.ConstantCallSite; 6 | import java.lang.invoke.MethodHandle; 7 | import java.lang.invoke.MethodHandles; 8 | import java.lang.invoke.MethodType; 9 | import java.lang.invoke.MutableCallSite; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.function.Supplier; 14 | import java.util.stream.Collectors; 15 | 16 | public class Linker { 17 | static class Function { 18 | final Fn fn; 19 | private final HashMap>, HashMap> bindingMapMap = new HashMap<>(); 20 | final HashMap callSiteMap = new HashMap<>(); 21 | 22 | Function(Fn fn) { 23 | this.fn = fn; 24 | } 25 | 26 | CallSite createCallSite(Linker linker, String name, MethodType methodType) { 27 | return callSiteMap.computeIfAbsent(methodType, key -> { 28 | Type returnType = Type.getTypeFromClass(methodType.returnType()); 29 | Type[] parameterTypes = methodType.parameterList().stream().map(Type::getTypeFromClass).toArray(Type[]::new); 30 | HashMap bindingMap = bindingMapMap.computeIfAbsent(methodType.parameterList(), key2 -> { 31 | HashMap newBindingMap = new HashMap<>(); 32 | TypeInferer.inferType(fn, returnType, parameterTypes, newBindingMap); 33 | return newBindingMap; 34 | }); 35 | return new InvalidableCallSite(methodType, () -> 36 | Generator.generate(fn, linker, bindingMap, returnType.mix(true) /*FIXME ?*/, name, parameterTypes)); 37 | }); 38 | } 39 | } 40 | 41 | static class InvalidableCallSite extends MutableCallSite { 42 | private final Supplier supplier; 43 | private final MethodHandle fallback; 44 | 45 | InvalidableCallSite(MethodType methodType, Supplier supplier) { 46 | super(methodType); 47 | this.supplier = supplier; 48 | MethodHandle fallback = MethodHandles.foldArguments( 49 | MethodHandles.exactInvoker(methodType), FALLBACK.bindTo(this)); 50 | this.setTarget(supplier.get()); 51 | this.fallback = fallback; 52 | } 53 | 54 | @SuppressWarnings("unused") // called by a method handle 55 | private MethodHandle fallback() { 56 | MethodHandle target = supplier.get(); 57 | setTarget(target); 58 | return target; 59 | } 60 | 61 | void invalidate() { 62 | setTarget(fallback); 63 | } 64 | 65 | private static final MethodHandle FALLBACK; 66 | static { 67 | try { 68 | FALLBACK = MethodHandles.lookup().findVirtual(InvalidableCallSite.class, "fallback", 69 | MethodType.methodType(MethodHandle.class)); 70 | } catch (NoSuchMethodException | IllegalAccessException e) { 71 | throw new AssertionError(e); 72 | } 73 | } 74 | } 75 | 76 | private final Map functionMap; 77 | 78 | public Linker(Script script) { 79 | functionMap = script.funs().stream().collect(Collectors.toMap(Fn::name, Function::new)); 80 | } 81 | 82 | public void invalidate(String nameAndType) { 83 | int index = nameAndType.indexOf('('); 84 | String name = nameAndType.substring(0, index); 85 | MethodType methodType = MethodType.fromMethodDescriptorString(nameAndType.substring(index), null); 86 | 87 | System.out.println("linker invalidate: " + name + ':' + methodType); 88 | functionMap.get(name).callSiteMap.get(methodType).invalidate(); 89 | } 90 | 91 | public CallSite getCallSite(String name, MethodType methodType) { 92 | // try built-ins first 93 | if (name.equals("print")) { 94 | MethodHandle mh; 95 | try { 96 | mh = MethodHandles.publicLookup().findVirtual(PrintStream.class, "println", 97 | MethodType.methodType(void.class, methodType.parameterType(0))); 98 | } catch (NoSuchMethodException | IllegalAccessException e) { 99 | throw new AssertionError(e); 100 | } 101 | mh = mh.bindTo(System.out); 102 | return new ConstantCallSite(mh.asType(mh.type().changeReturnType(methodType.returnType()))); 103 | } 104 | 105 | Function function = functionMap.get(name); 106 | if (function == null || function.fn.parameters().size() != methodType.parameterCount()) { 107 | throw new IllegalStateException("no function matching " + name + methodType + " found"); 108 | } 109 | 110 | return function.createCallSite(this, name, methodType); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Parser.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.io.Reader; 4 | import java.math.BigInteger; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | import com.github.forax.vmboiler.sample.script.Expr.*; 10 | import com.github.forax.vmboiler.sample.script.tools.Analyzers; 11 | import com.github.forax.vmboiler.sample.script.tools.GrammarEvaluator; 12 | import com.github.forax.vmboiler.sample.script.tools.TerminalEvaluator; 13 | 14 | public class Parser { 15 | static final class FunGrammarEvaluator implements GrammarEvaluator { 16 | @Override 17 | public void acceptScript() { 18 | // do nothing 19 | } 20 | 21 | private Script script; 22 | 23 | public Script getScript() { 24 | return script; 25 | } 26 | 27 | @Override 28 | public void script(List fun_star) { 29 | script = new Script(fun_star); 30 | } 31 | 32 | @Override 33 | public Fn fun(String id, List id_star, List expr_star) { 34 | return new Fn(id, id_star.stream().map(Parameter::new).collect(Collectors.toList()), new Block(expr_star)); 35 | } 36 | 37 | @Override 38 | public Expr expr_integer(Object integer) { 39 | return new Literal(integer); 40 | } 41 | @Override 42 | public Expr expr_text(String text) { 43 | return new Literal(text); 44 | } 45 | 46 | @Override 47 | public Expr expr_block(List expr_star) { 48 | return new Block(expr_star); 49 | } 50 | 51 | @Override 52 | public Expr expr_var_access(String id) { 53 | return new VarAccess(id); 54 | } 55 | @Override 56 | public Expr expr_var_assignment(String id, Expr expr) { 57 | return new VarAssignment(id, expr); 58 | } 59 | 60 | @Override 61 | public Expr expr_call(String id, List expr_star) { 62 | return new Call(null, id, expr_star); 63 | } 64 | 65 | @Override 66 | public Expr expr_if(Expr expr, Expr expr2, Expr expr3) { 67 | return new If(expr, expr2, expr3); 68 | } 69 | @Override 70 | public Expr expr_while(Expr expr, List expr_star) { 71 | return new While(expr, new Block(expr_star)); 72 | } 73 | 74 | 75 | 76 | @Override 77 | public Expr expr_add(Expr expr, Expr expr2) { 78 | return binOp(Op.add, expr, expr2); 79 | } 80 | @Override 81 | public Expr expr_sub(Expr expr, Expr expr2) { 82 | return binOp(Op.sub, expr, expr2); 83 | } 84 | @Override 85 | public Expr expr_mul(Expr expr, Expr expr2) { 86 | return binOp(Op.mul, expr, expr2); 87 | } 88 | @Override 89 | public Expr expr_div(Expr expr, Expr expr2) { 90 | return binOp(Op.div, expr, expr2); 91 | } 92 | @Override 93 | public Expr expr_rem(Expr expr, Expr expr2) { 94 | return binOp(Op.rem, expr, expr2); 95 | } 96 | 97 | @Override 98 | public Expr expr_eq(Expr expr, Expr expr2) { 99 | return binOp(Op.eq, expr, expr2); 100 | } 101 | @Override 102 | public Expr expr_ne(Expr expr, Expr expr2) { 103 | return binOp(Op.ne, expr, expr2); 104 | } 105 | @Override 106 | public Expr expr_lt(Expr expr, Expr expr2) { 107 | return binOp(Op.lt, expr, expr2); 108 | } 109 | @Override 110 | public Expr expr_le(Expr expr, Expr expr2) { 111 | return binOp(Op.le, expr, expr2); 112 | } 113 | @Override 114 | public Expr expr_gt(Expr expr, Expr expr2) { 115 | return binOp(Op.gt, expr, expr2); 116 | } 117 | @Override 118 | public Expr expr_ge(Expr expr, Expr expr2) { 119 | return binOp(Op.ge, expr, expr2); 120 | } 121 | 122 | private static Expr binOp(Op op, Expr expr, Expr expr2) { 123 | return new Call(op, op.name(), Arrays.asList(expr, expr2)); 124 | } 125 | } 126 | 127 | public static Script parse(Reader reader) { 128 | TerminalEvaluator terminalEvaluator = new TerminalEvaluator() { 129 | @Override 130 | public String text(CharSequence data) { 131 | return data.subSequence(1, data.length() - 1).toString(); 132 | } 133 | @Override 134 | public Object integer(CharSequence data) { 135 | try { 136 | return Integer.parseInt(data.toString()); 137 | } catch(IllegalArgumentException e) { 138 | return new BigInteger(data.toString()); 139 | } 140 | } 141 | @Override 142 | public String id(CharSequence data) { 143 | return data.toString(); 144 | } 145 | 146 | @Override 147 | public void comment(CharSequence data) { 148 | // ignore 149 | } 150 | }; 151 | 152 | FunGrammarEvaluator grammarEvaluator = new FunGrammarEvaluator(); 153 | Analyzers.run(reader, terminalEvaluator, grammarEvaluator, null, null); 154 | return grammarEvaluator.getScript(); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/TypeInferer.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | 7 | import com.github.forax.vmboiler.sample.script.Expr.*; 8 | 9 | public class TypeInferer { 10 | static class Env { 11 | final HashMap scope; 12 | final HashMap bindingMap; 13 | Type expectedType = Type.VOID; 14 | 15 | private Env(HashMap scope, HashMap bindingMap) { 16 | this.scope = scope; 17 | this.bindingMap = bindingMap; 18 | } 19 | 20 | Env(HashMap bindingMap) { 21 | this(new HashMap<>(), bindingMap); 22 | } 23 | 24 | Env newEnv() { 25 | return new Env(new HashMap<>(scope), bindingMap); 26 | } 27 | 28 | // warning stupid side effect, *must* be called when calling VISITOR.call(...) 29 | Env expectedType(Type type) { 30 | expectedType = type; 31 | return this; 32 | } 33 | } 34 | 35 | public static Type inferType(Fn fn, Type returnType, Type[] parameterTypes, HashMap bindingMap) { 36 | Env env = new Env(bindingMap); 37 | for(int i = 0; i < parameterTypes.length; i++) { 38 | Parameter parameter = fn.parameters().get(i); 39 | Binding binding = new Binding(parameterTypes[i]); 40 | env.scope.put(parameter.name(), binding); 41 | bindingMap.put(parameter, binding); 42 | } 43 | return VISITOR.call(fn.block(), env.expectedType(returnType)); 44 | } 45 | 46 | private static final Visitor VISITOR = new Visitor() 47 | .when(Literal.class, (literal, env) -> { 48 | Object constant = literal.constant(); 49 | return (constant instanceof Integer)? Type.INT: Type.OBJECT; 50 | }) 51 | .when(Block.class, (block, env) -> { 52 | List exprs = block.exprs(); 53 | int size = exprs.size(); 54 | if (size == 0) { 55 | return Type.OBJECT; 56 | } 57 | Type expectedType = env.expectedType; 58 | for(int i = 0; i < size - 1; i++) { 59 | TypeInferer.VISITOR.call(exprs.get(i), env.expectedType(Type.VOID)); 60 | } 61 | return TypeInferer.VISITOR.call(exprs.get(size - 1), env.expectedType(expectedType)); 62 | }) 63 | .when(VarAccess.class, (varAccess, env) -> { 64 | String name = varAccess.name(); 65 | Binding binding = env.scope.get(name); 66 | if (binding == null) { 67 | throw new IllegalStateException("no variable " + name + " defined in scope"); 68 | } 69 | env.bindingMap.put(varAccess, binding); 70 | return binding.type(); 71 | }) 72 | .when(VarAssignment.class, (varAssignment, env) -> { 73 | String name = varAssignment.name(); 74 | Binding binding = env.scope.get(name); 75 | Type expectedType = env.expectedType; 76 | expectedType = (binding != null && binding.type() != null)? binding.type(): (expectedType != Type.VOID)? expectedType: null; 77 | Type type = TypeInferer.VISITOR.call(varAssignment.expr(), env.expectedType(expectedType)); 78 | if (binding != null) { 79 | binding.type(Type.merge(binding.type(), type)); 80 | } else { 81 | binding = new Binding(type); 82 | env.scope.put(name, binding); 83 | } 84 | env.bindingMap.put(varAssignment, binding); 85 | return type; 86 | }) 87 | .when(Call.class, (call, env) -> { 88 | Type expectedType = env.expectedType; 89 | List types = call.exprs().stream().map(expr -> TypeInferer.VISITOR.call(expr, env.expectedType(null))).collect(Collectors.toList()); 90 | Type returnType = (expectedType != null)? expectedType.mix(true): 91 | call.optionalOp().map(op -> op.returnTypeOp().apply(types.get(0), types.get(1))).orElse(Type.MIXED_INT); //TODO improve heuristic ? 92 | env.bindingMap.put(call, new Binding(returnType)); 93 | return returnType; 94 | }) 95 | .when(If.class, (if_, env) -> { 96 | Type expectedType = env.expectedType; 97 | TypeInferer.VISITOR.call(if_.condition(), env.newEnv().expectedType(Type.BOOL)); 98 | Type type1 = TypeInferer.VISITOR.call(if_.truePart(), env.newEnv().expectedType(expectedType)); 99 | Type type2 = TypeInferer.VISITOR.call(if_.falsePart(), env.newEnv().expectedType(expectedType)); 100 | Type type = (expectedType == Type.VOID)? Type.VOID: Type.merge(type1, type2); 101 | env.bindingMap.put(if_, new Binding(type)); 102 | return type; 103 | }) 104 | .when(While.class, (while_, env) -> { 105 | Type expectedType = env.expectedType; 106 | TypeInferer.VISITOR.call(while_.condition(), env.newEnv().expectedType(Type.BOOL)); 107 | TypeInferer.VISITOR.call(while_.body(), env.newEnv().expectedType(Type.VOID)); 108 | Type type = (expectedType == Type.VOID)? Type.VOID: Type.OBJECT; 109 | env.bindingMap.put(while_, new Binding(type)); 110 | return type; 111 | }) 112 | ; 113 | } 114 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/tools/GrammarEvaluator.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.tools; 2 | 3 | import com.github.forax.vmboiler.sample.script.Expr; 4 | import com.github.forax.vmboiler.sample.script.Fn; 5 | import java.util.List; 6 | 7 | /** 8 | * This class is generated - please do not edit it 9 | */ 10 | public interface GrammarEvaluator { 11 | /** This methods is called after the reduction of the non terminal script 12 | * by the grammar production script. 13 | * script ::= fun_star_0 14 | */ 15 | public void script(List fun_star); 16 | /** This methods is called after the reduction of the non terminal fun 17 | * by the grammar production fun. 18 | * fun ::= fn lpar id id_star_1 colon expr_star_2 rpar 19 | */ 20 | public Fn fun(String id,List id_star,List expr_star); 21 | /** This methods is called after the reduction of the non terminal expr 22 | * by the grammar production expr_integer. 23 | * expr ::= integer 24 | */ 25 | public Expr expr_integer(Object integer); 26 | /** This methods is called after the reduction of the non terminal expr 27 | * by the grammar production expr_text. 28 | * expr ::= text 29 | */ 30 | public Expr expr_text(String text); 31 | /** This methods is called after the reduction of the non terminal expr 32 | * by the grammar production expr_block. 33 | * expr ::= lpar expr_star_3 rpar 34 | */ 35 | public Expr expr_block(List expr_star); 36 | /** This methods is called after the reduction of the non terminal expr 37 | * by the grammar production expr_var_access. 38 | * expr ::= id 39 | */ 40 | public Expr expr_var_access(String id); 41 | /** This methods is called after the reduction of the non terminal expr 42 | * by the grammar production expr_var_assignment. 43 | * expr ::= id assign expr 44 | */ 45 | public Expr expr_var_assignment(String id,Expr expr); 46 | /** This methods is called after the reduction of the non terminal expr 47 | * by the grammar production expr_call. 48 | * expr ::= id lpar expr_star_4 rpar 49 | */ 50 | public Expr expr_call(String id,List expr_star); 51 | /** This methods is called after the reduction of the non terminal expr 52 | * by the grammar production expr_if. 53 | * expr ::= if_ lpar expr expr expr rpar 54 | */ 55 | public Expr expr_if(Expr expr,Expr expr2,Expr expr3); 56 | /** This methods is called after the reduction of the non terminal expr 57 | * by the grammar production expr_while. 58 | * expr ::= while_ lpar expr expr_star_5 rpar 59 | */ 60 | public Expr expr_while(Expr expr,List expr_star); 61 | /** This methods is called after the reduction of the non terminal expr 62 | * by the grammar production expr_mul. 63 | * expr ::= expr mul expr 64 | */ 65 | public Expr expr_mul(Expr expr,Expr expr2); 66 | /** This methods is called after the reduction of the non terminal expr 67 | * by the grammar production expr_div. 68 | * expr ::= expr div expr 69 | */ 70 | public Expr expr_div(Expr expr,Expr expr2); 71 | /** This methods is called after the reduction of the non terminal expr 72 | * by the grammar production expr_rem. 73 | * expr ::= expr rem expr 74 | */ 75 | public Expr expr_rem(Expr expr,Expr expr2); 76 | /** This methods is called after the reduction of the non terminal expr 77 | * by the grammar production expr_add. 78 | * expr ::= expr add expr 79 | */ 80 | public Expr expr_add(Expr expr,Expr expr2); 81 | /** This methods is called after the reduction of the non terminal expr 82 | * by the grammar production expr_sub. 83 | * expr ::= expr sub expr 84 | */ 85 | public Expr expr_sub(Expr expr,Expr expr2); 86 | /** This methods is called after the reduction of the non terminal expr 87 | * by the grammar production expr_eq. 88 | * expr ::= expr eq expr 89 | */ 90 | public Expr expr_eq(Expr expr,Expr expr2); 91 | /** This methods is called after the reduction of the non terminal expr 92 | * by the grammar production expr_ne. 93 | * expr ::= expr ne expr 94 | */ 95 | public Expr expr_ne(Expr expr,Expr expr2); 96 | /** This methods is called after the reduction of the non terminal expr 97 | * by the grammar production expr_lt. 98 | * expr ::= expr lt expr 99 | */ 100 | public Expr expr_lt(Expr expr,Expr expr2); 101 | /** This methods is called after the reduction of the non terminal expr 102 | * by the grammar production expr_le. 103 | * expr ::= expr le expr 104 | */ 105 | public Expr expr_le(Expr expr,Expr expr2); 106 | /** This methods is called after the reduction of the non terminal expr 107 | * by the grammar production expr_gt. 108 | * expr ::= expr gt expr 109 | */ 110 | public Expr expr_gt(Expr expr,Expr expr2); 111 | /** This methods is called after the reduction of the non terminal expr 112 | * by the grammar production expr_ge. 113 | * expr ::= expr ge expr 114 | */ 115 | public Expr expr_ge(Expr expr,Expr expr2); 116 | 117 | public void acceptScript(); 118 | } 119 | -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/Constant.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler; 2 | 3 | import static com.github.forax.vmboiler.Type.VM_BYTE; 4 | import static com.github.forax.vmboiler.Type.VM_CHAR; 5 | import static com.github.forax.vmboiler.Type.VM_DOUBLE; 6 | import static com.github.forax.vmboiler.Type.VM_FLOAT; 7 | import static com.github.forax.vmboiler.Type.VM_INT; 8 | import static com.github.forax.vmboiler.Type.VM_LONG; 9 | import static com.github.forax.vmboiler.Type.VM_SHORT; 10 | import static com.github.forax.vmboiler.Type.VM_VOID; 11 | import static org.objectweb.asm.Opcodes.ACONST_NULL; 12 | import static org.objectweb.asm.Opcodes.BIPUSH; 13 | import static org.objectweb.asm.Opcodes.DCONST_0; 14 | import static org.objectweb.asm.Opcodes.DCONST_1; 15 | import static org.objectweb.asm.Opcodes.FCONST_0; 16 | import static org.objectweb.asm.Opcodes.FCONST_1; 17 | import static org.objectweb.asm.Opcodes.FCONST_2; 18 | import static org.objectweb.asm.Opcodes.ICONST_0; 19 | import static org.objectweb.asm.Opcodes.LCONST_0; 20 | import static org.objectweb.asm.Opcodes.SIPUSH; 21 | 22 | import java.util.function.BiConsumer; 23 | 24 | import org.objectweb.asm.Handle; 25 | import org.objectweb.asm.MethodVisitor; 26 | 27 | /** 28 | * A side effect free constant. 29 | */ 30 | public final class Constant extends Value { 31 | private final BiConsumer consumer; 32 | 33 | private Constant(Type type, BiConsumer consumer) { 34 | super(type); 35 | if (type.isMixed() || type.vmType() == VM_VOID) { 36 | throw new IllegalArgumentException("a constant can not have a type VM_VOID or mixed"); 37 | } 38 | this.consumer = consumer; 39 | } 40 | 41 | /** 42 | * Creates a constant with a type and a value. 43 | * The value must be a type representable as a constant of 44 | * the constant pool. 45 | * @param type the type of the constant. 46 | * @param constant the value of the constant. 47 | */ 48 | public Constant(Type type, Object constant) { 49 | this(type, (mv, t) -> load(mv, t, constant)); 50 | } 51 | 52 | /** 53 | * Creates a constant with a type and using invokedynamic 54 | * to load the constant. 55 | * @param type the type of the constant. 56 | * @param bsm the bootstrap method 57 | * @param bsmCsts the constant arguments of the bootstrap method 58 | * @param name name used by invokedynamic 59 | */ 60 | public Constant(Type type, Handle bsm, Object[] bsmCsts, String name) { 61 | this(type, (mv, t) -> { 62 | mv.visitInvokeDynamicInsn(name, "()" + t.vmType(), bsm, bsmCsts); 63 | }); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "constant(" + type() + ')'; 69 | } 70 | 71 | @Override 72 | void loadPrimitive(MethodVisitor mv) { 73 | consumer.accept(mv, type()); 74 | } 75 | @Override 76 | void loadAll(MethodVisitor mv) { 77 | consumer.accept(mv, type()); 78 | } 79 | 80 | private static void load(MethodVisitor mv, Type type, Object constant) { 81 | switch(type.vmType()) { 82 | case VM_BYTE: 83 | case VM_CHAR: 84 | case VM_SHORT: 85 | case VM_INT: 86 | loadInt(mv, constant); 87 | return; 88 | case VM_LONG: 89 | loadLong(mv, constant); 90 | return; 91 | case VM_FLOAT: 92 | loadFloat(mv, constant); 93 | return; 94 | case VM_DOUBLE: 95 | loadDouble(mv, constant); 96 | return; 97 | default: // string or null ! 98 | } 99 | if (constant == null) { 100 | mv.visitInsn(ACONST_NULL); 101 | return; 102 | } 103 | mv.visitLdcInsn(constant); 104 | } 105 | 106 | private static void loadInt(MethodVisitor mv, Object constant) { 107 | int value = (Integer)constant; 108 | switch(value) { 109 | case -1: 110 | case 0: 111 | case 1: 112 | case 2: 113 | case 3: 114 | case 4: 115 | case 5: 116 | mv.visitInsn(ICONST_0 + value); 117 | return; 118 | default: 119 | } 120 | if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { 121 | mv.visitIntInsn(BIPUSH, value); 122 | return; 123 | } 124 | if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { 125 | mv.visitIntInsn(SIPUSH, value); 126 | return; 127 | } 128 | mv.visitLdcInsn(constant); 129 | } 130 | 131 | private static void loadLong(MethodVisitor mv, Object constant) { 132 | long value = (Long)constant; 133 | if (value == 0 || value == 1) { 134 | mv.visitInsn(LCONST_0 + (int)value); 135 | return; 136 | } 137 | mv.visitLdcInsn(constant); 138 | } 139 | 140 | private static void loadFloat(MethodVisitor mv, Object constant) { 141 | float value = (Float)constant; 142 | if (value == 0.0f) { 143 | mv.visitInsn(FCONST_0); 144 | return; 145 | } 146 | if (value == 1.0f) { 147 | mv.visitInsn(FCONST_1); 148 | return; 149 | } 150 | if (value == 2.0f) { 151 | mv.visitInsn(FCONST_2); 152 | return; 153 | } 154 | mv.visitLdcInsn(constant); 155 | } 156 | 157 | private static void loadDouble(MethodVisitor mv, Object constant) { 158 | double value = (Double)constant; 159 | if (value == 0.0) { 160 | mv.visitInsn(DCONST_0); 161 | return; 162 | } 163 | if (value == 1.0) { 164 | mv.visitInsn(DCONST_1); 165 | return; 166 | } 167 | mv.visitLdcInsn(constant); 168 | } 169 | } -------------------------------------------------------------------------------- /test/src/test/FiboGen.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import static org.objectweb.asm.Opcodes.*; 4 | import static org.objectweb.asm.Opcodes.ACC_STATIC; 5 | import static org.objectweb.asm.Opcodes.ACC_SUPER; 6 | import static org.objectweb.asm.Opcodes.ALOAD; 7 | import static org.objectweb.asm.Opcodes.ASTORE; 8 | import static org.objectweb.asm.Opcodes.GETSTATIC; 9 | import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; 10 | import static org.objectweb.asm.Opcodes.INVOKESTATIC; 11 | import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; 12 | import static org.objectweb.asm.Opcodes.RETURN; 13 | import static org.objectweb.asm.Opcodes.V1_7; 14 | 15 | import java.io.IOException; 16 | import java.io.PrintWriter; 17 | import java.lang.invoke.CallSite; 18 | import java.lang.invoke.MethodHandles.Lookup; 19 | import java.lang.invoke.MethodType; 20 | import java.nio.file.Files; 21 | import java.nio.file.Paths; 22 | 23 | import org.objectweb.asm.ClassReader; 24 | import org.objectweb.asm.ClassWriter; 25 | import org.objectweb.asm.Handle; 26 | import org.objectweb.asm.Label; 27 | import org.objectweb.asm.MethodVisitor; 28 | import org.objectweb.asm.util.CheckClassAdapter; 29 | 30 | import com.github.forax.vmboiler.CodeGen; 31 | import com.github.forax.vmboiler.Constant; 32 | import com.github.forax.vmboiler.Type; 33 | import com.github.forax.vmboiler.Var; 34 | 35 | public class FiboGen { 36 | public enum Types implements Type { 37 | INT, INT_MIXED, BOOL 38 | ; 39 | @Override 40 | public boolean isMixed() { 41 | return this == INT_MIXED; 42 | } 43 | @Override 44 | public String vmType() { 45 | return (this == BOOL)? Type.VM_BOOLEAN: Type.VM_INT; 46 | } 47 | } 48 | 49 | private static final Object[] EMPTY_ARRAY = new Object[0]; 50 | 51 | private static final String FIBO_RT = FiboRT.class.getName().replace('.', '/'); 52 | private static final Handle BSM = new Handle(H_INVOKESTATIC, 53 | FIBO_RT, "bsm", 54 | MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class).toMethodDescriptorString()); 55 | private static final Handle DEOPT_ARGS = new Handle(H_INVOKESTATIC, 56 | FIBO_RT, "deopt_args", 57 | MethodType.methodType(boolean.class, Lookup.class, String.class, MethodType.class, Object[].class).toMethodDescriptorString()); 58 | private static final Handle DEOPT_RET = new Handle(H_INVOKESTATIC, 59 | FIBO_RT, "deopt_return", 60 | MethodType.methodType(boolean.class, Lookup.class, String.class, MethodType.class, Object.class).toMethodDescriptorString()); 61 | 62 | 63 | public static void main(String[] args) throws IOException { 64 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 65 | writer.visit(V1_8, ACC_PUBLIC|ACC_SUPER, "Fibo", null, "java/lang/Object", null); 66 | MethodVisitor mv = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "fibo", "(I)I", null, null); 67 | mv.visitCode(); 68 | CodeGen codeGen = new CodeGen(mv, Types.INT_MIXED); 69 | Constant one = new Constant(Types.INT, 1); 70 | Constant two = new Constant(Types.INT, 2); 71 | Var r1 = codeGen.createVar(Types.INT); 72 | Var r2 = codeGen.createVar(Types.BOOL); 73 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 74 | r2, "lt", r1, two); 75 | Label nextLabel = new Label(); 76 | codeGen.jumpIfFalse(r2, nextLabel); 77 | codeGen.ret(one); 78 | codeGen.label(nextLabel); 79 | Var r3 = codeGen.createVar(Types.INT_MIXED); 80 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 81 | r3, "sub", r1, one); 82 | Var r4 = codeGen.createVar(Types.INT_MIXED); 83 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 84 | r4, "fibo", r3); 85 | Var r5 = codeGen.createVar(Types.INT_MIXED); 86 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 87 | r5, "sub", r1, two); 88 | Var r6 = codeGen.createVar(Types.INT_MIXED); 89 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 90 | r6, "fibo", r5); 91 | Var r7 = codeGen.createVar(Types.INT_MIXED); 92 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, EMPTY_ARRAY, 93 | r7, "add", r4, r6); 94 | codeGen.ret(r7); 95 | codeGen.end(); 96 | mv.visitMaxs(-1, -1); 97 | mv.visitEnd(); 98 | 99 | MethodVisitor main = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "main", 100 | "([Ljava/lang/String;)V", null, null); 101 | 102 | main.visitCode(); 103 | Label start = new Label(); 104 | Label end = new Label(); 105 | Label handler = new Label(); 106 | 107 | main.visitFieldInsn(GETSTATIC, "java/lang/System", "out", 108 | "Ljava/io/PrintStream;"); 109 | main.visitLdcInsn(7); 110 | 111 | main.visitTryCatchBlock(start, end, handler, "com/github/forax/vmboiler/rt/OptimisticError"); 112 | main.visitLabel(start); 113 | main.visitMethodInsn(INVOKESTATIC, "FiboSample", "fibo", "(I)I", false); 114 | main.visitLabel(end); 115 | main.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false); 116 | main.visitInsn(RETURN); 117 | 118 | main.visitLabel(handler); 119 | main.visitVarInsn(ASTORE, 0); 120 | main.visitFieldInsn(GETSTATIC, "java/lang/System", "out", 121 | "Ljava/io/PrintStream;"); 122 | main.visitVarInsn(ALOAD, 0); 123 | main.visitMethodInsn(INVOKEVIRTUAL, "com/github/forax/vmboiler/rt/OptimisticError", 124 | "value", "()Ljava/lang/Object;", false); 125 | main.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); 126 | main.visitInsn(RETURN); 127 | 128 | main.visitMaxs(-1, -1); 129 | main.visitEnd(); 130 | 131 | writer.visitEnd(); 132 | byte[] array = writer.toByteArray(); 133 | 134 | ClassReader reader = new ClassReader(array); 135 | CheckClassAdapter.verify(reader, true, new PrintWriter(System.out)); 136 | 137 | Files.write(Paths.get("Fibo.class"), array); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /test/src/test/Example.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import static org.objectweb.asm.Opcodes.ACC_PUBLIC; 4 | import static org.objectweb.asm.Opcodes.ACC_STATIC; 5 | import static org.objectweb.asm.Opcodes.ACC_SUPER; 6 | import static org.objectweb.asm.Opcodes.GETSTATIC; 7 | import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; 8 | import static org.objectweb.asm.Opcodes.INVOKESTATIC; 9 | import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; 10 | import static org.objectweb.asm.Opcodes.RETURN; 11 | import static org.objectweb.asm.Opcodes.V1_8; 12 | 13 | import java.io.IOException; 14 | import java.io.PrintWriter; 15 | import java.lang.invoke.CallSite; 16 | import java.lang.invoke.MethodHandle; 17 | import java.lang.invoke.MethodHandles; 18 | import java.lang.invoke.MethodHandles.Lookup; 19 | import java.lang.invoke.MethodType; 20 | import java.nio.file.Files; 21 | import java.nio.file.Paths; 22 | import java.util.function.Function; 23 | import java.util.function.Supplier; 24 | 25 | import org.objectweb.asm.ClassReader; 26 | import org.objectweb.asm.ClassWriter; 27 | import org.objectweb.asm.Handle; 28 | import org.objectweb.asm.Label; 29 | import org.objectweb.asm.MethodVisitor; 30 | import org.objectweb.asm.util.CheckClassAdapter; 31 | 32 | import com.github.forax.vmboiler.CodeGen; 33 | import com.github.forax.vmboiler.Constant; 34 | import com.github.forax.vmboiler.Type; 35 | import com.github.forax.vmboiler.Var; 36 | import com.github.forax.vmboiler.rt.OptimisticError; 37 | 38 | public class Example { 39 | enum Types implements Type { 40 | INT, INT_MIXED, ANY 41 | ; 42 | @Override 43 | public boolean isMixed() { 44 | return this == INT_MIXED; 45 | } 46 | @Override 47 | public String vmType() { 48 | return (this == ANY)? Type.VM_OBJECT: Type.VM_INT; 49 | } 50 | } 51 | 52 | private static final Object[] EMPTY_ARRAY = new Object[0]; 53 | 54 | private static final String EXAMPLE_RT = ExampleRT.class.getName().replace('.', '/'); 55 | private static final Handle BSM = new Handle(H_INVOKESTATIC, 56 | EXAMPLE_RT, "bsm", 57 | MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class).toMethodDescriptorString()); 58 | private static final Handle DEOPT_ARGS = new Handle(H_INVOKESTATIC, 59 | EXAMPLE_RT, "deopt_args", 60 | MethodType.methodType(boolean.class, Lookup.class, String.class, MethodType.class, Object[].class, String.class).toMethodDescriptorString()); 61 | private static final Handle DEOPT_RET = new Handle(H_INVOKESTATIC, 62 | EXAMPLE_RT, "deopt_ret", 63 | MethodType.methodType(boolean.class, Lookup.class, String.class, MethodType.class, Object.class, String.class).toMethodDescriptorString()); 64 | 65 | private static byte[] generateAdd1Any() { 66 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 67 | writer.visit(V1_8, ACC_PUBLIC|ACC_SUPER, "Foo", null, "java/lang/Object", null); 68 | MethodVisitor mv = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "foo", "(I)Ljava/lang/Object;", null, null); 69 | mv.visitCode(); 70 | CodeGen codeGen = new CodeGen(mv, Types.ANY); 71 | Var x = codeGen.createVar(Types.INT); 72 | Constant one = new Constant(Types.INT, 1); 73 | Var result = codeGen.createVar(Types.ANY); 74 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, new Object[] { "x" }, 75 | result, "add", x, one); 76 | codeGen.ret(result); 77 | codeGen.end(); 78 | mv.visitMaxs(-1, -1); 79 | mv.visitEnd(); 80 | writer.visitEnd(); 81 | return writer.toByteArray(); 82 | } 83 | 84 | private static byte[] generateAdd1Int() { 85 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 86 | writer.visit(V1_8, ACC_PUBLIC|ACC_SUPER, "Foo", null, "java/lang/Object", null); 87 | MethodVisitor mv = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "foo", "(I)I", null, null); 88 | mv.visitCode(); 89 | CodeGen codeGen = new CodeGen(mv, Types.INT_MIXED); 90 | Var x = codeGen.createVar(Types.INT); 91 | Constant one = new Constant(Types.INT, 1); 92 | Var result = codeGen.createVar(Types.INT_MIXED); 93 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, new Object[] { "x" }, 94 | result, "add", x, one); 95 | codeGen.ret(result); 96 | codeGen.end(); 97 | mv.visitMaxs(-1, -1); 98 | mv.visitEnd(); 99 | writer.visitEnd(); 100 | return writer.toByteArray(); 101 | } 102 | 103 | private static byte[] generateAdd2Int() { 104 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 105 | writer.visit(V1_8, ACC_PUBLIC|ACC_SUPER, "Foo", null, "java/lang/Object", null); 106 | MethodVisitor mv = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, "foo", "(I)I", null, null); 107 | mv.visitCode(); 108 | CodeGen codeGen = new CodeGen(mv, Types.INT_MIXED); 109 | Var x = codeGen.createVar(Types.INT); 110 | Constant one = new Constant(Types.INT, 1); 111 | Var result1 = codeGen.createVar(Types.INT_MIXED); 112 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, new Object[] { "x" }, 113 | result1, "add", x, one); 114 | Var result2 = codeGen.createVar(Types.INT_MIXED); 115 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, new Object[] { "result1" }, 116 | result2, "add", result1, one); 117 | codeGen.ret(result2); 118 | codeGen.end(); 119 | mv.visitMaxs(-1, -1); 120 | mv.visitEnd(); 121 | writer.visitEnd(); 122 | return writer.toByteArray(); 123 | } 124 | 125 | public static MethodHandle createFunction(Supplier generator, MethodType methodType) { 126 | byte[] array = generator.get(); 127 | ClassReader reader = new ClassReader(array); 128 | CheckClassAdapter.verify(reader, true, new PrintWriter(System.out)); 129 | 130 | Class type = new ClassLoader() { 131 | Class createClass() { 132 | return defineClass("Foo", array, 0, array.length); 133 | } 134 | }.createClass(); 135 | try { 136 | return MethodHandles.publicLookup().findStatic(type, "foo", methodType); 137 | } catch (NoSuchMethodException | IllegalAccessException e) { 138 | throw new AssertionError(e); 139 | } 140 | } 141 | 142 | public static void main(String[] args) throws Throwable { 143 | //MethodHandle mh = createFunction(Example::generateAdd1Any, MethodType.methodType(Object.class, int.class)); 144 | //MethodHandle mh = createFunction(Example::generateAdd1Int, MethodType.methodType(int.class, int.class)); 145 | MethodHandle mh = createFunction(Example::generateAdd2Int, MethodType.methodType(int.class, int.class)); 146 | try { 147 | int value = (int)mh.invoke(Integer.MAX_VALUE); 148 | //int value = (int)mh.invokeExact(Integer.MAX_VALUE); 149 | System.out.println(value); 150 | } catch(OptimisticError e) { 151 | System.out.println(e.value()); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/rt/RT.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.rt; 2 | 3 | import java.lang.invoke.CallSite; 4 | import java.lang.invoke.ConstantCallSite; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.invoke.MethodType; 9 | import java.lang.invoke.MutableCallSite; 10 | import java.util.Arrays; 11 | 12 | /** 13 | * The runtime support that handle the dynamic part of the deoptimization. 14 | * This call should never be referenced directly. 15 | */ 16 | public final class RT { 17 | /** 18 | * Bootstrap method called called in a deoptimisation path when at least one argument 19 | * doesn't fit in its parameter type. 20 | * 21 | * @param lookup lookup object 22 | * @param name name of the virtual method 23 | * @param methodType descriptor of the virtual method 24 | * @param array parameters that begins with the 4 objects followed by two arrays 25 | *
 26 |    *        a string encoding if an argument is mixed ('M') or not ('.')
 27 |    *        a bootstrap method as method handle with signature (Lookup,String,MethodType, ...)CallSite,
 28 |    *        the number of constant arguments of the bootstrap method
 29 |    *        a deopt callback as a method handle with signature (Object[], ...)boolean
 30 |    *        the bootstrap constant arguments of the bsm as an array of objects flattened
 31 |    *        the deopt callback constant arguments as an array of objects flattened
 32 |    *    
33 | * @return a callsite 34 | * @throws Throwable if an exception occurs 35 | */ 36 | //called by generated code 37 | public static CallSite bsm(Lookup lookup, String name, MethodType methodType, Object... array) throws Throwable { 38 | // decode all arguments 39 | String mixed = (String)array[0]; 40 | MethodHandle bsm = (MethodHandle)array[1]; 41 | int bsmConstantCount = (int)array[2]; 42 | MethodHandle deoptArgs = (MethodHandle)array[3]; // boolean mh(Lookup, Object[], ...) 43 | 44 | //System.out.println("bootstrap: " + name + '(' + mixed + ") with " + bsm + "/" + bsmConstantCount + " " + deoptArgs); 45 | 46 | // call the bsm 47 | Object[] arguments = new Object[3 + bsmConstantCount]; 48 | arguments[0] = lookup; 49 | arguments[1] = name; 50 | arguments[2] = decodeMixedMethodType(mixed, methodType); 51 | if (bsmConstantCount != 0) { 52 | System.arraycopy(array, 4, arguments, 3, bsmConstantCount); 53 | } 54 | CallSite callSite = (CallSite)bsm.invokeWithArguments(arguments); 55 | 56 | // prepend lookup/name/methodType 57 | deoptArgs = MethodHandles.insertArguments(deoptArgs, 0, lookup, name, methodType); 58 | 59 | // bundle deoptArgs constant arguments with deoptArgs if necessary 60 | if (array.length != 4 + bsmConstantCount) { 61 | Object[] deoptArgsCsts = Arrays.copyOfRange(array, 4 + bsmConstantCount, array.length); 62 | 63 | //FIXME check that deoptArgs as the right number of arguments 64 | //FIXME should we support varargs ? 65 | 66 | //System.out.println("deopt cst args " + deoptArgs + " " + Arrays.toString(deoptArgsCsts)); 67 | 68 | deoptArgs = MethodHandles.insertArguments(deoptArgs, 1, deoptArgsCsts); 69 | } 70 | 71 | MethodHandle target = callSite.dynamicInvoker(); 72 | target = target.asSpreader(Object[].class, mixed.length()); 73 | target = deoptCallback(target, deoptArgs); 74 | target = MethodHandles.filterReturnValue(DECODE_MIXED_VALUES.bindTo(mixed), target); 75 | target = target.asCollector(Object[].class, methodType.parameterCount()); 76 | target = target.asType(methodType); 77 | 78 | return new ConstantCallSite(target); 79 | } 80 | 81 | /** 82 | * Bootstrap method called called in a deoptimisation path when a return value 83 | * doesn't fit in the return type. 84 | * 85 | * @param lookup the lookup object. 86 | * @param name the name of the method 87 | * @param methodType always (Object)OptimisiticError 88 | * @param deoptRet the callback to cause to indicate a deopt error. 89 | * @param deoptRetCsts the constant arguments of deoptRet 90 | * @return a call site 91 | * @throws Throwable if an error occurs 92 | */ 93 | // called by generated code 94 | public static CallSite bsm_optimistic_failure(Lookup lookup, String name, MethodType methodType, MethodHandle deoptRet, Object... deoptRetCsts) throws Throwable { 95 | //System.out.println("bsm_optimistic_failure " + name + " with " + deoptRet + "/" + deoptRetCsts.length); 96 | 97 | // do some checks 98 | //if (!deoptRet.type().equals(MethodType.methodType(boolean.class, Object.class))) { 99 | // throw new WrongMethodTypeException("invalid deop callback signature ! " + deoptRet.type()); 100 | //} 101 | 102 | // prepend lookup/name/methodType 103 | deoptRet = MethodHandles.insertArguments(deoptRet, 0, lookup, name, methodType); 104 | 105 | // bundle deoptRet constant arguments with deoptRet if necessary 106 | if (deoptRetCsts.length != 0) { 107 | deoptRet = MethodHandles.insertArguments(deoptRet, 1, deoptRetCsts); 108 | } 109 | 110 | return new ConstantCallSite( 111 | MethodHandles.filterReturnValue(OPTIMISTIC_ERROR_VALUE, 112 | deoptCallback(MethodHandles.identity(Object.class), deoptRet))); 113 | } 114 | 115 | 116 | private static MethodHandle deoptCallback(MethodHandle target, MethodHandle deoptCallback) { 117 | MutableCallSite callSite = new MutableCallSite(target); 118 | Class parameterType = target.type().parameterType(0); 119 | MethodHandle unit = MethodHandles.identity(parameterType).asType(MethodType.methodType(void.class, parameterType)); 120 | MethodHandle fallback = MethodHandles.dropArguments(SET_TARGET.bindTo(callSite).bindTo(target), 0, parameterType); 121 | callSite.setTarget(MethodHandles.foldArguments(target, MethodHandles.guardWithTest(deoptCallback, unit, fallback))); 122 | return callSite.dynamicInvoker(); 123 | } 124 | 125 | private static final MethodHandle SET_TARGET; 126 | static { 127 | try { 128 | SET_TARGET = MethodHandles.publicLookup().findVirtual(MutableCallSite.class, "setTarget", 129 | MethodType.methodType(void.class, MethodHandle.class)); 130 | } catch (NoSuchMethodException | IllegalAccessException e) { 131 | throw new AssertionError(e); 132 | } 133 | } 134 | 135 | 136 | private static MethodType decodeMixedMethodType(String mixed, MethodType methodType) { 137 | int length = mixed.length(); 138 | Class[] parameterTypes = new Class[length]; 139 | int a = 0; 140 | for(int i = 0; i < length; i++) { 141 | if (mixed.charAt(i) == 'M') { 142 | parameterTypes[i] = Object.class; 143 | a += 2; 144 | } else { 145 | parameterTypes[i] = methodType.parameterType(a++); 146 | } 147 | } 148 | return MethodType.methodType(methodType.returnType(), parameterTypes); 149 | } 150 | 151 | @SuppressWarnings("unused") // called by a method handle 152 | private static Object[] decodeMixedValues(String mixed, Object[] args) { 153 | int length = mixed.length(); 154 | Object[] newArgs = new Object[length]; 155 | int a = 0; 156 | for(int i = 0; i < length; i++) { 157 | if (mixed.charAt(i) == 'M') { 158 | newArgs[i] = unmix(args[a], args[a + 1]); 159 | a += 2; 160 | } else { 161 | newArgs[i] = args[a++]; 162 | } 163 | } 164 | return newArgs; 165 | } 166 | 167 | private static Object unmix(Object prim, Object ref) { 168 | return (ref == NONE)? prim: ref; 169 | } 170 | 171 | /** 172 | * Well know constant indicating if a mixed type store its value 173 | * in its primitive slot or in its object slot. 174 | */ 175 | // used by generated code 176 | public static final Object NONE = new Object(); 177 | 178 | private static final MethodHandle DECODE_MIXED_VALUES, OPTIMISTIC_ERROR_VALUE; 179 | static { 180 | Lookup lookup = MethodHandles.lookup(); 181 | try { 182 | DECODE_MIXED_VALUES = lookup.findStatic(RT.class, "decodeMixedValues", 183 | MethodType.methodType(Object[].class, String.class, Object[].class)); 184 | OPTIMISTIC_ERROR_VALUE = lookup.findVirtual(OptimisticError.class, "value", 185 | MethodType.methodType(Object.class)); 186 | } catch (NoSuchMethodException | IllegalAccessException e) { 187 | throw new AssertionError(e); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/RT.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import java.lang.invoke.CallSite; 4 | import java.lang.invoke.ConstantCallSite; 5 | import java.lang.invoke.MethodHandle; 6 | import java.lang.invoke.MethodHandles; 7 | import java.lang.invoke.MethodHandles.Lookup; 8 | import java.lang.invoke.MethodType; 9 | import java.lang.reflect.Method; 10 | import java.math.BigInteger; 11 | import java.util.HashMap; 12 | 13 | import com.github.forax.vmboiler.rt.OptimisticError; 14 | 15 | public class RT { 16 | public static CallSite bsm(Lookup lookup, String name, MethodType methodType, Linker linker) { 17 | //System.out.println("link " + name + methodType); 18 | return linker.getCallSite(name, methodType); 19 | } 20 | 21 | @SuppressWarnings("unused") // used by a method handle 22 | private static boolean deopt_args(Linker linker, String nameAndType, Binding[] bindings, Lookup lookup, String name, MethodType methodType, Object[] values) { 23 | //System.out.println("deopt args " + Arrays.toString(values) + " " + Arrays.toString(bindings)); 24 | 25 | boolean invalidation = false; 26 | for(int i = 0; i < bindings.length; i++) { 27 | Binding binding = bindings[i]; 28 | if (binding == null) { 29 | continue; 30 | } 31 | Type type = binding.type(); 32 | Type mergedType = Type.merge(type, Type.getTypeFromValue(values[i])); 33 | if (type != mergedType) { 34 | binding.type(mergedType); 35 | invalidation = true; 36 | } 37 | } 38 | if (invalidation) { 39 | linker.invalidate(nameAndType); 40 | } 41 | return false; 42 | } 43 | 44 | @SuppressWarnings("unused") // used by a method handle 45 | private static boolean deopt_return(Linker linker, String nameAndType, Binding binding, Lookup lookup, String name, MethodType methodType, Object value) { 46 | System.out.println("deopt return " + value + " " + binding); 47 | if (binding != null) { 48 | binding.type(Type.merge(Type.getTypeFromValue(value), binding.type())); 49 | linker.invalidate(nameAndType); 50 | } 51 | return false; 52 | } 53 | 54 | @SuppressWarnings("unused") // used by a method handle 55 | private static void throwOptimisticError(Object value) { 56 | throw OptimisticError.newOptimisticError(value); 57 | } 58 | 59 | private static final MethodHandle THROW_OPTIMISTIC_ERROR, IS_INSTANCE; 60 | static final MethodHandle DEOPT_ARGS, DEOPT_RETURN; 61 | static { 62 | Lookup lookup = MethodHandles.lookup(); 63 | try { 64 | THROW_OPTIMISTIC_ERROR = lookup.findStatic(RT.class, "throwOptimisticError", 65 | MethodType.methodType(void.class, Object.class)); 66 | IS_INSTANCE = MethodHandles.publicLookup().findVirtual(Class.class, "isInstance", 67 | MethodType.methodType(boolean.class, Object.class)); 68 | DEOPT_ARGS = lookup.findStatic(RT.class, "deopt_args", 69 | MethodType.methodType(boolean.class, Linker.class, String.class, Binding[].class, Lookup.class, String.class, MethodType.class, Object[].class)); 70 | DEOPT_RETURN = lookup.findStatic(RT.class, "deopt_return", 71 | MethodType.methodType(boolean.class, Linker.class, String.class, Binding.class, Lookup.class, String.class, MethodType.class, Object.class)); 72 | } catch (NoSuchMethodException | IllegalAccessException e) { 73 | throw new AssertionError(e); 74 | } 75 | } 76 | 77 | private static MethodHandle checkTypeAndConvert(Class returnType) { 78 | return MethodHandles.guardWithTest(IS_INSTANCE.bindTo(boxed(returnType)), 79 | MethodHandles.identity(Object.class).asType(MethodType.methodType(returnType, Object.class)), 80 | THROW_OPTIMISTIC_ERROR.asType(MethodType.methodType(returnType, Object.class))); 81 | } 82 | 83 | private static Class boxed(Class type) { 84 | if (!type.isPrimitive()) { 85 | return type; 86 | } 87 | switch(type.getName()) { 88 | case "int": 89 | return Integer.class; 90 | case "double": 91 | return Double.class; 92 | default: 93 | throw new AssertionError("unknown type " + type); 94 | } 95 | } 96 | 97 | public static CallSite bsm_convert(Lookup lookup, String name, MethodType methodType) { 98 | //System.out.println("convert " + methodType); 99 | 100 | MethodHandle target; 101 | Class returnType = methodType.returnType(); 102 | Class parameterType = methodType.parameterType(0); 103 | if (returnType == parameterType || returnType == Object.class || returnType == void.class) { 104 | target = MethodHandles.identity(parameterType).asType(methodType); 105 | } else { 106 | target = checkTypeAndConvert(returnType).asType(methodType); 107 | } 108 | return new ConstantCallSite(target); 109 | } 110 | 111 | static class Ops { 112 | private static BigInteger toBig(Object o) { 113 | if (o instanceof Integer) { 114 | return BigInteger.valueOf((Integer)o); 115 | } 116 | return (BigInteger)o; 117 | } 118 | 119 | public static int add(int a, int b) { 120 | //return a + b; 121 | try { 122 | return Math.addExact(a, b); 123 | } catch(ArithmeticException e) { 124 | throw OptimisticError.newOptimisticError(BigInteger.valueOf(a).add(BigInteger.valueOf(b))); 125 | } 126 | //throw OptimisticError.newOptimisticError(BigInteger.valueOf(a).add(BigInteger.valueOf(b))); 127 | } 128 | public static Object add(Object a, Object b) { 129 | return toBig(a).add(toBig(b)); 130 | } 131 | 132 | public static int sub(int a, int b) { 133 | //return a - b; 134 | try { 135 | return Math.subtractExact(a, b); 136 | } catch(ArithmeticException e) { 137 | throw OptimisticError.newOptimisticError(BigInteger.valueOf(a).subtract(BigInteger.valueOf(b))); 138 | } 139 | } 140 | public static Object sub(Object a, Object b) { 141 | return toBig(a).subtract(toBig(b)); 142 | } 143 | 144 | public static int mul(int a, int b) { 145 | try { 146 | return Math.multiplyExact(a, b); 147 | } catch(ArithmeticException e) { 148 | throw OptimisticError.newOptimisticError(BigInteger.valueOf(a).multiply(BigInteger.valueOf(b))); 149 | } 150 | } 151 | public static Object mul(Object a, Object b) { 152 | return toBig(a).multiply(toBig(b)); 153 | } 154 | 155 | public static int div(int a, int b) { 156 | return a / b; 157 | } 158 | public static Object div(Object a, Object b) { 159 | return toBig(a).divide(toBig(b)); 160 | } 161 | 162 | public static boolean lt(int a, int b) { 163 | return a < b; 164 | } 165 | public static boolean lt(Object a, Object b) { 166 | return toBig(a).compareTo(toBig(b)) < 0; 167 | } 168 | public static boolean le(int a, int b) { 169 | return a <= b; 170 | } 171 | public static boolean le(Object a, Object b) { 172 | return toBig(a).compareTo(toBig(b)) <= 0; 173 | } 174 | 175 | public static boolean gt(int a, int b) { 176 | return a > b; 177 | } 178 | public static boolean gt(Object a, Object b) { 179 | return toBig(a).compareTo(toBig(b)) > 0; 180 | } 181 | public static boolean ge(int a, int b) { 182 | return a >= b; 183 | } 184 | public static boolean ge(Object a, Object b) { 185 | return toBig(a).compareTo(toBig(b)) >= 0; 186 | } 187 | 188 | public static boolean eq(int a, int b) { 189 | return a == b; 190 | } 191 | public static boolean eq(Object a, Object b) { 192 | return toBig(a).equals(toBig(b)); 193 | } 194 | public static boolean ne(int a, int b) { 195 | return a != b; 196 | } 197 | public static boolean ne(Object a, Object b) { 198 | return !toBig(a).equals(toBig(b)); 199 | } 200 | 201 | static final HashMap OP_MAP; 202 | static { 203 | Lookup lookup = MethodHandles.lookup(); 204 | HashMap opMap = new HashMap<>(); 205 | for(Method method: Ops.class.getMethods()) { 206 | if (method.getParameterCount() != 2) { 207 | continue; 208 | } 209 | int index = (method.getParameterTypes()[0] == int.class)? 0: 1; 210 | MethodHandle target; 211 | try { 212 | target = lookup.unreflect(method); 213 | } catch (IllegalAccessException e) { 214 | throw new AssertionError(e); 215 | } 216 | opMap.computeIfAbsent(method.getName(), name -> new MethodHandle[2])[index] = target; 217 | } 218 | OP_MAP = opMap; 219 | } 220 | } 221 | 222 | public static CallSite bsm_op(Lookup lookup, String name, MethodType methodType, Linker linker) { 223 | //System.out.println("link op " + name + methodType); 224 | 225 | MethodHandle[] mhs = Ops.OP_MAP.get(name); 226 | if (mhs == null) { 227 | throw new UnsupportedOperationException(name + methodType); 228 | } 229 | MethodHandle target; 230 | if (mhs[0].type() == methodType) { 231 | target = mhs[0]; 232 | } else { 233 | target = mhs[1]; 234 | if (target.type().returnType() != methodType.returnType()) { 235 | target = MethodHandles.filterReturnValue(target, checkTypeAndConvert(methodType.returnType())); 236 | } 237 | target = target.asType(methodType); 238 | } 239 | return new ConstantCallSite(target); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/tools/Analyzers.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.tools; 2 | 3 | import java.io.InputStreamReader; 4 | import java.io.Reader; 5 | 6 | import com.github.forax.vmboiler.sample.script.lexer.RuleEnum; 7 | import com.github.forax.vmboiler.sample.script.parser.TerminalEnum; 8 | import com.github.forax.vmboiler.sample.script.parser.NonTerminalEnum; 9 | import com.github.forax.vmboiler.sample.script.parser.ProductionEnum; 10 | import com.github.forax.vmboiler.sample.script.parser.VersionEnum; 11 | import com.github.forax.vmboiler.sample.script.lexer.LexerDataTable; 12 | import com.github.forax.vmboiler.sample.script.parser.ParserDataTable; 13 | import com.github.forax.vmboiler.sample.script.tools.ToolsDataTable; 14 | import com.github.forax.vmboiler.sample.script.tools.GrammarEvaluator; 15 | import com.github.forax.vmboiler.sample.script.tools.TerminalEvaluator; 16 | 17 | import fr.umlv.tatoo.runtime.buffer.LexerBuffer; 18 | import fr.umlv.tatoo.runtime.buffer.TokenBuffer; 19 | import fr.umlv.tatoo.runtime.buffer.impl.LocationTracker; 20 | import fr.umlv.tatoo.runtime.buffer.impl.ReaderWrapper; 21 | import fr.umlv.tatoo.runtime.lexer.LexerTable; 22 | import fr.umlv.tatoo.runtime.parser.ParserTable; 23 | import fr.umlv.tatoo.runtime.tools.DataViewer; 24 | import fr.umlv.tatoo.runtime.tools.Debug; 25 | import fr.umlv.tatoo.runtime.tools.SemanticStack; 26 | import fr.umlv.tatoo.runtime.tools.ToolsTable; 27 | import fr.umlv.tatoo.runtime.tools.builder.Builder; 28 | 29 | /** Helper methods that can be used to run a couple lexer/parser on a text. 30 | * 31 | * This class is generated - please do not edit it 32 | */ 33 | public class Analyzers { 34 | /** 35 | * Runs the analyzer (lexer+parser) on a reader and print recognized tokens and 36 | * applied parser rules on error input (see {@link Debug}). 37 | * @param reader the source of standard input if null 38 | * @param terminalEvaluator the terminal evaluator or just method call printer if null 39 | * @param grammarEvaluator the grammar evaluator or just method call printer if null 40 | * @param start the start or default start if null 41 | * @param version the version of default version if null 42 | */ 43 | public static void runDebug(Reader reader, 44 | TerminalEvaluator terminalEvaluator, 45 | GrammarEvaluator grammarEvaluator, 46 | NonTerminalEnum start, 47 | VersionEnum version) { 48 | if (reader==null) 49 | reader=new InputStreamReader(System.in); 50 | @SuppressWarnings("unchecked") TerminalEvaluator debugTerminalEvaluator = 51 | Debug.createTraceProxy(TerminalEvaluator.class,terminalEvaluator); 52 | GrammarEvaluator debugGrammarEvaluator = Debug.createTraceProxy(GrammarEvaluator.class,grammarEvaluator); 53 | run(reader,debugTerminalEvaluator,debugGrammarEvaluator, 54 | start,version); 55 | } 56 | 57 | /** Runs the analyzer (lexer+parser) on a reader and sends recognized tokens 58 | * as CharSequence. Tokens are transformed to objects by the terminal evaluator. 59 | * At last, the grammar evaluator is called with these objects. 60 | * 61 | * This implementation uses a {@link fr.umlv.tatoo.runtime.buffer.impl.ReaderWrapper} 62 | * configured with a location tracker as buffer and calls. 63 | * 64 | * @param reader a reader used to obtain the characters of the text to parse. 65 | * @param terminalEvaluator an interface that returns the value of a token. 66 | * @param grammarEvaluator an interface that evaluates the grammar productions. 67 | * @param start a start non terminal of the grammar used as root state of the parser. 68 | * If start is null, 69 | * the {@link fr.umlv.tatoo.runtime.parser.ParserTable#getDefaultStart() default start} 70 | * non terminal is used. 71 | * @param version a version of the grammar used to parse the reader. 72 | * If version is null, 73 | * the {@link fr.umlv.tatoo.runtime.parser.ParserTable#getDefaultVersion() default version} 74 | * of the grammar is used. 75 | * 76 | * @see #run(TokenBuffer, TerminalEvaluator, GrammarEvaluator, NonTerminalEnum, VersionEnum) 77 | */ 78 | public static void run( 79 | Reader reader, 80 | TerminalEvaluator terminalEvaluator, 81 | GrammarEvaluator grammarEvaluator, 82 | NonTerminalEnum start, 83 | VersionEnum version) { 84 | 85 | run(new ReaderWrapper(reader, new LocationTracker()), terminalEvaluator, grammarEvaluator, start, version); 86 | } 87 | 88 | public static &LexerBuffer,D> void runDebug( 89 | B tokenBuffer, 90 | TerminalEvaluator terminalEvaluator, 91 | GrammarEvaluator grammarEvaluator, 92 | NonTerminalEnum start, 93 | VersionEnum version) { 94 | @SuppressWarnings("unchecked") TerminalEvaluator debugTerminalEvaluator = 95 | Debug.createTraceProxy(TerminalEvaluator.class,terminalEvaluator); 96 | GrammarEvaluator debugGrammarEvaluator = Debug.createTraceProxy(GrammarEvaluator.class,grammarEvaluator); 97 | run(tokenBuffer,debugTerminalEvaluator,debugGrammarEvaluator, 98 | start,version); 99 | } 100 | 101 | /** Runs the analyzer (lexer+parser) on a token buffer and sends recognized tokens 102 | * as CharSequence. Tokens are transformed to objects by the terminal evaluator. 103 | * At last, the grammar evaluator is called with these objects. 104 | * 105 | * It is up to the caller to create its buffer and to provide or not a location tracker. 106 | * 107 | * @param type of the buffer. 108 | * 109 | * @param tokenBuffer the token buffer used to obtain the characters of the text to parse. 110 | * @param terminalEvaluator an interface that returns the value of a token. 111 | * @param grammarEvaluator an interface that evaluates the grammar productions. 112 | * @param start a start non terminal of the grammar used as root state of the parser. 113 | * If start is null, 114 | * the {@link fr.umlv.tatoo.runtime.parser.ParserTable#getDefaultStart() default start} 115 | * non terminal is used. 116 | * @param version a version of the grammar used to parse the reader. 117 | * If version is null, 118 | * the {@link fr.umlv.tatoo.runtime.parser.ParserTable#getDefaultVersion() default version} 119 | * of the grammar is used. 120 | * 121 | * @see #run(Reader, TerminalEvaluator, GrammarEvaluator, NonTerminalEnum, VersionEnum) 122 | */ 123 | public static &LexerBuffer,D> void run( 124 | B tokenBuffer, 125 | TerminalEvaluator terminalEvaluator, 126 | GrammarEvaluator grammarEvaluator, 127 | NonTerminalEnum start, 128 | VersionEnum version) { 129 | 130 | analyzerTokenBufferBuilder(tokenBuffer,terminalEvaluator,grammarEvaluator,new SemanticStack()). 131 | start(start).version(version).createLexer().run(); 132 | } 133 | 134 | public static Builder.LexerTableBuilder lexerBuilder() { 135 | return Builder.lexer(LexerDataTable.createTable()); 136 | } 137 | 138 | public static Builder.ParserTableBuilder parserBuilder() { 139 | return Builder.parser(ParserDataTable.createTable()); 140 | } 141 | 142 | public static Builder.AnalyzerTableBuilder analyzerBuilder() { 143 | return Builder.analyzer(LEXER_TABLE,PARSER_TABLE,TOOLS_TABLE); 144 | } 145 | 146 | public static Builder.AnalyzerBuilder analyzerLexerBufferBuilder(B lexerBuffer, 147 | TerminalEvaluator terminalEvaluator, GrammarEvaluator grammarEvaluator, 148 | SemanticStack semanticStack) { 149 | return analyzerBuilder().buffer(lexerBuffer).listener(AnalyzerProcessor.createAnalyzerProcessor(terminalEvaluator,grammarEvaluator, 150 | DataViewer.getIdentityDataViewer(),semanticStack)); 151 | } 152 | 153 | public static &LexerBuffer,D> Builder.AnalyzerBuilder analyzerTokenBufferBuilder(B tokenBuffer, 154 | TerminalEvaluator terminalEvaluator, GrammarEvaluator grammarEvaluator, 155 | SemanticStack semanticStack) { 156 | return analyzerBuilder().buffer(tokenBuffer).listener(AnalyzerProcessor.createAnalyzerProcessor(terminalEvaluator,grammarEvaluator, 157 | DataViewer.getTokenBufferViewer(),semanticStack)); 158 | } 159 | 160 | private static final LexerTable LEXER_TABLE; 161 | private static final ParserTable PARSER_TABLE; 162 | private static final ToolsTable TOOLS_TABLE; 163 | 164 | static { 165 | LEXER_TABLE = LexerDataTable.createTable(); 166 | PARSER_TABLE = ParserDataTable.createTable(); 167 | TOOLS_TABLE = ToolsDataTable.createToolsTable(); 168 | } 169 | 170 | /* sample main method 171 | 172 | public static void main(String[] args) throws java.io.IOException { 173 | java.io.Reader reader; 174 | if (args.length>0) { 175 | reader = new java.io.FileReader(args[0]); 176 | } else { 177 | reader = new java.io.InputStreamReader(System.in); 178 | } 179 | //TODO implements the terminal attribute evaluator here 180 | TerminalEvaluator terminalEvaluator = fr.umlv.tatoo.runtime.tools.Debug.createTraceProxy(TerminalEvaluator.class); 181 | 182 | //TODO implements the grammar evaluator here 183 | GrammarEvaluator grammarEvaluator = fr.umlv.tatoo.runtime.tools.Debug.createTraceProxy(GrammarEvaluator.class); 184 | 185 | //TODO choose a start non terminal and a version here 186 | VersionEnum version = VersionEnum.DEFAULT; 187 | NonTerminalEnum start = NonTerminalEnum.script; 188 | 189 | Analyzers.run(reader, terminalEvaluator, grammarEvaluator, start, version); 190 | }*/ 191 | } 192 | -------------------------------------------------------------------------------- /test/getting-started.md: -------------------------------------------------------------------------------- 1 | Getting Started 2 | === 3 | 4 | ###Intro 5 | 6 | Let say i want to implement a function that add 1 to a value, 7 | By example, if I want to implement it in Ruby, I will write something like this 8 | ```ruby 9 | def addOne(x) 10 | return x + 1 11 | end 12 | ``` 13 | 14 | Ruby (like Python) handles overflow by transforming integers that can not be stored 15 | on 32/64 bits (Fixnum) into an infinite big number (Bignum) automatically. 16 | 17 | ```ruby 18 | result = addOne(1) 19 | puts result.is_a? Fixnum # true 20 | 21 | MAX = (2 ** (0.size * 8 - 2) - 1) 22 | puts MAX.is_a? Fixnum # true 23 | 24 | result2 = addOne(FIXNUM_MAX) 25 | puts result2.is_a? Fixnum # false 26 | ``` 27 | 28 | Now say that i want to implement Ruby on the JVM, 29 | the equivalent Java code will be something like this 30 | 31 | ```java 32 | static Object addOne(int x) { 33 | try { 34 | return Math.addExact(x , 1); 35 | } catch(ArithmeticException e) { // overflow ! 36 | return BigInteger.valueOf(x).add(BiInteger.ONE); 37 | } 38 | } 39 | ``` 40 | 41 | The return type of addOne is an Object because if there is an overflow, 42 | the function can return an object of type BigInteger. 43 | The problem is that it means that the result of Math.addExact also need 44 | to be transformed to an Object meaning that we are asking the Java VM 45 | to box a primitive 32bits into an object because it may overflow. 46 | 47 | The idea of the vmboiler is to avoid that boxing to only pay the price 48 | of using objects when there is an overflow. 49 | So instead the equivalent Java code for the generated code should be 50 | something like this 51 | 52 | ```java 53 | static int addOne(int x) { 54 | try { 55 | return Math.addExact(x , 1); 56 | } catch(ArithmeticException e) { // overflow ! 57 | throw OptimisiticError.newOptimisticError( 58 | BigInteger.valueOf(x).add(BigInteger.ONE)); 59 | } 60 | } 61 | ``` 62 | 63 | addOne() returns an int so in the general case, there is no boxing, 64 | and if there is an overflow, we use an exception 65 | (named [OptimisticError](../src/com/github/forax/vmboiler/rt/OptimisticError.java)) 66 | to indicate that the returned value can not be stored in the return type. 67 | 68 | Things get a little more complex when trying to call the method addOne, 69 | let suppose i want a method addTwo defined like this 70 | 71 | ```ruby 72 | def addTwo(x) 73 | return addOne(addOne(x)) 74 | end 75 | ``` 76 | 77 | The equivalent Java code is something like that 78 | 79 | ```java 80 | static int addTwo(int x) { 81 | int result2; 82 | try { 83 | int result1; 84 | try { 85 | result1 = addOne(x); 86 | } catch(OptimisiticError e) { 87 | // e.value() is an Object not an int 88 | // how to initialize result1 ? 89 | } 90 | result2 = addOne(result1); 91 | } catch(OptimisiticError e) { 92 | // e.value() is an Object not an int 93 | // how to initialize result2 ? 94 | } 95 | return result2; 96 | } 97 | ``` 98 | 99 | As you can see we have a problem because the result of the first call 100 | to addOne() need to be used as argument of the second call to addOne. 101 | But addOne() can either return an int or return an Object and 102 | we still want to avoid boxing. 103 | 104 | To solve that, the idea is to consider that the type of result1 105 | (and result2) is a kind of mixed type that can be either an int 106 | or an object stored into two different local variables 107 | (on storing an int, one storing an object) with the convention 108 | that if the object part is a known constant NONE, then the value 109 | is stored in the int part, otherwise the value is in the object part. 110 | 111 | So the corresponding Java code is in fact something more like that 112 | ```java 113 | static int addTwo(int x) { 114 | Object result2_o 115 | int result2_i; 116 | try { 117 | Object result1_o; 118 | int result1_i; 119 | try { 120 | result1_i = addOne(x); // take an int 121 | result1_o = NONE; 122 | } catch(OptimisiticError e) { 123 | result1_o = e.value(); 124 | result1_i = 0; 125 | } 126 | if (result1_o == NONE) { 127 | result2_i = addOne(result1_i); // take an int 128 | result2_o = NONE; 129 | } else { 130 | result2_o = addOne(result1_o); // take an object 131 | result2_i = 0; 132 | } 133 | } catch(OptimisiticError e) { 134 | result2_o = e.value(); 135 | result2_i = 0; 136 | } 137 | if (result2_o == NONE) { 138 | return result2_i; 139 | } 140 | throw OptimisiticError.newOptimisiticError(result2_o); 141 | } 142 | ``` 143 | 144 | You may think that this code will never be fast, given 145 | the size of the bytecode required but you will be wrong because 146 | this code is highly optimizable because even simple JITs do 147 | constant propagation and catch/branch profiling. 148 | 149 | So for the JIT, given that for the first catch block will 150 | be never executed if there is no overflow, 151 | result1_o will be equal to NONE, so the code of the else branch 152 | will never be executed so result2_o will be equal to NONE. 153 | 154 | The equivalent code in Java to what the JIT will generate is 155 | ```java 156 | static int addTwo(int x) { 157 | int result2_i; 158 | try { 159 | int result1_i; 160 | try { 161 | result1_i = addOne(x); // take an int 162 | } 163 | result2_i = addOne(result1_i); // take an int 164 | } 165 | return result2_i; 166 | } 167 | ``` 168 | 169 | which is really close to the code someone will write in Java 170 | if there is no overflow. 171 | 172 | The idea of the vmboiler is to just use the vmboiler API 173 | to generate the classical code and to let the vmboiler code 174 | generator to handle the code that deal with 175 | the exception OptimisticError. 176 | 177 | 178 | ### How to use the vm boiler ? 179 | 180 | First, you need to declare the types of your languages and 181 | how they are mapped to the Java VM type. 182 | 183 | The boiler ask you to implement an interface 184 | [Type](../src/com/github/forax/vmboiler/Type.java) 185 | with two methods isMixed and vmType. 186 | * isMixed indicates if a compound type composed of a primitive type 187 | and an object type. 188 | * vmType returns the corresponding Java VM type or the primitive part 189 | if the type is mixed. 190 | 191 | Here is the declaration of the types for our example 192 | ```java 193 | enum Types implements Type { 194 | INT, INT_MIXED, ANY 195 | ; 196 | @Override 197 | public boolean isMixed() { 198 | return this == INT_MIXED; 199 | } 200 | @Override 201 | public String vmType() { 202 | return (this == ANY)? Type.VM_OBJECT: Type.VM_INT; 203 | } 204 | } 205 | ``` 206 | 207 | After that, the CodeGen object allow you to generate code 208 | using constants and variables in a register based fashion. 209 | First, the CodeGen takes a [ASM](http://asm.ow2.org/) 210 | MethodVisitor] as parameter and the function return type. 211 | Here, the return type is INT_MIXED because it can be either 212 | a 32 bits int or something that should be boxed into an object. 213 | Then the code declare a 214 | [variable](../src/com/github/forax/vmboiler/Var.java) 215 | 'x' of type INT which is the parameter of the function. 216 | It creates a 217 | [constant](../src/com/github/forax/vmboiler/Constant.java) 218 | '1'. 219 | The code then declare another variable 'result' that will 220 | contains the result value of the operation '+'. 221 | Note that the result value is also a MIXED_INT because 222 | '+' can overflow. And at the end, the result value is 223 | returned as return value of the function. 224 | 225 | ```java 226 | private static byte[] generateAdd1Int() { 227 | ... 228 | CodeGen codeGen = new CodeGen(mv, Types.INT_MIXED); 229 | Var x = codeGen.createVar(Types.INT); 230 | Constant one = new Constant(Types.INT, 1); 231 | Var result = codeGen.createVar(Types.ANY); 232 | // call + here 233 | codeGen.ret(result); 234 | codeGen.end(); 235 | ... 236 | } 237 | ``` 238 | 239 | Now, we need to add the call to the operator '+', 240 | because the semantics of '+' depends on the semantics 241 | of the dynamic language, the boiler rely on the JVM instruction 242 | [invokedynamic](https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/package-summary.html) 243 | to do the linking at runtime between the call of 244 | the operation and the code of the operation. 245 | So codeGen.call that emit a call takes 5 parameters that explain 246 | how to find the code of '+' at runtime and 4 regular parameters, 247 | the result variable (result), the name of the operator (add) 248 | and the two arguments (x and one). 249 | 250 | ```java 251 | private static byte[] generateAdd1Any() { 252 | ... 253 | CodeGen codeGen = new CodeGen(mv, Types.ANY); 254 | Var x = codeGen.createVar(Types.INT); 255 | Constant one = new Constant(Types.INT, 1); 256 | Var result = codeGen.createVar(Types.ANY); 257 | codeGen.call(BSM, EMPTY_ARRAY, DEOPT_ARGS, DEOPT_RET, new Object[] { "x" }, 258 | result, "add", x, one); 259 | codeGen.ret(result); 260 | codeGen.end(); 261 | ... 262 | } 263 | ``` 264 | 265 | The first two parameters, BSM and EMPTY_ARRAY, define the bootstrap method 266 | of the invokedynamic instructions and the constant arguments that can be 267 | sent to the bootstrap method. For our example, we will not sent any constant 268 | arguments that why we use an empty array. 269 | 270 | The bootstrap method is a classical bootstrap method of invokedynamic 271 | ```java 272 | public static CallSite bsm(Lookup lookup, String name, MethodType methodType) throws Throwable { 273 | ... 274 | } 275 | ``` 276 | 277 | The third and the forth parameters defined the deoptimization methods, 278 | i.e. methods that are called if either the arguments of the call or 279 | the return value of the call doesn't fit in the declared type. 280 | The fifth argument is an array of constant arguments that will be passed 281 | to the two methods like the constant arguments of a bootstrap method. 282 | A deoptimization method will be called with as first arguments 283 | the lookup object, the name and the method type of the deoptimized method 284 | followed either by the arguments of the call or the return value. 285 | The return boolean value indicate if the method must be called for every 286 | deoptimization (if true) or only once (if false). 287 | 288 | ```java 289 | public static boolean deopt_args(Lookup lookup, String name, MethodType methodType, Object[] values, String parameterNames) throws Throwable { 290 | ... 291 | return false; 292 | } 293 | 294 | public static boolean deopt_ret(Lookup lookup, String name, MethodType methodType, Object value, String parameterNames) throws Throwable { 295 | ... 296 | return false; 297 | } 298 | ``` 299 | 300 | The last step,, is to implement the bootstrap method and the two deoptimization 301 | methods depending on the semantics of your language. 302 | 303 | By example, for our small Ruby example, we need to implement 304 | how to do the addition of two primitive ints, of two objects that can be 305 | either boxed ints or bigints. 306 | We need in the bootstrap method to check if the requested implementation 307 | is the addition of primitive ints or not. 308 | Get a method pointer (a method handle) to the implementation 309 | and if the implementation may return an object and requested return type 310 | is an int, check at runtime if the return value if a small int or a big int. 311 | The deoptimisation methods will just print a debug message. 312 | 313 | You can play with the full code of the [Example](src/test/Example.java) and 314 | its runtime support [ExamplerRT](src/test/ExampleRT.java) 315 | by commenting/uncommenting the first lines of the main. 316 | 317 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /script/src/com/github/forax/vmboiler/sample/script/Generator.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script; 2 | 3 | import static org.objectweb.asm.Opcodes.*; 4 | 5 | import java.io.IOException; 6 | import java.io.PrintWriter; 7 | import java.io.UncheckedIOException; 8 | import java.lang.invoke.CallSite; 9 | import java.lang.invoke.MethodHandle; 10 | import java.lang.invoke.MethodHandles; 11 | import java.lang.invoke.MethodHandles.Lookup; 12 | import java.lang.invoke.MethodType; 13 | import java.lang.reflect.Field; 14 | import java.math.BigInteger; 15 | import java.nio.file.Files; 16 | import java.nio.file.Paths; 17 | import java.util.ArrayList; 18 | import java.util.Arrays; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | import org.objectweb.asm.ClassReader; 24 | import org.objectweb.asm.ClassWriter; 25 | import org.objectweb.asm.Handle; 26 | import org.objectweb.asm.Label; 27 | import org.objectweb.asm.MethodVisitor; 28 | import org.objectweb.asm.util.CheckClassAdapter; 29 | 30 | import sun.misc.Unsafe; 31 | 32 | import com.github.forax.vmboiler.CodeGen; 33 | import com.github.forax.vmboiler.Constant; 34 | import com.github.forax.vmboiler.Value; 35 | import com.github.forax.vmboiler.sample.script.Expr.Parameter; 36 | import com.github.forax.vmboiler.sample.script.Expr.*; 37 | 38 | @SuppressWarnings("restriction") 39 | public class Generator { 40 | static class Env { 41 | final CodeGen codeGen; 42 | final Linker linker; 43 | final String linkerPlaceholder; 44 | final String nameAndType; 45 | final ConstantPoolPatch constantPoolPatch; 46 | final HashMap varMap; 47 | final HashMap bindingMap; 48 | 49 | Var expectedVar; 50 | 51 | Env(CodeGen codeGen, Linker linker, String nameAndType, ConstantPoolPatch constantPoolPatch, HashMap varMap, HashMap bindingMap) { 52 | this.codeGen = codeGen; 53 | this.linker = linker; 54 | this.linkerPlaceholder = constantPoolPatch.encode(linker); 55 | this.nameAndType = nameAndType; 56 | this.constantPoolPatch = constantPoolPatch; 57 | this.varMap = varMap; 58 | this.bindingMap = bindingMap; 59 | } 60 | 61 | String encodeDeopt(MethodHandle mh, Object o) { 62 | return constantPoolPatch.encode(MethodHandles.insertArguments(mh, 0, linker, nameAndType, o)); 63 | } 64 | String encodeConst(Object constant) { 65 | return constantPoolPatch.encode(constant); 66 | } 67 | 68 | Env expectedVar(Var var) { 69 | expectedVar = var; 70 | return this; 71 | } 72 | } 73 | 74 | static class Var extends com.github.forax.vmboiler.Var { 75 | final String name; 76 | final Binding binding; 77 | 78 | Var(Type type, String name, Binding binding) { 79 | super(type); 80 | this.name = name; 81 | this.binding = binding; 82 | } 83 | } 84 | 85 | private static final Constant NULL = new Constant(Type.OBJECT, null); 86 | 87 | public static MethodHandle generate(Fn fn, Linker linker, HashMap bindingMap, Type returnType, String name, Type[] parameterTypes) { 88 | ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS|ClassWriter.COMPUTE_FRAMES); 89 | writer.visit(V1_8, ACC_PUBLIC|ACC_SUPER, "Fn", null, "java/lang/Object", null); 90 | ConstantPoolPatch constantPoolPatch = new ConstantPoolPatch(writer); 91 | 92 | String desc = Arrays.stream(parameterTypes).map(Type::vmType).collect(Collectors.joining("", "(", ")")) 93 | + returnType.vmType(); 94 | MethodVisitor mv = writer.visitMethod(ACC_PUBLIC|ACC_STATIC, name, desc, null, null); 95 | CodeGen codeGen = new CodeGen(mv, returnType); 96 | mv.visitCode(); 97 | 98 | HashMap varMap = new HashMap<>(); 99 | ArrayList mixedParameterVars = new ArrayList<>(); 100 | List parameters = fn.parameters(); 101 | for(int i = 0; i < parameters.size(); i++) { 102 | Type parameterType = parameterTypes[i]; 103 | Parameter parameter = parameters.get(i); 104 | Binding binding = bindingMap.get(parameter); 105 | Var parameterVar = createVar(codeGen, parameterType, parameter.name(), false, binding); 106 | if (binding.type() == parameterType) { 107 | varMap.put(binding, parameterVar); 108 | } else { 109 | mixedParameterVars.add(parameterVar); // need a new variable with a mixed type 110 | } 111 | } 112 | 113 | // we need two passes here, because all parameter declarations must be done first 114 | // but some parameters may have a mixed type so require a new variable 115 | Env env = new Env(codeGen, linker, name + desc, constantPoolPatch, varMap, bindingMap); 116 | for(Var parameterVar: mixedParameterVars) { 117 | Binding binding = parameterVar.binding; 118 | Var var = createVar(codeGen, binding.type(), parameterVar.name, false, binding); 119 | convert(var, parameterVar, env); 120 | varMap.put(binding, var); 121 | } 122 | 123 | Value value = VISITOR.call(fn.block(), env); 124 | 125 | if (returnType == value.type() || returnType.erase() == value.type()) { 126 | env.codeGen.ret(value); 127 | } else { 128 | Var var = createVar(codeGen, returnType, null, !returnType.isMixed(), null); 129 | convert(var, value, env); 130 | env.codeGen.ret(var); 131 | } 132 | 133 | codeGen.end(); 134 | mv.visitMaxs(-1, -1); 135 | mv.visitEnd(); 136 | writer.visitEnd(); 137 | 138 | byte[] bytecode = writer.toByteArray(); 139 | 140 | CheckClassAdapter.verify(new ClassReader(bytecode), true, new PrintWriter(System.out)); 141 | /*try { 142 | Files.write(Paths.get(name + ".class"), bytecode); 143 | } catch (IOException e) { 144 | throw new UncheckedIOException(e); 145 | }*/ 146 | 147 | Class clazz = UNSAFE.defineAnonymousClass(RT.class, bytecode, constantPoolPatch.createPatchArray()); 148 | UNSAFE.ensureClassInitialized(clazz); 149 | try { 150 | return MethodHandles.publicLookup().findStatic(clazz, name, MethodType.fromMethodDescriptorString(desc, null)); 151 | } catch (NoSuchMethodException | IllegalAccessException | TypeNotPresentException e) { 152 | throw new AssertionError(e); 153 | } 154 | } 155 | 156 | private static final Unsafe UNSAFE; 157 | static { 158 | try { 159 | Field field = Unsafe.class.getDeclaredField("theUnsafe"); 160 | field.setAccessible(true); 161 | UNSAFE = (Unsafe)field.get(null); 162 | } catch (NoSuchFieldException | IllegalAccessException e) { 163 | throw new AssertionError(e); 164 | } 165 | } 166 | 167 | private static Var createVar(CodeGen codeGen, Type type, String name, boolean stackAllocated, Binding binding) { 168 | if (stackAllocated) { 169 | return new Var(type, name, binding); 170 | } 171 | return codeGen.createVar(type, t -> new Var(type, name, binding)); 172 | } 173 | 174 | private static void convert(Var var, Value value, Env env) { 175 | if (var == value) { 176 | return; 177 | } 178 | if (var.type() == value.type() || ((Type)var.type()).erase() == value.type()) { 179 | env.codeGen.move(var, value); 180 | } else { 181 | Binding[] bindings = new Binding[] { (value instanceof Var)? ((Var)value).binding: null }; 182 | env.codeGen.call(BSM_CONVERT, EMPTY_ARRAY, 183 | env.encodeDeopt(RT.DEOPT_ARGS, bindings), 184 | env.encodeDeopt(RT.DEOPT_RETURN, var.binding), 185 | EMPTY_ARRAY, 186 | var, "convert", value); 187 | } 188 | } 189 | 190 | private static final Object[] EMPTY_ARRAY = new Object[0]; 191 | 192 | private static final String RT_NAME = RT.class.getName().replace('.', '/'); 193 | private static final Handle BSM = new Handle(H_INVOKESTATIC, RT_NAME, "bsm", 194 | MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Linker.class).toMethodDescriptorString()); 195 | private static final Handle BSM_CONVERT = new Handle(H_INVOKESTATIC, RT_NAME, "bsm_convert", 196 | MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class).toMethodDescriptorString()); 197 | private static final Handle BSM_OP = new Handle(H_INVOKESTATIC, RT_NAME, "bsm_op", 198 | MethodType.methodType(CallSite.class, Lookup.class, String.class, MethodType.class, Linker.class).toMethodDescriptorString()); 199 | 200 | private static final Visitor VISITOR = new Visitor() 201 | .when(Literal.class, (literal, env) -> { 202 | Object constant = literal.constant(); 203 | if (constant instanceof Integer) { 204 | return new Constant(Type.INT, constant); 205 | } 206 | if (constant instanceof BigInteger) { 207 | return new Constant(Type.OBJECT, env.encodeConst(constant)); 208 | } 209 | return new Constant(Type.OBJECT, constant); 210 | }) 211 | .when(Block.class, (block, env) -> { 212 | List exprs = block.exprs(); 213 | int size = exprs.size(); 214 | if (size == 0) { 215 | return NULL; 216 | } 217 | Var expectedVar = env.expectedVar; 218 | for(int i = 0; i < exprs.size() - 1; i++) { 219 | Generator.VISITOR.call(exprs.get(i), env.expectedVar(null)); 220 | } 221 | return Generator.VISITOR.call(exprs.get(exprs.size() - 1), env.expectedVar(expectedVar)); 222 | }) 223 | .when(VarAccess.class, (varAccess, env) -> { 224 | Binding binding = env.bindingMap.get(varAccess); 225 | return env.varMap.get(binding); 226 | }) 227 | .when(VarAssignment.class, (varAssignment, env) -> { 228 | Binding binding = env.bindingMap.get(varAssignment); 229 | Var var = env.varMap.get(binding); 230 | if (var == null) { 231 | var = (Var)createVar(env.codeGen, binding.type(), varAssignment.name(), false, binding); 232 | env.varMap.put(binding, var); 233 | } 234 | Value value = Generator.VISITOR.call(varAssignment.expr(), env.expectedVar(var)); 235 | convert(var, value, env); 236 | return value; 237 | }) 238 | .when(Call.class, (call, env) -> { 239 | Binding binding = env.bindingMap.get(call); 240 | Var expectedVar = env.expectedVar; 241 | Value[] values = call.exprs().stream().map(expr -> Generator.VISITOR.call(expr, env.expectedVar(null))).toArray(Value[]::new); 242 | Binding[] bindings = Arrays.stream(values).map(value -> (value instanceof Var)? ((Var)value).binding: null).toArray(Binding[]::new); 243 | Var rVar = (expectedVar != null && binding.type() == expectedVar.type())? expectedVar: 244 | createVar(env.codeGen, binding.type(), null, false, binding); 245 | Handle bsm = call.optionalOp().map(op -> BSM_OP).orElse(BSM); 246 | 247 | // make call to fibo explicit 248 | /*if ((call.name() + "(I)I").equals(env.nameAndType)) { 249 | env.codeGen.methodVisitor().visitVarInsn(ILOAD, 1 + ((Var)values[0]).slot()); 250 | env.codeGen.methodVisitor().visitMethodInsn(INVOKESTATIC, "Fn", call.name(), "(I)I", false); 251 | env.codeGen.methodVisitor().visitVarInsn(ISTORE, 1 + rVar.slot()); 252 | env.codeGen.methodVisitor().visitFieldInsn(GETSTATIC, "com/github/forax/vmboiler/rt/RT", "NONE", "Ljava/lang/Object;"); 253 | env.codeGen.methodVisitor().visitVarInsn(ASTORE, rVar.slot()); 254 | return rVar; 255 | }*/ 256 | // 257 | 258 | env.codeGen.call(bsm, new Object[] { env.linkerPlaceholder }, 259 | env.encodeDeopt(RT.DEOPT_ARGS, bindings), 260 | env.encodeDeopt(RT.DEOPT_RETURN, binding), 261 | EMPTY_ARRAY, 262 | rVar, call.name(), values); 263 | return rVar; 264 | }) 265 | .when(If.class, (if_, env) -> { 266 | Binding binding = env.bindingMap.get(if_); 267 | Var conditionVar = createVar(env.codeGen, Type.BOOL, null, true, null); 268 | Value value = Generator.VISITOR.call(if_.condition(), env.expectedVar(conditionVar)); 269 | Label label = new Label(); 270 | Label end = new Label(); 271 | Type type = binding.type(); 272 | Var expectedVar = env.expectedVar; 273 | Var rVar = (type == Type.VOID)? null: // no join (phi) if type is void 274 | (expectedVar != null && expectedVar.type() == type)? expectedVar: 275 | createVar(env.codeGen, type, null, false, binding); 276 | env.codeGen.jumpIfFalse(value, label); 277 | Value value1 = Generator.VISITOR.call(if_.truePart(), env.expectedVar(rVar)); 278 | if (rVar != null) { 279 | convert(rVar, value1, env); 280 | } 281 | env.codeGen.jump(end); 282 | env.codeGen.label(label); 283 | Value value2 = Generator.VISITOR.call(if_.falsePart(), env.expectedVar(rVar)); 284 | if (rVar != null) { 285 | convert(rVar, value2, env); 286 | } 287 | env.codeGen.label(end); 288 | return (rVar != null)? rVar: NULL; 289 | }) 290 | .when(While.class, (while_, env) -> { 291 | Label end = new Label(); 292 | Label test = new Label(); 293 | env.codeGen.label(test); 294 | Var conditionVar = createVar(env.codeGen, Type.BOOL, null, true, null); 295 | Value result = Generator.VISITOR.call(while_.condition(), env.expectedVar(conditionVar)); 296 | env.codeGen.jumpIfFalse(result, end); 297 | Generator.VISITOR.call(while_.body(), env.expectedVar(null)); 298 | env.codeGen.jump(test); 299 | env.codeGen.label(end); 300 | return NULL; 301 | }) 302 | ; 303 | } 304 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/lexer/LexerDataTable.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.lexer; 2 | 3 | import com.github.forax.vmboiler.sample.script.lexer.RuleEnum; 4 | import fr.umlv.tatoo.runtime.lexer.LexerTable; 5 | import fr.umlv.tatoo.runtime.lexer.rules.RuleData; 6 | import fr.umlv.tatoo.runtime.regex.CharRegexTable; 7 | import java.util.EnumMap; 8 | /** 9 | * This class is generated - please do not edit it 10 | */ 11 | public class LexerDataTable { 12 | 13 | public static LexerTable createTable() { 14 | return new LexerDataTable().table; 15 | } 16 | 17 | private LexerDataTable() { 18 | initassignMainAccepts(); 19 | initassignMainTransitions(); 20 | CharRegexTable assignMain = new CharRegexTable(1, assignMainTransitions, assignMainAccepts); 21 | RuleData assign = new RuleData(assignMain, null, 0, false); 22 | initcolonMainAccepts(); 23 | initcolonMainTransitions(); 24 | CharRegexTable colonMain = new CharRegexTable(1, colonMainTransitions, colonMainAccepts); 25 | RuleData colon = new RuleData(colonMain, null, 1, false); 26 | initeolMainAccepts(); 27 | initeolMainTransitions(); 28 | CharRegexTable eolMain = new CharRegexTable(1, eolMainTransitions, eolMainAccepts); 29 | RuleData eol = new RuleData(eolMain, null, 2, false); 30 | initlparMainAccepts(); 31 | initlparMainTransitions(); 32 | CharRegexTable lparMain = new CharRegexTable(1, lparMainTransitions, lparMainAccepts); 33 | RuleData lpar = new RuleData(lparMain, null, 3, false); 34 | initrparMainAccepts(); 35 | initrparMainTransitions(); 36 | CharRegexTable rparMain = new CharRegexTable(1, rparMainTransitions, rparMainAccepts); 37 | RuleData rpar = new RuleData(rparMain, null, 4, false); 38 | initaddMainAccepts(); 39 | initaddMainTransitions(); 40 | CharRegexTable addMain = new CharRegexTable(1, addMainTransitions, addMainAccepts); 41 | RuleData add = new RuleData(addMain, null, 5, false); 42 | initsubMainAccepts(); 43 | initsubMainTransitions(); 44 | CharRegexTable subMain = new CharRegexTable(1, subMainTransitions, subMainAccepts); 45 | RuleData sub = new RuleData(subMain, null, 6, false); 46 | initmulMainAccepts(); 47 | initmulMainTransitions(); 48 | CharRegexTable mulMain = new CharRegexTable(1, mulMainTransitions, mulMainAccepts); 49 | RuleData mul = new RuleData(mulMain, null, 7, false); 50 | initdivMainAccepts(); 51 | initdivMainTransitions(); 52 | CharRegexTable divMain = new CharRegexTable(1, divMainTransitions, divMainAccepts); 53 | RuleData div = new RuleData(divMain, null, 8, false); 54 | initremMainAccepts(); 55 | initremMainTransitions(); 56 | CharRegexTable remMain = new CharRegexTable(1, remMainTransitions, remMainAccepts); 57 | RuleData rem = new RuleData(remMain, null, 9, false); 58 | initeqMainAccepts(); 59 | initeqMainTransitions(); 60 | CharRegexTable eqMain = new CharRegexTable(1, eqMainTransitions, eqMainAccepts); 61 | RuleData eq = new RuleData(eqMain, null, 10, false); 62 | initneMainAccepts(); 63 | initneMainTransitions(); 64 | CharRegexTable neMain = new CharRegexTable(1, neMainTransitions, neMainAccepts); 65 | RuleData ne = new RuleData(neMain, null, 11, false); 66 | initltMainAccepts(); 67 | initltMainTransitions(); 68 | CharRegexTable ltMain = new CharRegexTable(1, ltMainTransitions, ltMainAccepts); 69 | RuleData lt = new RuleData(ltMain, null, 12, false); 70 | initleMainAccepts(); 71 | initleMainTransitions(); 72 | CharRegexTable leMain = new CharRegexTable(2, leMainTransitions, leMainAccepts); 73 | RuleData le = new RuleData(leMain, null, 13, false); 74 | initgtMainAccepts(); 75 | initgtMainTransitions(); 76 | CharRegexTable gtMain = new CharRegexTable(1, gtMainTransitions, gtMainAccepts); 77 | RuleData gt = new RuleData(gtMain, null, 14, false); 78 | initgeMainAccepts(); 79 | initgeMainTransitions(); 80 | CharRegexTable geMain = new CharRegexTable(2, geMainTransitions, geMainAccepts); 81 | RuleData ge = new RuleData(geMain, null, 15, false); 82 | initfnMainAccepts(); 83 | initfnMainTransitions(); 84 | CharRegexTable fnMain = new CharRegexTable(1, fnMainTransitions, fnMainAccepts); 85 | RuleData fn = new RuleData(fnMain, null, 16, false); 86 | initif_MainAccepts(); 87 | initif_MainTransitions(); 88 | CharRegexTable if_Main = new CharRegexTable(1, if_MainTransitions, if_MainAccepts); 89 | RuleData if_ = new RuleData(if_Main, null, 17, false); 90 | initwhile_MainAccepts(); 91 | initwhile_MainTransitions(); 92 | CharRegexTable while_Main = new CharRegexTable(1, while_MainTransitions, while_MainAccepts); 93 | RuleData while_ = new RuleData(while_Main, null, 18, false); 94 | inittextMainAccepts(); 95 | inittextMainTransitions(); 96 | CharRegexTable textMain = new CharRegexTable(1, textMainTransitions, textMainAccepts); 97 | RuleData text = new RuleData(textMain, null, 19, false); 98 | initintegerMainAccepts(); 99 | initintegerMainTransitions(); 100 | CharRegexTable integerMain = new CharRegexTable(1, integerMainTransitions, integerMainAccepts); 101 | RuleData integer = new RuleData(integerMain, null, 20, false); 102 | initidMainAccepts(); 103 | initidMainTransitions(); 104 | CharRegexTable idMain = new CharRegexTable(1, idMainTransitions, idMainAccepts); 105 | RuleData id = new RuleData(idMain, null, 21, false); 106 | initspaceMainAccepts(); 107 | initspaceMainTransitions(); 108 | CharRegexTable spaceMain = new CharRegexTable(1, spaceMainTransitions, spaceMainAccepts); 109 | RuleData space = new RuleData(spaceMain, null, 22, false); 110 | initcommentMainAccepts(); 111 | initcommentMainTransitions(); 112 | CharRegexTable commentMain = new CharRegexTable(1, commentMainTransitions, commentMainAccepts); 113 | RuleData comment = new RuleData(commentMain, null, 23, false); 114 | 115 | EnumMap datas = new EnumMap(RuleEnum.class); 116 | datas.put(RuleEnum.assign, assign); 117 | datas.put(RuleEnum.colon, colon); 118 | datas.put(RuleEnum.eol, eol); 119 | datas.put(RuleEnum.lpar, lpar); 120 | datas.put(RuleEnum.rpar, rpar); 121 | datas.put(RuleEnum.add, add); 122 | datas.put(RuleEnum.sub, sub); 123 | datas.put(RuleEnum.mul, mul); 124 | datas.put(RuleEnum.div, div); 125 | datas.put(RuleEnum.rem, rem); 126 | datas.put(RuleEnum.eq, eq); 127 | datas.put(RuleEnum.ne, ne); 128 | datas.put(RuleEnum.lt, lt); 129 | datas.put(RuleEnum.le, le); 130 | datas.put(RuleEnum.gt, gt); 131 | datas.put(RuleEnum.ge, ge); 132 | datas.put(RuleEnum.fn, fn); 133 | datas.put(RuleEnum.if_, if_); 134 | datas.put(RuleEnum.while_, while_); 135 | datas.put(RuleEnum.text, text); 136 | datas.put(RuleEnum.integer, integer); 137 | datas.put(RuleEnum.id, id); 138 | datas.put(RuleEnum.space, space); 139 | datas.put(RuleEnum.comment, comment); 140 | table = new LexerTable(datas); 141 | } 142 | 143 | 144 | private boolean[] assignMainAccepts; 145 | private void initassignMainAccepts() { 146 | assignMainAccepts = new boolean[] {true,false}; 147 | } 148 | 149 | private int[][] assignMainTransitions; 150 | private void initassignMainTransitions() { 151 | assignMainTransitions = new int[][] {{0,-1},{0,-1,61,0,62,-1}}; 152 | } 153 | 154 | private boolean[] colonMainAccepts; 155 | private void initcolonMainAccepts() { 156 | colonMainAccepts = new boolean[] {true,false}; 157 | } 158 | 159 | private int[][] colonMainTransitions; 160 | private void initcolonMainTransitions() { 161 | colonMainTransitions = new int[][] {{0,-1},{0,-1,58,0,59,-1}}; 162 | } 163 | 164 | private boolean[] eolMainAccepts; 165 | private void initeolMainAccepts() { 166 | eolMainAccepts = new boolean[] {true,false}; 167 | } 168 | 169 | private int[][] eolMainTransitions; 170 | private void initeolMainTransitions() { 171 | eolMainTransitions = new int[][] {{0,-1},{0,-1,10,0,11,-1}}; 172 | } 173 | 174 | private boolean[] lparMainAccepts; 175 | private void initlparMainAccepts() { 176 | lparMainAccepts = new boolean[] {true,false}; 177 | } 178 | 179 | private int[][] lparMainTransitions; 180 | private void initlparMainTransitions() { 181 | lparMainTransitions = new int[][] {{0,-1},{0,-1,40,0,41,-1}}; 182 | } 183 | 184 | private boolean[] rparMainAccepts; 185 | private void initrparMainAccepts() { 186 | rparMainAccepts = new boolean[] {true,false}; 187 | } 188 | 189 | private int[][] rparMainTransitions; 190 | private void initrparMainTransitions() { 191 | rparMainTransitions = new int[][] {{0,-1},{0,-1,41,0,42,-1}}; 192 | } 193 | 194 | private boolean[] addMainAccepts; 195 | private void initaddMainAccepts() { 196 | addMainAccepts = new boolean[] {true,false}; 197 | } 198 | 199 | private int[][] addMainTransitions; 200 | private void initaddMainTransitions() { 201 | addMainTransitions = new int[][] {{0,-1},{0,-1,43,0,44,-1}}; 202 | } 203 | 204 | private boolean[] subMainAccepts; 205 | private void initsubMainAccepts() { 206 | subMainAccepts = new boolean[] {true,false}; 207 | } 208 | 209 | private int[][] subMainTransitions; 210 | private void initsubMainTransitions() { 211 | subMainTransitions = new int[][] {{0,-1},{0,-1,45,0,46,-1}}; 212 | } 213 | 214 | private boolean[] mulMainAccepts; 215 | private void initmulMainAccepts() { 216 | mulMainAccepts = new boolean[] {true,false}; 217 | } 218 | 219 | private int[][] mulMainTransitions; 220 | private void initmulMainTransitions() { 221 | mulMainTransitions = new int[][] {{0,-1},{0,-1,42,0,43,-1}}; 222 | } 223 | 224 | private boolean[] divMainAccepts; 225 | private void initdivMainAccepts() { 226 | divMainAccepts = new boolean[] {true,false}; 227 | } 228 | 229 | private int[][] divMainTransitions; 230 | private void initdivMainTransitions() { 231 | divMainTransitions = new int[][] {{0,-1},{0,-1,47,0,48,-1}}; 232 | } 233 | 234 | private boolean[] remMainAccepts; 235 | private void initremMainAccepts() { 236 | remMainAccepts = new boolean[] {true,false}; 237 | } 238 | 239 | private int[][] remMainTransitions; 240 | private void initremMainTransitions() { 241 | remMainTransitions = new int[][] {{0,-1},{0,-1,37,0,38,-1}}; 242 | } 243 | 244 | private boolean[] eqMainAccepts; 245 | private void initeqMainAccepts() { 246 | eqMainAccepts = new boolean[] {true,false,false}; 247 | } 248 | 249 | private int[][] eqMainTransitions; 250 | private void initeqMainTransitions() { 251 | eqMainTransitions = new int[][] {{0,-1},{0,-1,61,2,62,-1},{0,-1,61,0,62,-1}}; 252 | } 253 | 254 | private boolean[] neMainAccepts; 255 | private void initneMainAccepts() { 256 | neMainAccepts = new boolean[] {true,false,false}; 257 | } 258 | 259 | private int[][] neMainTransitions; 260 | private void initneMainTransitions() { 261 | neMainTransitions = new int[][] {{0,-1},{0,-1,33,2,34,-1},{0,-1,61,0,62,-1}}; 262 | } 263 | 264 | private boolean[] ltMainAccepts; 265 | private void initltMainAccepts() { 266 | ltMainAccepts = new boolean[] {true,false}; 267 | } 268 | 269 | private int[][] ltMainTransitions; 270 | private void initltMainTransitions() { 271 | ltMainTransitions = new int[][] {{0,-1},{0,-1,60,0,61,-1}}; 272 | } 273 | 274 | private boolean[] leMainAccepts; 275 | private void initleMainAccepts() { 276 | leMainAccepts = new boolean[] {true,false,false}; 277 | } 278 | 279 | private int[][] leMainTransitions; 280 | private void initleMainTransitions() { 281 | leMainTransitions = new int[][] {{0,-1},{0,-1,61,0,62,-1},{0,-1,60,1,61,-1}}; 282 | } 283 | 284 | private boolean[] gtMainAccepts; 285 | private void initgtMainAccepts() { 286 | gtMainAccepts = new boolean[] {true,false}; 287 | } 288 | 289 | private int[][] gtMainTransitions; 290 | private void initgtMainTransitions() { 291 | gtMainTransitions = new int[][] {{0,-1},{0,-1,62,0,63,-1}}; 292 | } 293 | 294 | private boolean[] geMainAccepts; 295 | private void initgeMainAccepts() { 296 | geMainAccepts = new boolean[] {true,false,false}; 297 | } 298 | 299 | private int[][] geMainTransitions; 300 | private void initgeMainTransitions() { 301 | geMainTransitions = new int[][] {{0,-1},{0,-1,61,0,62,-1},{0,-1,62,1,63,-1}}; 302 | } 303 | 304 | private boolean[] fnMainAccepts; 305 | private void initfnMainAccepts() { 306 | fnMainAccepts = new boolean[] {true,false,false}; 307 | } 308 | 309 | private int[][] fnMainTransitions; 310 | private void initfnMainTransitions() { 311 | fnMainTransitions = new int[][] {{0,-1},{0,-1,102,2,103,-1},{0,-1,110,0,111,-1}}; 312 | } 313 | 314 | private boolean[] if_MainAccepts; 315 | private void initif_MainAccepts() { 316 | if_MainAccepts = new boolean[] {true,false,false}; 317 | } 318 | 319 | private int[][] if_MainTransitions; 320 | private void initif_MainTransitions() { 321 | if_MainTransitions = new int[][] {{0,-1},{0,-1,105,2,106,-1},{0,-1,102,0,103,-1}}; 322 | } 323 | 324 | private boolean[] while_MainAccepts; 325 | private void initwhile_MainAccepts() { 326 | while_MainAccepts = new boolean[] {true,false,false,false,false,false}; 327 | } 328 | 329 | private int[][] while_MainTransitions; 330 | private void initwhile_MainTransitions() { 331 | while_MainTransitions = new int[][] {{0,-1},{0,-1,119,5,120,-1},{0,-1,105,4,106,-1},{0,-1,101,0,102,-1},{0,-1,108,3,109,-1},{0,-1,104,2,105,-1}}; 332 | } 333 | 334 | private boolean[] textMainAccepts; 335 | private void inittextMainAccepts() { 336 | textMainAccepts = new boolean[] {true,false,false}; 337 | } 338 | 339 | private int[][] textMainTransitions; 340 | private void inittextMainTransitions() { 341 | textMainTransitions = new int[][] {{0,-1},{0,-1,39,2,40,-1},{0,2,39,0,40,2}}; 342 | } 343 | 344 | private boolean[] integerMainAccepts; 345 | private void initintegerMainAccepts() { 346 | integerMainAccepts = new boolean[] {true,false}; 347 | } 348 | 349 | private int[][] integerMainTransitions; 350 | private void initintegerMainTransitions() { 351 | integerMainTransitions = new int[][] {{0,-1,48,0,58,-1},{0,-1,48,0,58,-1}}; 352 | } 353 | 354 | private boolean[] idMainAccepts; 355 | private void initidMainAccepts() { 356 | idMainAccepts = new boolean[] {true,false}; 357 | } 358 | 359 | private int[][] idMainTransitions; 360 | private void initidMainTransitions() { 361 | idMainTransitions = new int[][] {{0,0,9,-1,11,0,13,-1,14,0,32,-1,33,0,40,-1,42,0,44,-1,45,0,58,-1,60,0,61,-1,62,0},{0,0,9,-1,11,0,13,-1,14,0,32,-1,33,0,40,-1,42,0,44,-1,45,0,58,-1,60,0,61,-1,62,0}}; 362 | } 363 | 364 | private boolean[] spaceMainAccepts; 365 | private void initspaceMainAccepts() { 366 | spaceMainAccepts = new boolean[] {true,false}; 367 | } 368 | 369 | private int[][] spaceMainTransitions; 370 | private void initspaceMainTransitions() { 371 | spaceMainTransitions = new int[][] {{0,-1,9,0,11,-1,13,0,14,-1,32,0,33,-1,44,0,45,-1,59,0,60,-1},{0,-1,9,0,11,-1,13,0,14,-1,32,0,33,-1,44,0,45,-1,59,0,60,-1}}; 372 | } 373 | 374 | private boolean[] commentMainAccepts; 375 | private void initcommentMainAccepts() { 376 | commentMainAccepts = new boolean[] {true,false,false,false}; 377 | } 378 | 379 | private int[][] commentMainTransitions; 380 | private void initcommentMainTransitions() { 381 | commentMainTransitions = new int[][] {{0,-1},{0,-1,35,3,36,-1},{0,-1,10,0,11,-1},{0,3,10,0,11,3,13,2,14,3}}; 382 | } 383 | 384 | private final LexerTable table; 385 | } 386 | -------------------------------------------------------------------------------- /src/com/github/forax/vmboiler/CodeGen.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler; 2 | 3 | import static com.github.forax.vmboiler.Type.VM_BOOLEAN; 4 | import static com.github.forax.vmboiler.Type.VM_BYTE; 5 | import static com.github.forax.vmboiler.Type.VM_CHAR; 6 | import static com.github.forax.vmboiler.Type.VM_DOUBLE; 7 | import static com.github.forax.vmboiler.Type.VM_FLOAT; 8 | import static com.github.forax.vmboiler.Type.VM_INT; 9 | import static com.github.forax.vmboiler.Type.VM_LONG; 10 | import static com.github.forax.vmboiler.Type.VM_OBJECT; 11 | import static com.github.forax.vmboiler.Type.VM_SHORT; 12 | import static com.github.forax.vmboiler.Type.VM_VOID; 13 | import static com.github.forax.vmboiler.Value.loadOpcode; 14 | import static com.github.forax.vmboiler.Value.returnOpcode; 15 | import static com.github.forax.vmboiler.Value.size; 16 | import static org.objectweb.asm.Opcodes.*; 17 | 18 | import java.util.function.Function; 19 | 20 | import org.objectweb.asm.Handle; 21 | import org.objectweb.asm.Label; 22 | import org.objectweb.asm.MethodVisitor; 23 | 24 | import com.github.forax.vmboiler.rt.OptimisticError; 25 | import com.github.forax.vmboiler.rt.RT; 26 | 27 | /** 28 | * Wrapper on top of the ASM MethodVisitor allowing to generate code that is optimistically typed. 29 | * 30 | *

Type 31 | *

A {@link Type} is either a classical VM type (int, double, etc) or a mixed type, 32 | * i.e. a type composed by an object type and a primitive type. A mixed type allow to optimistically 33 | * use the primitive type and fallback to the object type if the value can not be stored in the 34 | * primitive type. 35 | * 36 | *

Values 37 | *

All {@link Value}s are represented either by a {@link Constant} (a side 38 | * effect free constant storable in the class constant pool) or by a variable {@link Var} 39 | * that act as a register. {@link Var Variable} unlike {@link Constant} are 40 | * {@link #createVar(Type, Function) created} on the current {@link CodeGen} 41 | * and should never be used with another {@link CodeGen}. 42 | * 43 | *

Operations 44 | *

There are only 9 operations (instructions) to define the semantics of your language, 45 | *

{@link #call(Handle, Object[], Object, Object, Object[], Var, String, Value...)} emit an invokedynamic 46 | * with the some values as arguments and an variable as register for the return type. 47 | * The first two arguments are the bootstrap method and the bootstrap arguments, 48 | * the 3rd and the 4th arguments are two method handles, the first one will be called 49 | * if at least one argument doesn't match its declared type and the second will be called 50 | * if the return type doesn't match the result type. 51 | *

{@link #move(Var, Value)} that copy the value into a variable. 52 | *

{@link #ret(Value)} that returns from the current function with the value. 53 | *

{@link #throwIt(Value)} that throw a value and terminate the function execution. 54 | *

{@link #label(Label)} that set a label that can be use as a target of a jump. 55 | *

{@link #jump(Label)} that jump unconditionally to the specified label. 56 | *

{@link #jumpIfTrue(Value, Label)} that jump if the value is true. 57 | *

{@link #jumpIfFalse(Value, Label)} that jump if the value is false. 58 | *

{@link #lineNumber(int)} that indicate a start of a new line in the source code. 59 | * 60 | *

End 61 | *

The method {@link #end()} must be called after all instructions are generated. 62 | * By default CodeGen delay the generation of bytecodes that handles deoptimization at the end of the method. 63 | * If you do not want this behavior, you can call {@link #end()} after any operations. 64 | */ 65 | public final class CodeGen { 66 | private final MethodVisitor mv; 67 | private final Type returnType; 68 | private int slotCount; 69 | private Runnable sideExit = () -> { /* empty */ }; 70 | 71 | /** 72 | * Create a new CodeGen to generate code of a method. 73 | * @param mv ASM method visitor. 74 | * @param returnType the return type of a method. 75 | */ 76 | public CodeGen(MethodVisitor mv, Type returnType) { 77 | this.mv = mv; 78 | this.returnType = returnType; 79 | } 80 | 81 | /** 82 | * Returns the underlying ASM MethodVisitor. 83 | * This method should be used rarely because the modification 84 | * of method visitor state will not be tracked by the current CodeGen. 85 | * @return the underlying ASM MethodVisitor. 86 | */ 87 | public MethodVisitor methodVisitor() { 88 | return mv; 89 | } 90 | 91 | /** 92 | * Returns type of the current method. 93 | * @return type of the current method. 94 | */ 95 | public Type returnType() { 96 | return returnType; 97 | } 98 | 99 | /** 100 | * Create a variable attached to the current CodeGen. 101 | * This is a convenient method for 102 | *

103 |    *  {@code createVar(type, Var::new)}
104 |    * 
105 | * 106 | * @param type the type of the variable. 107 | * @return a newly created variable 108 | * 109 | * @see #createVar(Type, Function) 110 | */ 111 | public Var createVar(Type type) { 112 | return createVar(type, Var::new); 113 | } 114 | 115 | /** 116 | * Create a variable attached to the current CodeGen. 117 | * This method first call the factory to create a variable and 118 | * then assign a slot to it. 119 | * 120 | * @param type type of the variable. 121 | * @param varFactory a factory which create a variable from a type. 122 | * @return a newly created variable. 123 | */ 124 | public V createVar(T type, Function varFactory) { 125 | V var = varFactory.apply(type); 126 | var.injectSlot(slotCount); 127 | slotCount += size(type.vmType()) + (type.isMixed()? 1: 0); 128 | return var; 129 | } 130 | 131 | /** 132 | * Copy the content of the value into the variable. 133 | * The type of the value must be either the same as the type of the variable 134 | * or if the type of the variable is mixed, the same as the primitive value of 135 | * the type of the variable. 136 | * 137 | * @param var the variable that will store the value. 138 | * @param value the value. 139 | */ 140 | public void move(Var var, Value value) { 141 | MethodVisitor mv = this.mv; 142 | Type varType = var.type(); 143 | Type valueType = value.type(); 144 | if (varType == valueType) { 145 | value.loadAll(mv); 146 | var.storeAll(mv); 147 | return; 148 | } 149 | if (!varType.isMixed() || valueType.isMixed() || varType.vmType() != valueType.vmType()) { 150 | throw new IllegalArgumentException("invalid convertion " + varType + " " + valueType); 151 | } 152 | value.loadPrimitive(mv); 153 | loadNone(mv); 154 | var.storeAll(mv); 155 | } 156 | 157 | /** 158 | * Call a 'virtual method' using invokedynamic. 159 | * 160 | * @param bsm the bootstrap method used to resolved the call. 161 | * @param bsmCsts the bootstrap constant arguments passed to the bootstrap method. 162 | * @param deoptArgs a method handle as a String or a {@link Handle} 163 | * that will be called if at least one argument doesn't match its declared type. 164 | * @param deoptRet a method handle as a String or a {@link Handle} 165 | * that will be called if return value doesn't match its declared type 166 | * @param deoptCsts constants arguments sent as arguments of deopt methods 167 | * @param result the variable that will contains the result value 168 | * (this variable can have a {@link Type#vmType()} equals to {@link Type#VM_VOID}). 169 | * @param name the name of the 'virtual method'. 170 | * @param values the arguments of the call. 171 | */ 172 | public void call(Handle bsm, Object[] bsmCsts, 173 | Object deoptArgs, Object deoptRet, Object[] deoptCsts, 174 | Var result, String name, Value... values) { 175 | MethodVisitor mv = this.mv; 176 | Label label = null; 177 | for(Value v: values) { 178 | if (v.type().isMixed()) { 179 | if (label == null) { 180 | label = new Label(); 181 | } 182 | loadNone(mv); 183 | mv.visitVarInsn(ALOAD, ((Var)v).slot()); 184 | mv.visitJumpInsn(IF_ACMPNE, label); 185 | } 186 | } 187 | 188 | StringBuilder desc = new StringBuilder().append('('); 189 | for(Value v: values) { 190 | v.loadPrimitive(mv); 191 | desc.append(v.type().vmType()); 192 | } 193 | desc.append(')').append(result.type().vmType()); 194 | 195 | Label start = new Label(); 196 | Label end = new Label(); 197 | Label handler = new Label(); 198 | Label sideExitBackLabel = new Label(); 199 | boolean mixed = result.type().isMixed(); 200 | if (mixed) { 201 | mv.visitTryCatchBlock(start, end, handler, OPTIMISTIC_ERROR); 202 | mv.visitLabel(start); 203 | } 204 | mv.visitInvokeDynamicInsn(name, desc.toString(), bsm, bsmCsts); 205 | if (mixed) { 206 | mv.visitLabel(end); 207 | result.storePrimitive(mv); 208 | loadNone(mv); 209 | mv.visitVarInsn(ASTORE, result.slot()); 210 | Runnable previousSideExit = sideExit; 211 | sideExit = () -> { 212 | previousSideExit.run(); 213 | mv.visitLabel(handler); 214 | mv.visitInvokeDynamicInsn(name, "(L" + OPTIMISTIC_ERROR + ";)Ljava/lang/Object;", 215 | BSM_OPTIMISTIC_FAILURE, concat(deoptRet, deoptCsts)); 216 | mv.visitVarInsn(ASTORE, result.slot()); 217 | loadZero(mv, result.type()); 218 | result.storePrimitive(mv); 219 | mv.visitJumpInsn(GOTO, sideExitBackLabel); 220 | }; 221 | } else { 222 | result.storePrimitive(mv); 223 | } 224 | 225 | mv.visitLabel(sideExitBackLabel); 226 | 227 | if (label != null) { 228 | callDeopt(label, sideExitBackLabel, handler, bsm, bsmCsts, deoptArgs, deoptCsts, result, name, values); 229 | } 230 | } 231 | 232 | private void callDeopt(Label sideExitStart, Label sideExitBackLabel, Label handler, 233 | Handle bsm, Object[] bsmCsts, Object deoptArgs, Object[] deoptCsts, 234 | Var result, String name, Value[] values) { 235 | MethodVisitor mv = this.mv; 236 | Runnable previousSideExit = sideExit; 237 | sideExit = () -> { 238 | previousSideExit.run(); 239 | mv.visitLabel(sideExitStart); 240 | StringBuilder desc2 = new StringBuilder().append('('); 241 | StringBuilder mixedDesc = new StringBuilder(); 242 | for(Value v: values) { 243 | v.loadAll(mv); 244 | Type type = v.type(); 245 | desc2.append(type.vmType()); 246 | if (type.isMixed()) { 247 | desc2.append("Ljava/lang/Object;"); 248 | mixedDesc.append('M'); 249 | } else { 250 | mixedDesc.append('.'); 251 | } 252 | } 253 | desc2.append(')').append(result.type().vmType()); 254 | Label start = new Label(); 255 | Label end = new Label(); 256 | boolean mixed = result.type().isMixed(); 257 | if (mixed) { 258 | mv.visitTryCatchBlock(start, end, handler, OPTIMISTIC_ERROR); 259 | mv.visitLabel(start); 260 | } 261 | mv.visitInvokeDynamicInsn(name, desc2.toString(), BSM, 262 | concat(mixedDesc.toString(), bsm, bsmCsts.length, deoptArgs, bsmCsts, deoptCsts)); 263 | if (mixed) { 264 | mv.visitLabel(end); 265 | result.storePrimitive(mv); 266 | loadNone(mv); 267 | mv.visitVarInsn(ASTORE, result.slot()); 268 | } else { 269 | result.storePrimitive(mv); 270 | } 271 | mv.visitJumpInsn(GOTO, sideExitBackLabel); 272 | }; 273 | } 274 | 275 | private static Object[] concat(Object o, Object[] array) { 276 | int length = array.length; 277 | Object[] newArray = new Object[length + 1]; 278 | newArray[0] = o; 279 | System.arraycopy(array, 0, newArray, 1, length); 280 | return newArray; 281 | } 282 | 283 | private static Object[] concat(Object o1, Object o2, Object o3, Object o4, Object[] array1, Object[] array2) { 284 | Object[] newArray = new Object[array1.length + array2.length + 4]; 285 | newArray[0] = o1; 286 | newArray[1] = o2; 287 | newArray[2] = o3; 288 | newArray[3] = o4; 289 | System.arraycopy(array1, 0, newArray, 4, array1.length); 290 | System.arraycopy(array2, 0, newArray, 4 + array1.length, array2.length); 291 | return newArray; 292 | } 293 | 294 | 295 | /** 296 | * Return the value as return value of the current method. 297 | * The type of the value must be either the same as the {@link #returnType()} 298 | * or if the return type is mixed, the same as the primitive value of the return type. 299 | * @param value the value used as return value. 300 | */ 301 | public void ret(Value value) { 302 | MethodVisitor mv = this.mv; 303 | Type valueType = value.type(); 304 | if (returnType != valueType && (!returnType.isMixed() || valueType.isMixed() || returnType.vmType() != valueType.vmType())) { 305 | throw new IllegalArgumentException("invalid convertion " + returnType + " " + valueType); 306 | } 307 | 308 | if (!valueType.isMixed()) { 309 | value.loadPrimitive(mv); 310 | mv.visitInsn(returnOpcode(valueType.vmType())); 311 | return; 312 | } 313 | Var var = (Var)value; 314 | int slot = var.slot(); 315 | mv.visitVarInsn(ALOAD, slot); 316 | CodeGen.loadNone(mv); 317 | Label endLabel = new Label(); 318 | mv.visitJumpInsn(IF_ACMPNE, endLabel); 319 | mv.visitVarInsn(loadOpcode(valueType.vmType()), slot + 1); 320 | mv.visitInsn(returnOpcode(valueType.vmType())); 321 | mv.visitLabel(endLabel); 322 | mv.visitVarInsn(ALOAD, slot); 323 | CodeGen.newOptimisticError(mv); 324 | mv.visitInsn(ATHROW); 325 | } 326 | 327 | 328 | /** 329 | * Set a label that can be used as target of a jump. 330 | * @param label an ASM label. 331 | * 332 | * @see #jump(Label) 333 | * @see #jumpIfTrue(Value, Label) 334 | * @see #jumpIfFalse(Value, Label) 335 | */ 336 | public void label(Label label) { 337 | mv.visitLabel(label); 338 | } 339 | 340 | /** 341 | * Unconditionally jump to the label pass as argument. 342 | * @param label an ASM Label 343 | */ 344 | public void jump(Label label) { 345 | mv.visitJumpInsn(GOTO, label); 346 | } 347 | 348 | /** 349 | * Jump to the label if the value is true. 350 | * @param value a value. 351 | * @param label an ASM label. 352 | */ 353 | public void jumpIfTrue(Value value, Label label) { 354 | String vmType = value.type().vmType(); 355 | if (vmType != VM_BOOLEAN && vmType != VM_INT) { 356 | throw new IllegalArgumentException("value.type must be an int or a boolean"); 357 | } 358 | MethodVisitor mv = this.mv; 359 | value.loadPrimitive(mv); 360 | mv.visitJumpInsn(IFNE, label); 361 | } 362 | 363 | /** 364 | * Jump to the label if the value is false. 365 | * @param value a value. 366 | * @param label an ASM label. 367 | */ 368 | public void jumpIfFalse(Value value, Label label) { 369 | String vmType = value.type().vmType(); 370 | if (vmType != VM_BOOLEAN && vmType != VM_INT) { 371 | throw new IllegalArgumentException("value.type must be an int or a boolean"); 372 | } 373 | MethodVisitor mv = this.mv; 374 | value.loadPrimitive(mv); 375 | mv.visitJumpInsn(IFEQ, label); 376 | } 377 | 378 | //public void jumpIfNull(Value value, Label label); 379 | //public void jumpIfNonNull(Value value, Label label); 380 | 381 | /** 382 | * Throw a value. 383 | * @param value the value to throw. 384 | */ 385 | public void throwIt(Value value) { 386 | if (value.type().vmType().charAt(0) == 'L') { 387 | throw new IllegalArgumentException("value.type must be a subtype of object"); 388 | } 389 | value.loadPrimitive(mv); 390 | mv.visitInsn(ATHROW); 391 | } 392 | 393 | /** 394 | * Generate a line number for the next instruction. 395 | * @param line a line number. 396 | */ 397 | public void lineNumber(int line) { 398 | Label label = new Label(); 399 | mv.visitLabel(label); 400 | mv.visitLineNumber(line, label); 401 | } 402 | 403 | /** 404 | * End by generating all the code that handle deoptimization paths. 405 | */ 406 | public void end() { 407 | sideExit.run(); 408 | sideExit = () -> { /* empty */ }; 409 | } 410 | 411 | private static final String RT = RT.class.getName().replace('.', '/'); 412 | private static final String OPTIMISTIC_ERROR = OptimisticError.class.getName().replace('.', '/'); 413 | private static final Handle BSM = new Handle(H_INVOKESTATIC, RT, "bsm", 414 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"); 415 | private static final Handle BSM_OPTIMISTIC_FAILURE = new Handle(H_INVOKESTATIC, RT, "bsm_optimistic_failure", 416 | "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;"); 417 | 418 | private static void newOptimisticError(MethodVisitor mv) { 419 | mv.visitMethodInsn(INVOKESTATIC, OPTIMISTIC_ERROR, "newOptimisticError", 420 | "(Ljava/lang/Object;)L" + OPTIMISTIC_ERROR + ';', false); 421 | } 422 | 423 | private static void loadNone(MethodVisitor mv) { 424 | mv.visitFieldInsn(GETSTATIC, RT, "NONE", "Ljava/lang/Object;"); 425 | } 426 | 427 | private static void loadZero(MethodVisitor mv, Type type) { 428 | switch(type.vmType()) { 429 | case VM_VOID: 430 | throw new IllegalArgumentException("type void not allowed here"); 431 | case VM_BOOLEAN: 432 | case VM_BYTE: 433 | case VM_CHAR: 434 | case VM_SHORT: 435 | case VM_INT: 436 | mv.visitInsn(ICONST_0); 437 | return; 438 | case VM_LONG: 439 | mv.visitInsn(LCONST_0); 440 | return; 441 | case VM_FLOAT: 442 | mv.visitInsn(FCONST_0); 443 | return; 444 | case VM_DOUBLE: 445 | mv.visitInsn(DCONST_0); 446 | return; 447 | default: //OBJECT, ARRAY 448 | mv.visitInsn(ACONST_NULL); 449 | } 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /script/gen-src/com/github/forax/vmboiler/sample/script/tools/AnalyzerProcessor.java: -------------------------------------------------------------------------------- 1 | package com.github.forax.vmboiler.sample.script.tools; 2 | 3 | import com.github.forax.vmboiler.sample.script.Expr; 4 | import com.github.forax.vmboiler.sample.script.Fn; 5 | import java.util.List; 6 | 7 | import com.github.forax.vmboiler.sample.script.lexer.RuleEnum; 8 | import com.github.forax.vmboiler.sample.script.parser.TerminalEnum; 9 | import com.github.forax.vmboiler.sample.script.parser.NonTerminalEnum; 10 | import com.github.forax.vmboiler.sample.script.parser.ProductionEnum; 11 | import com.github.forax.vmboiler.sample.script.tools.TerminalEvaluator; 12 | import com.github.forax.vmboiler.sample.script.tools.GrammarEvaluator; 13 | 14 | import fr.umlv.tatoo.runtime.buffer.LexerBuffer; 15 | import fr.umlv.tatoo.runtime.tools.AnalyzerListener; 16 | import fr.umlv.tatoo.runtime.tools.DataViewer; 17 | import fr.umlv.tatoo.runtime.tools.SemanticStack; 18 | 19 | /** This class is called by the parser when 20 | *
    21 | *
  1. a terminal is shifted
  2. 22 | *
  3. a non terminal is reduced
  4. 23 | *
  5. a non terminal is accepted
  6. 24 | *
25 | * In that case, depending on the information of the .xtls, terminal and non-terminal 26 | * values are pushed/pop from a semantic stack. 27 | * 28 | * Furthermore, in case of error recovery, values of the stack can be pop out 29 | * depending if the last recognized element is a terminal or a non-terminal. 30 | * 31 | * This class is generated - please do not edit it 32 | */ 33 | public class AnalyzerProcessor 34 | implements AnalyzerListener { 35 | 36 | private final GrammarEvaluator grammarEvaluator; 37 | private final TerminalEvaluator terminalEvaluator; 38 | private final DataViewer dataViewer; 39 | private final SemanticStack stack; 40 | 41 | protected AnalyzerProcessor(TerminalEvaluator terminalEvaluator, GrammarEvaluator grammarEvaluator, DataViewer dataViewer, SemanticStack stack) { 42 | this.terminalEvaluator=terminalEvaluator; 43 | this.grammarEvaluator=grammarEvaluator; 44 | this.dataViewer=dataViewer; 45 | this.stack=stack; 46 | } 47 | 48 | /** Creates a tools listener that redirect accept/shift/reduce and comment to the terminal Evaluator 49 | and the grammar evaluator.. 50 | This constructor allows to share the same stack between more 51 | than one parser processor. 52 | @param terminalEvaluator the terminal evaluator. 53 | @param grammarEvaluator the grammar evaluator. 54 | @param stack the stack used by the processor 55 | */ 56 | public static AnalyzerProcessor 57 | createAnalyzerProcessor(TerminalEvaluator terminalEvaluator, GrammarEvaluator grammarEvaluator, DataViewer dataViewer, SemanticStack stack) { 58 | 59 | return new AnalyzerProcessor(terminalEvaluator,grammarEvaluator,dataViewer,stack); 60 | } 61 | 62 | public void comment(RuleEnum rule, B buffer) { 63 | D data; 64 | switch(rule) { case comment: 65 | data=dataViewer.view(buffer); 66 | terminalEvaluator.comment(data); 67 | return; 68 | default: 69 | } 70 | throw new AssertionError("unknown rule "+rule); 71 | } 72 | 73 | public void shift(TerminalEnum terminal, RuleEnum rule, B buffer) { 74 | D data; 75 | switch(terminal) { case assign: { 76 | return; 77 | } 78 | case colon: { 79 | return; 80 | } 81 | case eol: { 82 | return; 83 | } 84 | case lpar: { 85 | return; 86 | } 87 | case rpar: { 88 | return; 89 | } 90 | case add: { 91 | return; 92 | } 93 | case sub: { 94 | return; 95 | } 96 | case mul: { 97 | return; 98 | } 99 | case div: { 100 | return; 101 | } 102 | case rem: { 103 | return; 104 | } 105 | case eq: { 106 | return; 107 | } 108 | case ne: { 109 | return; 110 | } 111 | case lt: { 112 | return; 113 | } 114 | case le: { 115 | return; 116 | } 117 | case gt: { 118 | return; 119 | } 120 | case ge: { 121 | return; 122 | } 123 | case fn: { 124 | return; 125 | } 126 | case if_: { 127 | return; 128 | } 129 | case while_: { 130 | return; 131 | } 132 | case text: { 133 | data=dataViewer.view(buffer); 134 | String text=terminalEvaluator.text(data); 135 | stack.push_Object(text); 136 | return; 137 | } 138 | case integer: { 139 | data=dataViewer.view(buffer); 140 | Object integer=terminalEvaluator.integer(data); 141 | stack.push_Object(integer); 142 | return; 143 | } 144 | case id: { 145 | data=dataViewer.view(buffer); 146 | String id=terminalEvaluator.id(data); 147 | stack.push_Object(id); 148 | return; 149 | } 150 | case __eof__: { 151 | return; 152 | } 153 | } 154 | throw new AssertionError("unknown terminal "+terminal); 155 | } 156 | 157 | 158 | @SuppressWarnings("unchecked") 159 | public void reduce(ProductionEnum production) { 160 | switch(production) { case fun_star_0_empty: { // STAR_EMPTY 161 | stack.push_Object(new java.util.ArrayList()); 162 | 163 | } 164 | return; 165 | case fun_star_0_rec: { // STAR_RECURSIVE_LEFT 166 | 167 | Fn fun=(Fn)stack.pop_Object(); 168 | List fun_star_0=(List)stack.pop_Object(); 169 | fun_star_0.add(fun); 170 | stack.push_Object(fun_star_0); 171 | 172 | } 173 | return; 174 | case script: { // not synthetic 175 | List fun_star_0=(List)stack.pop_Object(); 176 | grammarEvaluator.script(fun_star_0); 177 | 178 | } 179 | return; 180 | case id_star_1_empty: { // STAR_EMPTY 181 | stack.push_Object(new java.util.ArrayList()); 182 | 183 | } 184 | return; 185 | case id_star_1_rec: { // STAR_RECURSIVE_LEFT 186 | 187 | String id=(String)stack.pop_Object(); 188 | List id_star_1=(List)stack.pop_Object(); 189 | id_star_1.add(id); 190 | stack.push_Object(id_star_1); 191 | 192 | } 193 | return; 194 | case expr_star_2_empty: { // STAR_EMPTY 195 | stack.push_Object(new java.util.ArrayList()); 196 | 197 | } 198 | return; 199 | case expr_star_2_rec: { // STAR_RECURSIVE_LEFT 200 | 201 | Expr expr=(Expr)stack.pop_Object(); 202 | List expr_star_2=(List)stack.pop_Object(); 203 | expr_star_2.add(expr); 204 | stack.push_Object(expr_star_2); 205 | 206 | } 207 | return; 208 | case fun: { // not synthetic 209 | List expr_star_2=(List)stack.pop_Object(); 210 | List id_star_1=(List)stack.pop_Object(); 211 | String id=(String)stack.pop_Object(); 212 | stack.push_Object(grammarEvaluator.fun(id,id_star_1,expr_star_2)); 213 | 214 | } 215 | return; 216 | case expr_integer: { // not synthetic 217 | Object integer=(Object)stack.pop_Object(); 218 | stack.push_Object(grammarEvaluator.expr_integer(integer)); 219 | 220 | } 221 | return; 222 | case expr_text: { // not synthetic 223 | String text=(String)stack.pop_Object(); 224 | stack.push_Object(grammarEvaluator.expr_text(text)); 225 | 226 | } 227 | return; 228 | case expr_star_3_empty: { // STAR_EMPTY 229 | stack.push_Object(new java.util.ArrayList()); 230 | 231 | } 232 | return; 233 | case expr_star_3_rec: { // STAR_RECURSIVE_LEFT 234 | 235 | Expr expr=(Expr)stack.pop_Object(); 236 | List expr_star_3=(List)stack.pop_Object(); 237 | expr_star_3.add(expr); 238 | stack.push_Object(expr_star_3); 239 | 240 | } 241 | return; 242 | case expr_block: { // not synthetic 243 | List expr_star_3=(List)stack.pop_Object(); 244 | stack.push_Object(grammarEvaluator.expr_block(expr_star_3)); 245 | 246 | } 247 | return; 248 | case expr_var_access: { // not synthetic 249 | String id=(String)stack.pop_Object(); 250 | stack.push_Object(grammarEvaluator.expr_var_access(id)); 251 | 252 | } 253 | return; 254 | case expr_var_assignment: { // not synthetic 255 | Expr expr=(Expr)stack.pop_Object(); 256 | String id=(String)stack.pop_Object(); 257 | stack.push_Object(grammarEvaluator.expr_var_assignment(id,expr)); 258 | 259 | } 260 | return; 261 | case expr_star_4_empty: { // STAR_EMPTY 262 | stack.push_Object(new java.util.ArrayList()); 263 | 264 | } 265 | return; 266 | case expr_star_4_rec: { // STAR_RECURSIVE_LEFT 267 | 268 | Expr expr=(Expr)stack.pop_Object(); 269 | List expr_star_4=(List)stack.pop_Object(); 270 | expr_star_4.add(expr); 271 | stack.push_Object(expr_star_4); 272 | 273 | } 274 | return; 275 | case expr_call: { // not synthetic 276 | List expr_star_4=(List)stack.pop_Object(); 277 | String id=(String)stack.pop_Object(); 278 | stack.push_Object(grammarEvaluator.expr_call(id,expr_star_4)); 279 | 280 | } 281 | return; 282 | case expr_if: { // not synthetic 283 | Expr expr3=(Expr)stack.pop_Object(); 284 | Expr expr2=(Expr)stack.pop_Object(); 285 | Expr expr=(Expr)stack.pop_Object(); 286 | stack.push_Object(grammarEvaluator.expr_if(expr,expr2,expr3)); 287 | 288 | } 289 | return; 290 | case expr_star_5_empty: { // STAR_EMPTY 291 | stack.push_Object(new java.util.ArrayList()); 292 | 293 | } 294 | return; 295 | case expr_star_5_rec: { // STAR_RECURSIVE_LEFT 296 | 297 | Expr expr=(Expr)stack.pop_Object(); 298 | List expr_star_5=(List)stack.pop_Object(); 299 | expr_star_5.add(expr); 300 | stack.push_Object(expr_star_5); 301 | 302 | } 303 | return; 304 | case expr_while: { // not synthetic 305 | List expr_star_5=(List)stack.pop_Object(); 306 | Expr expr=(Expr)stack.pop_Object(); 307 | stack.push_Object(grammarEvaluator.expr_while(expr,expr_star_5)); 308 | 309 | } 310 | return; 311 | case expr_mul: { // not synthetic 312 | Expr expr2=(Expr)stack.pop_Object(); 313 | Expr expr=(Expr)stack.pop_Object(); 314 | stack.push_Object(grammarEvaluator.expr_mul(expr,expr2)); 315 | 316 | } 317 | return; 318 | case expr_div: { // not synthetic 319 | Expr expr2=(Expr)stack.pop_Object(); 320 | Expr expr=(Expr)stack.pop_Object(); 321 | stack.push_Object(grammarEvaluator.expr_div(expr,expr2)); 322 | 323 | } 324 | return; 325 | case expr_rem: { // not synthetic 326 | Expr expr2=(Expr)stack.pop_Object(); 327 | Expr expr=(Expr)stack.pop_Object(); 328 | stack.push_Object(grammarEvaluator.expr_rem(expr,expr2)); 329 | 330 | } 331 | return; 332 | case expr_add: { // not synthetic 333 | Expr expr2=(Expr)stack.pop_Object(); 334 | Expr expr=(Expr)stack.pop_Object(); 335 | stack.push_Object(grammarEvaluator.expr_add(expr,expr2)); 336 | 337 | } 338 | return; 339 | case expr_sub: { // not synthetic 340 | Expr expr2=(Expr)stack.pop_Object(); 341 | Expr expr=(Expr)stack.pop_Object(); 342 | stack.push_Object(grammarEvaluator.expr_sub(expr,expr2)); 343 | 344 | } 345 | return; 346 | case expr_eq: { // not synthetic 347 | Expr expr2=(Expr)stack.pop_Object(); 348 | Expr expr=(Expr)stack.pop_Object(); 349 | stack.push_Object(grammarEvaluator.expr_eq(expr,expr2)); 350 | 351 | } 352 | return; 353 | case expr_ne: { // not synthetic 354 | Expr expr2=(Expr)stack.pop_Object(); 355 | Expr expr=(Expr)stack.pop_Object(); 356 | stack.push_Object(grammarEvaluator.expr_ne(expr,expr2)); 357 | 358 | } 359 | return; 360 | case expr_lt: { // not synthetic 361 | Expr expr2=(Expr)stack.pop_Object(); 362 | Expr expr=(Expr)stack.pop_Object(); 363 | stack.push_Object(grammarEvaluator.expr_lt(expr,expr2)); 364 | 365 | } 366 | return; 367 | case expr_le: { // not synthetic 368 | Expr expr2=(Expr)stack.pop_Object(); 369 | Expr expr=(Expr)stack.pop_Object(); 370 | stack.push_Object(grammarEvaluator.expr_le(expr,expr2)); 371 | 372 | } 373 | return; 374 | case expr_gt: { // not synthetic 375 | Expr expr2=(Expr)stack.pop_Object(); 376 | Expr expr=(Expr)stack.pop_Object(); 377 | stack.push_Object(grammarEvaluator.expr_gt(expr,expr2)); 378 | 379 | } 380 | return; 381 | case expr_ge: { // not synthetic 382 | Expr expr2=(Expr)stack.pop_Object(); 383 | Expr expr=(Expr)stack.pop_Object(); 384 | stack.push_Object(grammarEvaluator.expr_ge(expr,expr2)); 385 | 386 | } 387 | return; 388 | default: 389 | throw new AssertionError("unknown production "+production); 390 | } 391 | } 392 | 393 | public void accept(NonTerminalEnum nonterminal) { 394 | switch(nonterminal) { case script: 395 | grammarEvaluator.acceptScript(); 396 | return; 397 | default: 398 | } 399 | throw new AssertionError("unknown start nonterminal "+nonterminal); 400 | } 401 | 402 | public void popTerminalOnError(TerminalEnum terminal) { 403 | switch(terminal) { case assign: 404 | 405 | return; 406 | case colon: 407 | 408 | return; 409 | case eol: 410 | 411 | return; 412 | case lpar: 413 | 414 | return; 415 | case rpar: 416 | 417 | return; 418 | case add: 419 | 420 | return; 421 | case sub: 422 | 423 | return; 424 | case mul: 425 | 426 | return; 427 | case div: 428 | 429 | return; 430 | case rem: 431 | 432 | return; 433 | case eq: 434 | 435 | return; 436 | case ne: 437 | 438 | return; 439 | case lt: 440 | 441 | return; 442 | case le: 443 | 444 | return; 445 | case gt: 446 | 447 | return; 448 | case ge: 449 | 450 | return; 451 | case fn: 452 | 453 | return; 454 | case if_: 455 | 456 | return; 457 | case while_: 458 | 459 | return; 460 | case text: 461 | stack.pop_Object(); 462 | return; 463 | case integer: 464 | stack.pop_Object(); 465 | return; 466 | case id: 467 | stack.pop_Object(); 468 | return; 469 | case __eof__: 470 | 471 | return; 472 | } 473 | throw new AssertionError("unknown terminal "+terminal); 474 | } 475 | 476 | public void popNonTerminalOnError(NonTerminalEnum nonTerminal) { 477 | switch(nonTerminal) { case script: 478 | 479 | return; 480 | case fun: 481 | stack.pop_Object(); 482 | return; 483 | case expr: 484 | stack.pop_Object(); 485 | return; 486 | case fun_star_0: 487 | stack.pop_Object(); 488 | return; 489 | case id_star_1: 490 | stack.pop_Object(); 491 | return; 492 | case expr_star_2: 493 | stack.pop_Object(); 494 | return; 495 | case expr_star_3: 496 | stack.pop_Object(); 497 | return; 498 | case expr_star_4: 499 | stack.pop_Object(); 500 | return; 501 | case expr_star_5: 502 | stack.pop_Object(); 503 | return; 504 | } 505 | throw new AssertionError("unknown nonterminal "+nonTerminal); 506 | } 507 | } --------------------------------------------------------------------------------