Simon Krajewski 6 年之前
父節點
當前提交
d9535c217b
共有 54 個文件被更改,包括 7985 次插入28 次删除
  1. 2 2
      .travis.yml
  2. 1 1
      Makefile
  3. 1 1
      appveyor.yml
  4. 2 0
      extra/ImportAll.hx
  5. 6 0
      extra/all.hxml
  6. 7 2
      src/compiler/main.ml
  7. 1 0
      src/context/common.ml
  8. 2 0
      src/core/define.ml
  9. 3 3
      src/filters/capturedVars.ml
  10. 1 1
      src/filters/filters.ml
  11. 3006 0
      src/generators/genjvm.ml
  12. 229 0
      src/generators/jvm/jvmAttribute.ml
  13. 70 0
      src/generators/jvm/jvmBuilder.ml
  14. 169 0
      src/generators/jvm/jvmClass.ml
  15. 547 0
      src/generators/jvm/jvmCode.ml
  16. 173 0
      src/generators/jvm/jvmConstantPool.ml
  17. 275 0
      src/generators/jvm/jvmData.ml
  18. 262 0
      src/generators/jvm/jvmDebug.ml
  19. 145 0
      src/generators/jvm/jvmGlobals.ml
  20. 824 0
      src/generators/jvm/jvmMethod.ml
  21. 275 0
      src/generators/jvm/jvmSignature.ml
  22. 37 0
      src/generators/jvm/jvmVerificationTypeInfo.ml
  23. 312 0
      src/generators/jvm/jvmWriter.ml
  24. 1 0
      std/StdTypes.hx
  25. 1 1
      std/haxe/CallStack.hx
  26. 3 3
      std/haxe/format/JsonPrinter.hx
  27. 19 5
      std/java/_std/Array.hx
  28. 1 1
      std/java/_std/String.hx
  29. 10 0
      std/java/_std/StringBuf.hx
  30. 5 2
      std/java/_std/haxe/Int64.hx
  31. 93 0
      std/jvm/DynamicObject.hx
  32. 5 0
      std/jvm/EmptyConstructor.hx
  33. 25 0
      std/jvm/Enum.hx
  34. 38 0
      std/jvm/Exception.hx
  35. 495 0
      std/jvm/Jvm.hx
  36. 27 0
      std/jvm/NativeTools.hx
  37. 16 0
      std/jvm/Object.hx
  38. 109 0
      std/jvm/StringExt.hx
  39. 182 0
      std/jvm/_std/Reflect.hx
  40. 106 0
      std/jvm/_std/Std.hx
  41. 90 0
      std/jvm/_std/String.hx
  42. 329 0
      std/jvm/_std/Type.hx
  43. 8 0
      std/jvm/annotation/ClassReflectionInformation.hx
  44. 8 0
      std/jvm/annotation/EnumReflectionInformation.hx
  45. 8 0
      std/jvm/annotation/EnumValueReflectionInformation.hx
  46. 2 0
      tests/RunCi.hx
  47. 1 0
      tests/benchs/.vscode/settings.json
  48. 2 1
      tests/benchs/src/hxbenchmark/ResultPrinter.hx
  49. 1 0
      tests/runci/TestTarget.hx
  50. 24 0
      tests/runci/targets/Jvm.hx
  51. 16 0
      tests/sys/compile-jvm.hxml
  52. 5 0
      tests/unit/compile-jvm.hxml
  53. 4 4
      tests/unit/src/unit/TestJava.hx
  54. 1 1
      tests/unit/src/unit/issues/Issue3826.hx

+ 2 - 2
.travis.yml

@@ -171,7 +171,7 @@ matrix:
 
 
     - os: linux
     - os: linux
       env:
       env:
-        - TEST=macro,hl,js,php,flash9,as3,java,cs,python,lua
+        - TEST=macro,hl,js,php,flash9,as3,java,jvm,cs,python,lua
         - SAUCE=1
         - SAUCE=1
       addons:
       addons:
         sauce_connect: true
         sauce_connect: true
@@ -202,7 +202,7 @@ matrix:
     - os: osx
     - os: osx
       osx_image: xcode9.4 # to compile faster
       osx_image: xcode9.4 # to compile faster
       env:
       env:
-        - TEST=macro,hl,java,cs,lua,js,php,flash9,python
+        - TEST=macro,hl,java,jvm,cs,lua,js,php,flash9,python
       install: *install_osx
       install: *install_osx
 
 
     - os: osx
     - os: osx

+ 1 - 1
Makefile

@@ -29,7 +29,7 @@ STATICLINK?=0
 # Configuration
 # Configuration
 
 
 # Modules in these directories should only depend on modules that are in directories to the left
 # Modules in these directories should only depend on modules that are in directories to the left
-HAXE_DIRECTORIES=core core/json core/display syntax context context/display codegen codegen/gencommon generators optimization filters macro macro/eval typing compiler
+HAXE_DIRECTORIES=core core/json core/display syntax context context/display codegen codegen/gencommon generators generators/jvm optimization filters macro macro/eval typing compiler
 EXTLIB_LIBS=extlib-leftovers extc neko javalib swflib ttflib ilib objsize pcre ziplib
 EXTLIB_LIBS=extlib-leftovers extc neko javalib swflib ttflib ilib objsize pcre ziplib
 OCAML_LIBS=unix str threads dynlink
 OCAML_LIBS=unix str threads dynlink
 OPAM_LIBS=sedlex xml-light extlib ptmap sha
 OPAM_LIBS=sedlex xml-light extlib ptmap sha

+ 1 - 1
appveyor.yml

@@ -14,7 +14,7 @@ environment:
           secure: ewwkKcjnSKl/Vtrz1SXmI6XKk1ENmJDyzm5YaR2wi03foRhTke29TvymB21rDTSl
           secure: ewwkKcjnSKl/Vtrz1SXmI6XKk1ENmJDyzm5YaR2wi03foRhTke29TvymB21rDTSl
     matrix:
     matrix:
         - ARCH: 64
         - ARCH: 64
-          TEST: "neko,hl,python,cs,java,php"
+          TEST: "neko,hl,python,cs,java,jvm,php"
         - ARCH: 64
         - ARCH: 64
           TEST: "cpp"
           TEST: "cpp"
         - ARCH: 64
         - ARCH: 64

+ 2 - 0
extra/ImportAll.hx

@@ -56,6 +56,8 @@ class ImportAll {
 			if ( !Context.defined("target.threaded") ) return;
 			if ( !Context.defined("target.threaded") ) return;
 		case "java":
 		case "java":
 			if( !Context.defined("java") ) return;
 			if( !Context.defined("java") ) return;
+		case "jvm":
+			if( !Context.defined("jvm") ) return;
 		case "cs":
 		case "cs":
 			if( !Context.defined("cs") ) return;
 			if( !Context.defined("cs") ) return;
 		case "python":
 		case "python":

+ 6 - 0
extra/all.hxml

@@ -34,6 +34,12 @@
 -xml java.xml
 -xml java.xml
 -D xmldoc
 -D xmldoc
 
 
+--next
+-java all_jvm
+-D jvm
+-xml jvm.xml
+-D xmldoc
+
 --next
 --next
 -cs all_cs
 -cs all_cs
 -D unsafe
 -D unsafe

+ 7 - 2
src/compiler/main.ml

@@ -259,7 +259,9 @@ module Initialize = struct
 					old_flush()
 					old_flush()
 				);
 				);
 				Java.before_generate com;
 				Java.before_generate com;
-				add_std "java"; "java"
+				if defined com Define.Jvm then add_std "jvm";
+				add_std "java";
+				"java"
 			| Python ->
 			| Python ->
 				add_std "python";
 				add_std "python";
 				if not (Common.defined com Define.PythonVersion) then
 				if not (Common.defined com Define.PythonVersion) then
@@ -318,7 +320,10 @@ let generate tctx ext xml_out interp swf_header =
 		| Cs ->
 		| Cs ->
 			Gencs.generate,"cs"
 			Gencs.generate,"cs"
 		| Java ->
 		| Java ->
-			Genjava.generate,"java"
+			if Common.defined com Jvm then
+				Genjvm.generate,"java"
+			else
+				Genjava.generate,"java"
 		| Python ->
 		| Python ->
 			Genpy.generate,"python"
 			Genpy.generate,"python"
 		| Hl ->
 		| Hl ->

+ 1 - 0
src/context/common.ml

@@ -364,6 +364,7 @@ let get_config com =
 			pf_pad_nulls = true;
 			pf_pad_nulls = true;
 			pf_overload = true;
 			pf_overload = true;
 			pf_supports_threads = true;
 			pf_supports_threads = true;
+			pf_this_before_super = false;
 		}
 		}
 	| Python ->
 	| Python ->
 		{
 		{

+ 2 - 0
src/core/define.ml

@@ -56,6 +56,7 @@ type strict_defined =
 	| JsUnflatten
 	| JsUnflatten
 	| JsSourceMap
 	| JsSourceMap
 	| JsEnumsAsArrays
 	| JsEnumsAsArrays
+	| Jvm
 	| SourceMap
 	| SourceMap
 	| KeepOldOutput
 	| KeepOldOutput
 	| LoopUnrollMaxCost
 	| LoopUnrollMaxCost
@@ -168,6 +169,7 @@ let infos = function
 	| JsEnumsAsArrays -> "js_enums_as_arrays",("Generate enum representation as array instead of as object",[Platform Js])
 	| JsEnumsAsArrays -> "js_enums_as_arrays",("Generate enum representation as array instead of as object",[Platform Js])
 	| JsUnflatten -> "js_unflatten",("Generate nested objects for packages and types",[Platform Js])
 	| JsUnflatten -> "js_unflatten",("Generate nested objects for packages and types",[Platform Js])
 	| JsSourceMap -> "js_source_map",("Generate JavaScript source map even in non-debug mode",[Platform Js])
 	| JsSourceMap -> "js_source_map",("Generate JavaScript source map even in non-debug mode",[Platform Js])
+	| Jvm -> "jvm",("Generate jvm directly",[Platform Java])
 	| SourceMap -> "source_map",("Generate source map for compiled files (Currently supported for php only)",[Platform Php])
 	| SourceMap -> "source_map",("Generate source map for compiled files (Currently supported for php only)",[Platform Php])
 	| KeepOldOutput -> "keep_old_output",("Keep old source files in the output directory (for C#/Java)",[Platforms [Cs;Java]])
 	| KeepOldOutput -> "keep_old_output",("Keep old source files in the output directory (for C#/Java)",[Platforms [Cs;Java]])
 	| LoopUnrollMaxCost -> "loop_unroll_max_cost",("Maximum cost (number of expressions * iterations) before loop unrolling is canceled (default 250)",[])
 	| LoopUnrollMaxCost -> "loop_unroll_max_cost",("Maximum cost (number of expressions * iterations) before loop unrolling is canceled (default 250)",[])

+ 3 - 3
src/filters/capturedVars.ml

@@ -129,7 +129,7 @@ let captured_vars com e =
 			let rec browse = function
 			let rec browse = function
 				| Block f | Loop f | Function f -> f browse
 				| Block f | Loop f | Function f -> f browse
 				| Use ({ v_extra = Some( _ :: _, _) })
 				| Use ({ v_extra = Some( _ :: _, _) })
-				| Assign ({ v_extra = Some( _ :: _, _) }) when com.platform = Cs || com.platform = Java ->
+				| Assign ({ v_extra = Some( _ :: _, _) }) when com.platform = Cs || (com.platform = Java && not (Common.defined com Define.Jvm)) ->
 					(* Java and C# deal with functions with type parameters in a different way *)
 					(* Java and C# deal with functions with type parameters in a different way *)
 					(* so they do should not be wrapped *)
 					(* so they do should not be wrapped *)
 					()
 					()
@@ -220,7 +220,7 @@ let captured_vars com e =
 					f (collect_vars false);
 					f (collect_vars false);
 					decr depth;
 					decr depth;
 				| Use ({ v_extra = Some( _ :: _, _) })
 				| Use ({ v_extra = Some( _ :: _, _) })
-				| Assign ({ v_extra = Some( _ :: _, _) }) when com.platform = Cs || com.platform = Java ->
+				| Assign ({ v_extra = Some( _ :: _, _) }) when com.platform = Cs || (com.platform = Java && not (Common.defined com Define.Jvm)) ->
 					(* Java/C# use a special handling for functions with type parmaters *)
 					(* Java/C# use a special handling for functions with type parmaters *)
 					()
 					()
 				| Declare v ->
 				| Declare v ->
@@ -257,7 +257,7 @@ let captured_vars com e =
 		| Declare v ->
 		| Declare v ->
 			vars := PMap.add v.v_id !depth !vars;
 			vars := PMap.add v.v_id !depth !vars;
 		| Use ({ v_extra = Some( _ :: _, _) })
 		| Use ({ v_extra = Some( _ :: _, _) })
-		| Assign ({ v_extra = Some( _ :: _, _) }) when com.platform = Cs || com.platform = Java ->
+		| Assign ({ v_extra = Some( _ :: _, _) }) when com.platform = Cs || (com.platform = Java && not (Common.defined com Define.Jvm)) ->
 			()
 			()
 		| Use v ->
 		| Use v ->
 			(try
 			(try

+ 1 - 1
src/filters/filters.ml

@@ -807,7 +807,7 @@ let run com tctx main =
 			filters @ [
 			filters @ [
 				TryCatchWrapper.configure_cs com
 				TryCatchWrapper.configure_cs com
 			]
 			]
-		| Java ->
+		| Java when not (Common.defined com Jvm)->
 			SetHXGen.run_filter com new_types;
 			SetHXGen.run_filter com new_types;
 			filters @ [
 			filters @ [
 				TryCatchWrapper.configure_java com
 				TryCatchWrapper.configure_java com

+ 3006 - 0
src/generators/genjvm.ml

@@ -0,0 +1,3006 @@
+open Globals
+open Ast
+open Common
+open Type
+open Path
+open JvmGlobals
+open MethodAccessFlags
+open FieldAccessFlags
+open JvmData
+open JvmAttribute
+open JvmSignature
+open JvmMethod
+open JvmBuilder
+
+(* hacks *)
+
+let rec pow a b = match b with
+	| 0 -> Int32.one
+	| 1 -> a
+	| _ -> Int32.mul a (pow a (b - 1))
+
+let java_hash s =
+	let h = ref Int32.zero in
+	let l = String.length s in
+	let i31 = Int32.of_int 31 in
+	String.iteri (fun i char ->
+		let char = Int32.of_int (int_of_char char) in
+		h := Int32.add !h (Int32.mul char (pow i31 (l - (i + 1))))
+	) s;
+	!h
+
+let find_overload map_type c cf el =
+	let matches = ref [] in
+	let rec loop cfl = match cfl with
+		| cf :: cfl ->
+			begin match follow (monomorphs cf.cf_params (map_type cf.cf_type)) with
+				| TFun(tl'',_) as tf ->
+					let rec loop2 acc el tl = match el,tl with
+						| e :: el,(n,o,t) :: tl ->
+							begin try
+								Type.unify e.etype t;
+								loop2 ((e,o) :: acc) el tl
+							with _ ->
+								loop cfl
+							end
+						| [],[] ->
+							matches := ((List.rev acc),tf,(c,cf)) :: !matches;
+							loop cfl
+						| _ ->
+							loop cfl
+					in
+					loop2 [] el tl''
+				| t ->
+					loop cfl
+			end;
+		| [] ->
+			List.rev !matches
+	in
+	loop (cf :: cf.cf_overloads)
+
+let find_overload_rec' is_ctor map_type c name el =
+	let candidates = ref [] in
+	let has_function t1 (_,t2,_) =
+		begin match follow t1,t2 with
+		| TFun(tl1,_),TFun(tl2,_) -> type_iseq (TFun(tl1,t_dynamic)) (TFun(tl2,t_dynamic))
+		| _ -> false
+		end
+	in
+	let rec loop map_type c =
+		begin try
+			let cf = if is_ctor then
+				(match c.cl_constructor with Some cf -> cf | None -> raise Not_found)
+			else
+				PMap.find name c.cl_fields
+			in
+			begin match find_overload map_type c cf el with
+			| [] -> raise Not_found
+			| l ->
+				List.iter (fun ((_,t,_) as ca) ->
+					if not (List.exists (has_function t) !candidates) then candidates := ca :: !candidates
+				) l
+			end;
+			if Meta.has Meta.Overload cf.cf_meta || cf.cf_overloads <> [] then raise Not_found
+		with Not_found ->
+			if c.cl_interface then
+				List.iter (fun (c,tl) -> loop (fun t -> apply_params c.cl_params (List.map map_type tl) t) c) c.cl_implements
+			else match c.cl_super with
+			| None -> ()
+			| Some(c,tl) -> loop (fun t -> apply_params c.cl_params (List.map map_type tl) t) c
+		end;
+	in
+	loop map_type c;
+	match Overloads.Resolution.reduce_compatible (List.rev !candidates) with
+	| [_,_,(c,cf)] -> Some(c,cf)
+	| [] -> None
+	| ((_,_,(c,cf)) :: _) (* as resolved *) ->
+		(* let st = s_type (print_context()) in
+		print_endline (Printf.sprintf "Ambiguous overload for %s(%s)" name (String.concat ", " (List.map (fun e -> st e.etype) el)));
+		List.iter (fun (_,t,(c,cf)) ->
+			print_endline (Printf.sprintf "\tCandidate: %s.%s(%s)" (s_type_path c.cl_path) cf.cf_name (st t));
+		) resolved; *)
+		Some(c,cf)
+
+let find_overload_rec is_ctor map_type c cf el =
+	if Meta.has Meta.Overload cf.cf_meta || cf.cf_overloads <> [] then
+		find_overload_rec' is_ctor map_type c cf.cf_name el
+	else
+		Some(c,cf)
+
+let get_construction_mode c cf =
+	if Meta.has Meta.HxGen cf.cf_meta then ConstructInitPlusNew
+	else ConstructInit
+
+(* Haxe *)
+
+exception HarderFailure of string
+
+type field_generation_info = {
+	mutable has_this_before_super : bool;
+	(* This is an ordered list of fields that are targets of super() calls which is determined during
+	   pre-processing. The generator can pop from this list assuming that it processes the expression
+	   in the same order (which is should). *)
+	mutable super_call_fields : (tclass * tclass_field) list;
+}
+
+type generation_context = {
+	com : Common.context;
+	jar : Zip.out_file;
+	t_exception : Type.t;
+	t_throwable : Type.t;
+	anon_lut : ((string * jsignature) list,jpath) Hashtbl.t;
+	anon_path_lut : (path,jpath) Hashtbl.t;
+	field_infos : field_generation_info DynArray.t;
+	implicit_ctors : (path,(path * jsignature,tclass * tclass_field) PMap.t) Hashtbl.t;
+	default_export_config : export_config;
+	mutable current_field_info : field_generation_info option;
+	mutable anon_num : int;
+}
+
+type ret =
+	| RValue of jsignature option
+	| RVoid
+	| RReturn
+
+type method_type =
+	| MStatic
+	| MInstance
+	| MConstructor
+
+type access_kind =
+	| AKPost
+	| AKPre
+	| AKNone
+
+type compare_kind =
+	| CmpNormal of jcmp * jsignature
+	| CmpSpecial of (unit -> jbranchoffset ref)
+
+type block_exit =
+	| ExitExecute of (unit -> unit)
+	| ExitLoop
+
+open NativeSignatures
+
+let rec jsignature_of_type stack t =
+	if List.exists (fast_eq t) stack then object_sig else
+	let jsignature_of_type = jsignature_of_type (t :: stack) in
+	let jtype_argument_of_type t = jtype_argument_of_type stack t in
+	match t with
+	| TAbstract(a,tl) ->
+		begin match a.a_path with
+			| [],"Bool" -> TBool
+			| ["java"],"Int8" -> TByte
+			| ["java"],"Int16" -> TShort
+			| [],"Int" -> TInt
+			| ["haxe"],"Int32" -> TInt
+			| ["haxe"],"Int64" -> TLong
+			| ["java"],"Int64" -> TLong
+			| ["java"],"Char16" -> TChar
+			| [],"Single" -> TFloat
+			| [],"Float" -> TDouble
+			| [],"Null" ->
+				begin match tl with
+				| [t] -> get_boxed_type (jsignature_of_type t)
+				| _ -> assert false
+				end
+			| ["haxe";"ds"],"Vector" ->
+				begin match tl with
+				| [t] -> TArray(jsignature_of_type t,None)
+				| _ -> assert false
+				end
+			| [],"Dynamic" ->
+				object_sig
+			| [],("Class" | "Enum") ->
+				java_class_sig
+			| [],"EnumValue" ->
+				java_enum_sig object_sig
+			| _ ->
+				if Meta.has Meta.CoreType a.a_meta then
+					TObject(a.a_path,List.map jtype_argument_of_type tl)
+				else
+					jsignature_of_type (Abstract.get_underlying_type a tl)
+		end
+	| TDynamic _ -> object_sig
+	| TMono r ->
+		begin match !r with
+		| Some t -> jsignature_of_type t
+		| None -> object_sig
+		end
+	| TInst({cl_path = ([],"String")},[]) -> string_sig
+	| TInst({cl_path = ([],"Array")},[t]) ->
+		let t = get_boxed_type (jsignature_of_type t) in
+		TObject(([],"Array"),[TType(WNone,t)])
+	| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
+		TArray(jsignature_of_type t,None)
+	| TInst({cl_kind = KTypeParameter [t]},_) -> jsignature_of_type t
+	| TInst({cl_kind = KTypeParameter _; cl_path = (_,name)},_) -> TTypeParameter name
+	| TInst({cl_path = ["_Class"],"Class_Impl_"},_) -> java_class_sig
+	| TInst({cl_path = ["_Enum"],"Enum_Impl_"},_) -> java_class_sig
+	| TInst(c,tl) -> TObject(c.cl_path,List.map jtype_argument_of_type tl)
+	| TEnum(en,tl) -> TObject(en.e_path,List.map jtype_argument_of_type tl)
+	| TFun(tl,tr) -> method_sig (List.map (fun (_,o,t) ->
+		let jsig = jsignature_of_type t in
+		let jsig = if o then get_boxed_type jsig else jsig in
+		jsig
+	) tl) (if ExtType.is_void (follow tr) then None else Some (jsignature_of_type tr))
+	| TAnon an -> object_sig
+	| TType(td,tl) -> jsignature_of_type (apply_params td.t_params tl td.t_type)
+	| TLazy f -> jsignature_of_type (lazy_type f)
+
+and jtype_argument_of_type stack t =
+	TType(WNone,jsignature_of_type stack t)
+
+let jsignature_of_type t =
+	jsignature_of_type [] t
+
+module TAnonIdentifiaction = struct
+	let convert_fields fields =
+		let l = PMap.fold (fun cf acc -> cf :: acc) fields [] in
+		let l = List.sort (fun cf1 cf2 -> compare cf1.cf_name cf2.cf_name) l in
+		List.map (fun cf -> cf.cf_name,jsignature_of_type cf.cf_type) l
+
+	let identify gctx fields =
+		if PMap.is_empty fields then
+			haxe_dynamic_object_path,[]
+		else begin
+			let l = convert_fields fields in
+			try
+				Hashtbl.find gctx.anon_lut l,l
+			with Not_found ->
+				let id = gctx.anon_num in
+				gctx.anon_num <- gctx.anon_num + 1;
+				let path = (["haxe";"generated"],Printf.sprintf "Anon%i" id) in
+				Hashtbl.add gctx.anon_lut l path;
+				path,l
+		end
+
+	let identify_as gctx path fields =
+		if not (PMap.is_empty fields) && not (Hashtbl.mem gctx.anon_path_lut path) then begin
+			let fields = convert_fields fields in
+			Hashtbl.add gctx.anon_lut fields path;
+			Hashtbl.add gctx.anon_path_lut path path;
+		end
+
+end
+
+module AnnotationHandler = struct
+	let generate_annotations builder meta =
+		let parse_path e =
+			let sl = try string_list_of_expr_path_raise e with Exit -> Error.error "Field expression expected" (pos e) in
+			let path = match sl with
+				| s :: sl -> List.rev sl,s
+				| _ -> Error.error "Field expression expected" (pos e)
+			in
+			path
+		in
+		let rec parse_value e = match fst e with
+			| EConst (Int s) -> AInt (Int32.of_string s)
+			| EConst (Float s) -> ADouble (float_of_string s)
+			| EConst (String s) -> AString s
+			| EConst (Ident "true") -> ABool true
+			| EConst (Ident "false") -> ABool false
+			| EArrayDecl el -> AArray (List.map parse_value el)
+			| EField(e1,s) ->
+				let path = parse_path e1 in
+				AEnum(object_path_sig path,s)
+			| _ -> Error.error "Expected value expression" (pos e)
+		in
+		let parse_value_pair e = match fst e with
+			| EBinop(OpAssign,(EConst(Ident s),_),e1) ->
+				s,parse_value e1
+			| _ ->
+				Error.error "Assignment expression expected" (pos e)
+		in
+		let parse_expr e = match fst e with
+			| ECall(e1,el) ->
+				let path = parse_path e1 in
+				let _,name = ExtString.String.replace (snd path) "." "$" in
+				let path = (fst path,name) in
+				let values = List.map parse_value_pair el in
+				path,values
+			| _ ->
+				Error.error "Call expression expected" (pos e)
+		in
+		List.iter (fun (m,el,_) -> match m,el with
+			| Meta.Meta,[e] ->
+				let path,annotation = parse_expr e in
+				builder#add_annotation path annotation;
+			| _ ->
+				()
+		) meta
+end
+
+let enum_ctor_sig =
+	let ta = TArray(object_sig,None) in
+	method_sig [TInt;ta] None
+
+let convert_cmp_op = function
+	| OpEq -> CmpEq
+	| OpNotEq -> CmpNe
+	| OpLt -> CmpLt
+	| OpLte -> CmpLe
+	| OpGt -> CmpGt
+	| OpGte -> CmpGe
+	| _ -> assert false
+
+let flip_cmp_op = function
+	| CmpEq -> CmpNe
+	| CmpNe -> CmpEq
+	| CmpLt -> CmpGe
+	| CmpLe -> CmpGt
+	| CmpGt -> CmpLe
+	| CmpGe -> CmpLt
+
+let resolve_class com path =
+	let rec loop types = match types with
+		| (TClassDecl c) :: types when c.cl_path = path ->
+			c
+		| _ :: types ->
+			loop types
+		| [] ->
+			jerror ("No such type: " ^ s_type_path path)
+	in
+	loop com.types
+
+let write_class jar path jc =
+	let dir = match path with
+		| ([],s) -> s
+		| (sl,s) -> String.concat "/" sl ^ "/" ^ s
+	in
+	let path = dir ^ ".class" in
+	let t = Timer.timer ["jvm";"write"] in
+	let ch = IO.output_bytes() in
+	JvmWriter.write_jvm_class ch jc;
+	Zip.add_entry (Bytes.unsafe_to_string (IO.close_out ch)) jar path;
+	t()
+
+let is_const_int_pattern (el,_) =
+	List.for_all (fun e -> match e.eexpr with
+		| TConst (TInt _) -> true
+		| _ -> false
+	) el
+
+let is_const_string_pattern (el,_) =
+	List.for_all (fun e -> match e.eexpr with
+		| TConst (TString _) -> true
+		| _ -> false
+	) el
+
+let is_interface_var_access c cf =
+	c.cl_interface && match cf.cf_kind with
+		| Var _ | Method MethDynamic -> true
+		| _ -> false
+
+let type_unifies a b =
+	try Type.unify a b; true with _ -> false
+
+let get_field_info gctx ml =
+	let rec loop ml = match ml with
+	| (Meta.Custom ":jvm.fieldInfo",[(EConst (Int s),_)],_) :: _ ->
+		Some (DynArray.get gctx.field_infos (int_of_string s))
+	| _ :: ml ->
+		loop ml
+	| [] ->
+		None
+	in
+	loop ml
+
+let follow = Abstract.follow_with_abstracts
+
+class haxe_exception gctx (t : Type.t) = object(self)
+	val native_exception =
+		if follow t == t_dynamic then
+			throwable_sig,false
+		else if type_unifies t gctx.t_exception then
+			jsignature_of_type t,true
+		else
+			haxe_exception_sig,false
+
+	val mutable native_exception_path = None
+
+	method is_assignable_to (exc2 : haxe_exception) =
+		match self#is_native_exception,exc2#is_native_exception with
+		| true, true ->
+			(* Native exceptions are assignable if they unify *)
+			type_unifies t exc2#get_type
+		| false,false ->
+			(* Haxe exceptions are always assignable to each other *)
+			true
+		| false,true ->
+			(* Haxe exception is assignable to native only if caught type is java.lang.Exception/Throwable *)
+			let exc2_native_exception_type = exc2#get_native_exception_type in
+			exc2_native_exception_type = throwable_sig || exc2_native_exception_type = exception_sig
+		| _ ->
+			(* Native to Haxe is never assignable *)
+			false
+
+	method is_native_exception = snd native_exception
+	method get_native_exception_type = fst native_exception
+
+	method get_native_exception_path =
+		match native_exception_path with
+		| None ->
+			let path = (match (fst native_exception) with TObject(path,_) -> path | _ -> assert false) in
+			native_exception_path <- Some path;
+			path
+		| Some path ->
+			path
+
+	method get_type = t
+end
+
+class closure_context (jsig : jsignature) = object(self)
+	val lut = Hashtbl.create 0
+	val sigs = DynArray.create()
+
+	method add (var_id : int) (var_name : string) (var_sig : jsignature) =
+		DynArray.add sigs ((var_id,var_name),var_sig);
+		Hashtbl.add lut var_id (var_sig,var_name)
+
+	method get (code : JvmCode.builder) (var_id : int) =
+		let var_sig,var_name = Hashtbl.find lut var_id in
+		if DynArray.length sigs > 1 then begin
+			(-1),
+			(fun () ->
+				code#aload jsig 0;
+				let offset = code#get_pool#add_field self#get_path var_name var_sig FKField in
+				code#getfield offset jsig var_sig
+			),
+			(fun () ->
+				code#aload jsig 0;
+				let offset = code#get_pool#add_field self#get_path var_name var_sig FKField in
+				code#putfield offset jsig var_sig
+			)
+		end else begin
+			(-1),
+			(fun () ->
+				code#aload jsig 0;
+			),
+			(fun () ->
+				code#aload jsig 0;
+			)
+		end
+
+	method get_constructor_sig =
+		method_sig (List.map snd (DynArray.to_list sigs)) None
+
+	method get_jsig = jsig
+	method get_path = match jsig with TObject(path,_) -> path | _ -> assert false
+
+	method get_args = DynArray.to_list sigs
+end
+
+let create_context_class gctx jc jm name vl = match vl with
+	| [(vid,vname,vsig)] ->
+		let jsig = get_boxed_type vsig in
+		let ctx_class = new closure_context jsig in
+		ctx_class#add vid vname jsig;
+		ctx_class
+	| _ ->
+		let jc = jc#spawn_inner_class (Some jm) object_path None in
+		let path = jc#get_this_path in
+		let ctx_class = new closure_context (object_path_sig path) in
+		let jsigs = List.map (fun (_,_,vsig) -> vsig) vl in
+		let jm_ctor = jc#spawn_method "<init>" (method_sig jsigs None) [MPublic] in
+		jm_ctor#load_this;
+		jm_ctor#call_super_ctor ConstructInit (method_sig [] None);
+		List.iter2 (fun (vid,vname,vtype) jsig ->
+			jm_ctor#add_argument_and_field vname jsig;
+			ctx_class#add vid vname jsig;
+		) vl jsigs;
+		jm_ctor#get_code#return_void;
+		write_class gctx.jar path (jc#export_class gctx.default_export_config);
+		ctx_class
+
+let rvalue_any = RValue None
+let rvalue_sig jsig = RValue (Some jsig)
+let rvalue_type t = RValue (Some (jsignature_of_type t))
+
+class texpr_to_jvm gctx (jc : JvmClass.builder) (jm : JvmMethod.builder) (return_type : Type.t) = object(self)
+	val com = gctx.com
+	val code = jm#get_code
+	val pool : JvmConstantPool.constant_pool = jc#get_pool
+
+	val mutable local_lookup = Hashtbl.create 0;
+	val mutable last_line = 0
+
+	val mutable breaks = []
+	val mutable continue = 0
+	val mutable caught_exceptions = []
+	val mutable block_exits = []
+	val mutable env = None
+
+	method vtype t =
+		jsignature_of_type t
+
+	method mknull t = com.basic.tnull (follow t)
+
+	(* locals *)
+
+	method add_named_local (name : string) (jsig : jsignature) =
+		jm#add_local name jsig VarArgument
+
+	method add_local v init_state : (int * (unit -> unit) * (unit -> unit)) =
+		let t = self#vtype v.v_type in
+		let slot,load,store = jm#add_local v.v_name t init_state in
+		Hashtbl.add local_lookup v.v_id (slot,load,store);
+		slot,load,store
+
+	method get_local_by_id (vid,vname) =
+		if vid = 0 then
+			(0,(fun () -> jm#load_this),(fun () -> assert false))
+		else try
+			Hashtbl.find local_lookup vid
+		with Not_found -> try
+			begin match env with
+			| Some env ->
+				env#get code vid
+			| None ->
+				raise Not_found
+			end
+		with Not_found ->
+			failwith ("Unbound local: " ^ vname)
+
+	method get_local v =
+		self#get_local_by_id (v.v_id,v.v_name)
+
+	method set_context (ctx : closure_context) =
+		env <- Some ctx
+
+	(* casting *)
+
+	method expect_reference_type = jm#expect_reference_type
+
+	method cast t =
+		if follow t != t_dynamic then begin
+			let vt = self#vtype t in
+			jm#cast vt
+		end else
+			self#expect_reference_type
+
+	method cast_expect ret t = match ret with
+		| RValue (Some jsig) -> jm#cast jsig
+		| _ -> self#cast t
+
+	method tfunction e tf =
+		let name = jc#get_next_closure_name in
+		let outside = match Texpr.collect_captured_vars e with
+			| [],false ->
+				None
+			| vl,accesses_this ->
+				let vl = List.map (fun v -> v.v_id,v.v_name,jsignature_of_type v.v_type) vl in
+				let vl = if accesses_this then (0,"this",jc#get_jsig) :: vl else vl in
+				let ctx_class = create_context_class gctx jc jm name vl in
+				Some ctx_class
+		in
+		let jsig =
+			let args = List.map (fun (v,cto) ->
+				if cto <> None then v.v_type <- self#mknull v.v_type;
+				self#vtype v.v_type
+			) tf.tf_args in
+			let args = match outside with
+				| None -> args
+				| Some ctx_class -> ctx_class#get_jsig :: args
+			in
+			method_sig args (if ExtType.is_void (follow tf.tf_type) then None else Some (self#vtype tf.tf_type))
+		in
+		begin
+			let jm = jc#spawn_method name jsig [MPublic;MStatic] in
+			let handler = new texpr_to_jvm gctx jc jm tf.tf_type in
+			begin match outside with
+			| None -> ()
+			| Some ctx_class ->
+				handler#set_context ctx_class;
+				let name = match ctx_class#get_args with
+					| [(_,name),_] -> name
+					| _ -> "_hx_ctx"
+				in
+				ignore(handler#add_named_local name ctx_class#get_jsig)
+			end;
+			let inits = List.map (fun (v,cto) ->
+				let _,load,save = handler#add_local v VarArgument in
+				match cto with
+				| Some e when (match e.eexpr with TConst TNull -> false | _ -> true) ->
+					let f () =
+						load();
+						let jsig = self#vtype v.v_type in
+						jm#if_then
+							(fun () -> jm#get_code#if_nonnull_ref jsig)
+							(fun () ->
+								handler#texpr (rvalue_sig jsig) e;
+								jm#cast jsig;
+								save();
+							)
+					in
+					Some f
+				| _ ->
+					None
+			) tf.tf_args in
+			jm#finalize_arguments;
+			List.iter (function
+				| None -> ()
+				| Some f -> f()
+			) inits;
+			handler#texpr RReturn tf.tf_expr;
+		end;
+		jm#read_closure true jc#get_this_path name jsig;
+		outside
+
+	(* access *)
+
+	method read_native_array vta vte =
+		NativeArray.read code vta vte
+
+	method write_native_array vta vte =
+		NativeArray.write code vta vte
+
+	method read cast e1 fa =
+		match fa with
+		| FStatic({cl_path = (["java";"lang"],"Math")},({cf_name = "NaN" | "POSITIVE_INFINITY" | "NEGATIVE_INFINITY"} as cf)) ->
+			jm#getstatic double_path cf.cf_name TDouble
+		| FStatic({cl_path = (["java";"lang"],"Math")},({cf_name = "isNaN" | "isFinite"} as cf)) ->
+			jm#read_closure true double_path cf.cf_name (jsignature_of_type cf.cf_type);
+		| FStatic({cl_path = (["java";"lang"],"String")},({cf_name = "fromCharCode"} as cf)) ->
+			jm#read_closure true (["haxe";"jvm"],"StringExt") cf.cf_name (jsignature_of_type cf.cf_type);
+		| FStatic(c,({cf_kind = Method (MethNormal | MethInline)} as cf)) ->
+			jm#read_closure true c.cl_path cf.cf_name (jsignature_of_type cf.cf_type);
+		| FStatic(c,cf) ->
+			jm#getstatic c.cl_path cf.cf_name (self#vtype cf.cf_type);
+			cast();
+		| FInstance({cl_path = (["java";"lang"],"String")},_,{cf_name = "length"}) ->
+			self#texpr rvalue_any e1;
+			jm#invokevirtual string_path "length" (method_sig [] (Some TInt))
+		| FInstance({cl_path = (["java"],"NativeArray")},_,{cf_name = "length"}) ->
+			self#texpr rvalue_any e1;
+			let vtobj = self#vtype e1.etype in
+			code#arraylength vtobj;
+		| FInstance(c,tl,cf) | FClosure(Some(c,tl),({cf_kind = Method MethDynamic} as cf)) when not (is_interface_var_access c cf) ->
+			self#texpr rvalue_any e1;
+			jm#getfield c.cl_path cf.cf_name (self#vtype cf.cf_type);
+			cast();
+		| FEnum(en,ef) when not (match follow ef.ef_type with TFun _ -> true | _ -> false) ->
+			let jsig = self#vtype ef.ef_type in
+			let offset = pool#add_field en.e_path ef.ef_name jsig FKField in
+			code#getstatic offset jsig;
+			cast();
+		| FAnon ({cf_name = s} as cf) ->
+			self#texpr rvalue_any e1;
+			let default () =
+				jm#string s;
+				jm#invokestatic haxe_jvm_path "readField" (method_sig [object_sig;string_sig] (Some object_sig));
+				cast();
+			in
+			begin match follow e1.etype with
+			| TAnon an ->
+				let path,_ = TAnonIdentifiaction.identify gctx an.a_fields in
+				code#dup;
+				code#instanceof path;
+				jm#if_then_else
+					(fun () -> code#if_ref CmpEq)
+					(fun () ->
+						jm#cast (object_path_sig path);
+						jm#getfield path s (self#vtype cf.cf_type);
+						cast();
+					)
+					(fun () -> default());
+			| _ ->
+				default();
+			end
+		| FDynamic s | FInstance(_,_,{cf_name = s}) | FEnum(_,{ef_name = s}) | FClosure(Some({cl_interface = true},_),{cf_name = s}) ->
+			self#texpr rvalue_any e1;
+			jm#string s;
+			jm#invokestatic haxe_jvm_path "readField" (method_sig [object_sig;string_sig] (Some object_sig));
+			cast();
+		| FClosure((Some(c,_)),cf) ->
+			let jsig = self#vtype cf.cf_type in
+			jm#read_closure false c.cl_path cf.cf_name jsig;
+			self#texpr rvalue_any e1;
+			jm#invokevirtual method_handle_path "bindTo" (method_sig [object_sig] (Some method_handle_sig));
+		| _ ->
+			assert false
+
+	method read_write ret ak e (f : unit -> unit) =
+		let apply dup =
+			if ret <> RVoid && ak = AKPost then dup();
+			f();
+			if ret <> RVoid && ak <> AKPost then dup();
+		in
+		match (Texpr.skip e).eexpr with
+		| TLocal v ->
+			let _,load,store = self#get_local v in
+			if ak <> AKNone then load();
+			apply (fun () -> code#dup);
+			store();
+		| TField(_,FStatic(c,cf)) ->
+			let jsig_cf = self#vtype cf.cf_type in
+			if ak <> AKNone then jm#getstatic c.cl_path cf.cf_name jsig_cf;
+			apply (fun () -> code#dup);
+			jm#putstatic c.cl_path cf.cf_name jsig_cf;
+		| TField(e1,FInstance(c,tl,cf)) when not (is_interface_var_access c cf) ->
+			self#texpr rvalue_any e1;
+			let jsig_cf = self#vtype cf.cf_type in
+			if ak <> AKNone then begin
+				code#dup;
+				jm#getfield c.cl_path cf.cf_name jsig_cf
+			end;
+			apply (fun () -> code#dup_x1);
+			self#cast cf.cf_type;
+			jm#putfield c.cl_path cf.cf_name jsig_cf
+		| TField(e1,(FDynamic s | FAnon {cf_name = s} | FInstance(_,_,{cf_name = s}))) ->
+			self#texpr rvalue_any e1;
+			if ak <> AKNone then code#dup;
+			jm#string s;
+			if ak <> AKNone then begin
+				code#dup_x1;
+				jm#invokestatic haxe_jvm_path "readField" (method_sig [object_sig;string_sig] (Some object_sig));
+				self#cast_expect ret e.etype;
+			end;
+			apply (fun () -> code#dup_x2);
+			self#cast (self#mknull e.etype);
+			jm#invokestatic haxe_jvm_path "writeField" (method_sig [object_sig;string_sig;object_sig] None)
+		| TArray(e1,e2) ->
+			begin match follow e1.etype with
+				| TInst({cl_path = ([],"Array")} as c,[t]) ->
+					let t = self#mknull t in
+					self#texpr rvalue_any e1;
+					if ak <> AKNone then code#dup;
+					self#texpr rvalue_any e2;
+					jm#cast TInt;
+					if ak <> AKNone then begin
+						code#dup_x1;
+						jm#invokevirtual c.cl_path "__get" (method_sig [TInt] (Some object_sig));
+						self#cast_expect ret e.etype;
+					end;
+					apply (fun () -> code#dup_x2;);
+					self#cast t;
+					jm#invokevirtual c.cl_path "__set" (method_sig [TInt;object_sig] None);
+				| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
+					let vte = self#vtype t in
+					let vta = self#vtype e1.etype in
+					self#texpr rvalue_any e1;
+					if ak <> AKNone then code#dup;
+					self#texpr rvalue_any e2;
+					if ak <> AKNone then begin
+						code#dup_x1;
+						self#read_native_array vta vte
+					end;
+					apply (fun () -> code#dup_x2);
+					self#cast t;
+					self#write_native_array vta vte
+				| _ ->
+					self#texpr rvalue_any e1;
+					if ak <> AKNone then code#dup;
+					self#texpr rvalue_any e2;
+					jm#cast TInt;
+					if ak <> AKNone then begin
+						code#dup_x1;
+						jm#invokestatic haxe_jvm_path "arrayRead" (method_sig [object_sig;TInt] (Some object_sig));
+					end;
+					apply (fun () -> code#dup_x2;);
+					self#cast e.etype;
+					self#expect_reference_type;
+					jm#invokestatic haxe_jvm_path "arrayWrite" (method_sig [object_sig;TInt;object_sig] None);
+				end
+		| _ ->
+			print_endline (s_expr_ast false "" (s_type (print_context())) e);
+			assert false
+
+	(* branching *)
+
+	method apply_cmp = function
+		| CmpNormal(op,_) -> (fun () -> code#if_ref op)
+		| CmpSpecial f -> f
+
+	method if_null t =
+		(fun () -> code#if_null_ref t)
+
+	method if_not_null t =
+		(fun () -> code#if_nonnull_ref t)
+
+	method condition e = match (Texpr.skip e).eexpr with
+		| TBinop((OpEq | OpNotEq | OpLt | OpGt | OpLte | OpGte) as op,e1,e2) ->
+			let op = convert_cmp_op op in
+			self#binop_compare op e1 e2
+		| _ ->
+			self#texpr rvalue_any e;
+			jm#cast TBool;
+			CmpNormal(CmpEq,TBool)
+
+	method switch ret e1 cases def =
+		(* TODO: hack because something loses the exhaustiveness marker before we get here *)
+		let is_exhaustive = OptimizerTexpr.is_exhaustive e1 || (ExtType.is_bool (follow e1.etype) && List.length cases > 1) in
+		if cases = [] then
+			self#texpr ret e1
+		else if List.for_all is_const_int_pattern cases then begin
+			let cases = List.map (fun (el,e) ->
+				let il = List.map (fun e -> match e.eexpr with
+					| TConst (TInt i32) -> i32
+					| _ -> assert false
+				) el in
+				(il,(fun () -> self#texpr ret e))
+			) cases in
+			let def = match def with
+				| None -> None
+				| Some e -> Some (fun () -> self#texpr ret e)
+			in
+			self#texpr rvalue_any e1;
+			jm#cast TInt;
+			jm#int_switch is_exhaustive cases def
+		end else if List.for_all is_const_string_pattern cases then begin
+			let cases = List.map (fun (el,e) ->
+				let il = List.map (fun e -> match e.eexpr with
+					| TConst (TString s) -> java_hash s
+					| _ -> assert false
+				) el in
+				(il,(fun () -> self#texpr ret e))
+			) cases in
+			let def = match def with
+				| None -> None
+				| Some e -> Some (fun () -> self#texpr ret e)
+			in
+			self#texpr rvalue_any e1;
+			jm#cast string_sig;
+			jm#invokevirtual string_path "hashCode" (method_sig [] (Some TInt));
+			jm#int_switch is_exhaustive cases def
+		end else begin
+			(* TODO: rewriting this is stupid *)
+			let pop_scope = jm#push_scope in
+			self#texpr rvalue_any e1;
+			let v = alloc_var VGenerated "tmp" e1.etype null_pos in
+			let _,_,store = self#add_local v VarWillInit in
+			self#cast v.v_type;
+			store();
+			let ev = mk (TLocal v) v.v_type null_pos in
+			let el = List.rev_map (fun (el,e) ->
+				let f e' = mk (TBinop(OpEq,ev,e')) com.basic.tbool e'.epos in
+				let e_cond = match el with
+					| [] -> assert false
+					| [e] -> f e
+					| e :: el ->
+						List.fold_left (fun eacc e ->
+							mk (TBinop(OpBoolOr,eacc,f e)) com.basic.tbool e.epos
+						) (f e) el
+				in
+				(e_cond,e)
+			) cases in
+			(* If we rewrite an exhaustive switch that has no default value, treat the last case as the default case to satisfy control flow. *)
+			let cases,def = if is_exhaustive && def = None then (match List.rev cases with (_,e) :: cases -> List.rev cases,Some e | _ -> assert false) else cases,def in
+			let e = List.fold_left (fun e_else (e_cond,e_then) -> Some (mk (TIf(e_cond,e_then,e_else)) e_then.etype e_then.epos)) def el in
+			self#texpr ret (Option.get e);
+			pop_scope()
+		end
+
+	(* binops *)
+
+	method binop_exprs cast_type f1 f2 =
+		f1();
+		jm#cast ~allow_to_string:true cast_type;
+		f2();
+		jm#cast ~allow_to_string:true cast_type;
+
+	method get_binop_type_sig jsig1 jsig2 = match jsig1,jsig2 with
+		| TObject((["java";"lang"],"String"),_),_
+		| _,TObject((["java";"lang"],"String"),_) ->
+			string_sig
+		| TLong,_ | _,TLong -> TLong
+		| TDouble,_ | _,TDouble -> TDouble
+		| TFloat,_ | _,TFloat -> TFloat
+		| TInt,_ | _,TInt -> TInt
+		| TShort,_ | _,TShort -> TShort
+		| TChar,_ | _,TChar -> TChar
+		| TByte,_ | _,TByte -> TByte
+		| TBool,_ | _,TBool -> TBool
+		| jsig1,jsig2 ->
+			if jsig1 = string_sig || jsig2 = string_sig then
+				string_sig
+			else
+				object_sig
+
+	method get_binop_type t1 t2 = self#get_binop_type_sig (jsignature_of_type t1) (jsignature_of_type t2)
+
+	method do_compare op =
+		match code#get_stack#get_stack_items 2 with
+		| [TInt | TByte | TChar | TBool;TInt | TByte | TChar | TBool] ->
+			let op = flip_cmp_op op in
+			CmpSpecial (fun () -> code#if_icmp_ref op)
+		| [TObject((["java";"lang"],"String"),[]);TObject((["java";"lang"],"String"),[])] ->
+			jm#invokestatic haxe_jvm_path "stringCompare" (method_sig [string_sig;string_sig] (Some TInt));
+			let op = flip_cmp_op op in
+			CmpNormal(op,TBool)
+		| [TObject((["java";"lang"],"Object"),[]) | TTypeParameter _;_]
+		| [_;TObject((["java";"lang"],"Object"),[]) | TTypeParameter _] ->
+			jm#invokestatic haxe_jvm_path "compare" (method_sig [object_sig;object_sig] (Some TInt));
+			let op = flip_cmp_op op in
+			CmpNormal(op,TBool)
+		| [(TObject _ | TArray _) as t1;(TObject _ | TArray _) as t2] ->
+			CmpSpecial (fun () -> (if op = CmpEq then code#if_acmp_ne_ref else code#if_acmp_eq_ref) t1 t2)
+		| [TDouble;TDouble] ->
+			let op = flip_cmp_op op in
+			begin match op with
+			| CmpGe | CmpGt -> code#dcmpg;
+			| _ -> code#dcmpl;
+			end;
+			CmpNormal(op,TDouble)
+		| [TFloat;TFloat] ->
+			let op = flip_cmp_op op in
+			begin match op with
+			| CmpGe | CmpGt -> code#fcmpg;
+			| _ -> code#fcmpl;
+			end;
+			CmpNormal(op,TFloat)
+		| [TLong;TLong] ->
+			let op = flip_cmp_op op in
+			code#lcmpl;
+			CmpNormal(op,TLong)
+		| [t1;t2] ->
+			jerror (Printf.sprintf "Can't compare %s and %s" (generate_signature false t1) (generate_signature false t2))
+		| tl ->
+			jerror (Printf.sprintf "Bad stack: %s" (String.concat ", " (List.map (generate_signature false) tl)));
+
+	method binop_compare op e1 e2 =
+		let sig1 = jsignature_of_type e1.etype in
+		let sig2 = jsignature_of_type e2.etype in
+		match (Texpr.skip e1),(Texpr.skip e2) with
+		| {eexpr = TConst TNull},_ when not (is_unboxed sig2) ->
+			self#texpr rvalue_any e2;
+			CmpSpecial ((if op = CmpEq then self#if_not_null else self#if_null) sig2)
+		| _,{eexpr = TConst TNull} when not (is_unboxed sig1) ->
+			self#texpr rvalue_any e1;
+			CmpSpecial ((if op = CmpEq then self#if_not_null else self#if_null) sig1)
+		| {eexpr = TConst (TInt i32);etype = t2},e1 when Int32.to_int i32 = 0 && sig2 = TInt ->
+			let op = match op with
+				| CmpGt -> CmpGe
+				| CmpLt -> CmpLe
+				| CmpLe -> CmpLt
+				| CmpGe -> CmpGt
+				| CmpEq -> CmpNe
+				| CmpNe -> CmpEq
+			in
+			self#texpr rvalue_any e1;
+			self#cast t2;
+			CmpNormal(op,TInt)
+		| e1,{eexpr = TConst (TInt i32); etype = t2;} when Int32.to_int i32 = 0 && sig1 = TInt->
+			let op = flip_cmp_op op in
+			self#texpr rvalue_any e1;
+			self#cast t2;
+			CmpNormal(op,TInt)
+		| _ ->
+			match is_unboxed sig1,is_unboxed sig2 with
+			| true,true ->
+				let f e () = self#texpr rvalue_any e in
+				self#binop_exprs (self#get_binop_type e1.etype e2.etype) (f e1) (f e2);
+				self#do_compare op
+			| false,false ->
+				let sig_unboxed1 = get_unboxed_type sig1 in
+				let sig_unboxed2 = get_unboxed_type sig2 in
+				if sig1 = sig_unboxed1 && sig2 = sig_unboxed2 then begin
+					(* No basic types involved, do normal comparison *)
+					self#texpr rvalue_any e1;
+					self#texpr rvalue_any e2;
+					self#do_compare op
+				end else begin
+					(* At least one of the types is a wrapped numeric one *)
+					let cast_type = self#get_binop_type_sig sig_unboxed1 sig_unboxed2 in
+					self#texpr rvalue_any e1;
+					jm#get_code#dup;
+					jm#if_then_else
+						(self#if_not_null sig1)
+						(fun () ->
+							jm#get_code#pop;
+							self#texpr rvalue_any e2;
+							self#boolop (CmpSpecial (self#if_not_null sig2))
+						)
+						(fun () ->
+							jm#cast ~not_null:true cast_type;
+							self#texpr rvalue_any e2;
+							jm#get_code#dup;
+							jm#if_then_else
+								(self#if_not_null sig2)
+								(fun () ->
+									jm#get_code#pop;
+									jm#get_code#pop;
+									jm#get_code#bconst (op = CmpNe);
+								)
+								(fun () ->
+									jm#cast ~not_null:true cast_type;
+									self#boolop (self#do_compare op);
+								)
+						);
+					CmpNormal(CmpEq,TBool)
+				end
+			| false,true ->
+				self#texpr rvalue_any e1;
+				jm#get_code#dup;
+				jm#if_then_else
+					(self#if_not_null sig1)
+					(fun () ->
+						jm#get_code#pop;
+						jm#get_code#bconst (op = CmpNe)
+					)
+					(fun () ->
+						jm#cast ~not_null:true sig2;
+						self#texpr rvalue_any e2;
+						self#boolop (self#do_compare op)
+					);
+				CmpNormal(CmpEq,TBool)
+			| true,false ->
+				self#texpr rvalue_any e1;
+				self#texpr rvalue_any e2;
+				jm#get_code#dup;
+				jm#if_then_else
+					(self#if_not_null sig2)
+					(fun () ->
+						jm#get_code#pop;
+						jm#get_code#pop;
+						jm#get_code#bconst (op = CmpNe);
+					)
+					(fun () ->
+						jm#cast ~not_null:true sig1;
+						self#boolop (self#do_compare op)
+					);
+				CmpNormal(CmpEq,TBool)
+
+	method binop_basic ret op cast_type f1 f2 =
+		let emit_exprs () = self#binop_exprs cast_type f1 f2 in
+		let method_name () = match op with
+			| OpAdd -> "opAdd"
+			| OpSub -> "opSub"
+			| OpMult -> "opMul"
+			| OpDiv -> "opDiv"
+			| OpMod -> "opMod"
+			| OpAnd -> "opAnd"
+			| OpOr -> "opOr"
+			| OpXor -> "opXor"
+			| OpShl -> "opShl"
+			| OpShr -> "opShr"
+			| OpUShr -> "opUshr"
+			| _ -> assert false
+		in
+		begin match cast_type with
+			| TByte | TShort | TInt ->
+				begin match op with
+				| OpAdd ->
+					emit_exprs();
+					code#iadd
+				| OpSub ->
+					emit_exprs();
+					code#isub
+				| OpMult ->
+					emit_exprs();
+					code#imul
+				| OpDiv ->
+					f1();
+					jm#cast TDouble;
+					f2();
+					jm#cast TDouble;
+					code#ddiv;
+				| OpAnd ->
+					emit_exprs();
+					code#iand
+				| OpOr ->
+					emit_exprs();
+					code#ior
+				| OpXor ->
+					emit_exprs();
+					code#ixor
+				| OpShl ->
+					emit_exprs();
+					code#ishl
+				| OpShr ->
+					emit_exprs();
+					code#ishr
+				| OpUShr ->
+					emit_exprs();
+					code#iushr
+				| OpMod ->
+					emit_exprs();
+					code#irem
+				| _ -> jerror (Printf.sprintf "Unsupported binop on TInt: %s" (s_binop op))
+				end
+			| TFloat ->
+				emit_exprs();
+				begin match op with
+				| OpAdd -> code#fadd
+				| OpSub -> code#fsub
+				| OpMult -> code#fmul
+				| OpDiv -> code#fdiv
+				| OpMod -> code#frem
+				| _ -> jerror (Printf.sprintf "Unsupported binop on TFloat: %s" (s_binop op))
+				end
+			| TDouble ->
+				emit_exprs();
+				begin match op with
+				| OpAdd -> code#dadd
+				| OpSub -> code#dsub
+				| OpMult -> code#dmul
+				| OpDiv -> code#ddiv
+				| OpMod -> code#drem
+				| _ -> jerror (Printf.sprintf "Unsupported binop on TDouble: %s" (s_binop op))
+				end
+			| TLong ->
+				begin match op with
+				| OpAdd ->
+					emit_exprs();
+					code#ladd
+				| OpSub ->
+					emit_exprs();
+					code#lsub
+				| OpMult ->
+					emit_exprs();
+					code#lmul
+				| OpDiv ->
+					emit_exprs();
+					code#ldiv
+				| OpAnd ->
+					emit_exprs();
+					code#land_
+				| OpOr ->
+					emit_exprs();
+					code#lor_
+				| OpXor ->
+					emit_exprs();
+					code#lxor_
+				| OpShl ->
+					f1();
+					jm#cast TLong;
+					f2();
+					jm#cast TInt;
+					code#lshl;
+				| OpShr ->
+					f1();
+					jm#cast TLong;
+					f2();
+					jm#cast TInt;
+					code#lshr;
+				| OpUShr ->
+					f1();
+					jm#cast TLong;
+					f2();
+					jm#cast TInt;
+					code#lushr;
+				| OpMod ->
+					emit_exprs();
+					code#lrem
+				| _ -> jerror (Printf.sprintf "Unsupported binop on TInt: %s" (s_binop op))
+				end
+			| TBool | TObject((["java";"lang"],"Object"),_) | TTypeParameter _ ->
+				begin match op with
+				| OpBoolAnd ->
+					let operand f =
+						f();
+						jm#cast TBool;
+					in
+					operand f1;
+					jm#if_then_else
+						(fun () -> code#if_ref CmpEq)
+						(fun () -> operand f2)
+						(fun () -> code#bconst false)
+				| OpBoolOr ->
+					let operand f =
+						f();
+						jm#cast TBool;
+					in
+					operand f1;
+					jm#if_then_else
+						(fun () -> code#if_ref CmpEq)
+						(fun () -> code#bconst true)
+						(fun () -> operand f2)
+				| _ ->
+					emit_exprs();
+					let name = method_name () in
+					jm#invokestatic haxe_jvm_path name (method_sig [object_sig;object_sig] (Some object_sig))
+				end
+			| TObject(path,_) ->
+				emit_exprs();
+				if path = string_path then
+					jm#invokestatic haxe_jvm_path "stringConcat" (method_sig [object_sig;object_sig] (Some string_sig))
+				else begin
+					let name = method_name () in
+					jm#invokestatic haxe_jvm_path name (method_sig [object_sig;object_sig] (Some object_sig))
+				end
+			| _ ->
+				jerror (Printf.sprintf "Unsupported operation %s on %s" (s_binop op) (generate_signature false cast_type))
+		end;
+
+	method boolop cmp =
+		jm#if_then_else
+			(self#apply_cmp cmp)
+			(fun () -> code#bconst true)
+			(fun () -> code#bconst false)
+
+	method var_slot_is_in_int8_range v =
+		let slot,_,_ = self#get_local v in
+		in_range true Int8Range slot
+
+	method binop ret op e1 e2 = match op with
+		| OpEq | OpNotEq | OpLt | OpGt | OpLte | OpGte ->
+			let op = convert_cmp_op op in
+			self#boolop (self#binop_compare op e1 e2)
+		| OpAssign ->
+			let f () =
+				self#texpr (rvalue_type e1.etype) e2;
+				self#cast e1.etype;
+			in
+			self#read_write ret AKNone e1 f
+		| OpAssignOp op ->
+			let jsig1 = jsignature_of_type e1.etype in
+			begin match op,(Texpr.skip e1).eexpr,(Texpr.skip e2).eexpr with
+			| OpAdd,TLocal v,TConst (TInt i32) when is_unboxed (self#vtype v.v_type) && in_range false Int8Range (Int32.to_int i32) && self#var_slot_is_in_int8_range v->
+				let slot,load,_ = self#get_local v in
+				let i = Int32.to_int i32 in
+				code#iinc slot i;
+				if ret <> RVoid then load();
+			| OpSub,TLocal v,TConst (TInt i32) when is_unboxed (self#vtype v.v_type) && in_range false Int8Range (-Int32.to_int i32) && self#var_slot_is_in_int8_range v ->
+				let slot,load,_ = self#get_local v in
+				let i = -Int32.to_int i32 in
+				code#iinc slot i;
+				if ret <> RVoid then load();
+			| _ ->
+				let f () =
+					self#binop_basic ret op (self#get_binop_type e1.etype e2.etype) (fun () -> ()) (fun () -> self#texpr rvalue_any e2);
+					jm#cast jsig1;
+				in
+				self#read_write ret AKPre e1 f
+			end
+		| _ ->
+			let f e () = self#texpr rvalue_any e in
+			self#binop_basic ret op (self#get_binop_type e1.etype e2.etype) (f e1) (f e2)
+
+	method unop ret op flag e =
+		match op,(Texpr.skip e).eexpr with
+		| (Increment | Decrement),TLocal v when ExtType.is_int v.v_type && self#var_slot_is_in_int8_range v ->
+			let slot,load,_ = self#get_local v in
+			if flag = Postfix && ret <> RVoid then load();
+			code#iinc slot (if op = Increment then 1 else -1);
+			if flag = Prefix && ret <> RVoid then load();
+		| (Increment | Decrement),_ ->
+			let is_null = is_null e.etype in
+			let f () =
+				begin match jm#get_code#get_stack#top with
+				| TLong ->
+					code#lconst Int64.one;
+					if op = Increment then code#ladd else code#lsub
+				| TDouble ->
+					code#dconst 1.;
+					if op = Increment then code#dadd else code#dsub
+				| TByte | TShort | TInt ->
+					code#iconst Int32.one;
+					if op = Increment then code#iadd else code#isub;
+					if is_null then self#expect_reference_type;
+				| _ ->
+					jm#invokestatic haxe_jvm_path (if op = Increment then "opIncrement" else "opDecrement") (method_sig [object_sig] (Some object_sig))
+				end
+			in
+			self#read_write ret (if flag = Prefix then AKPre else AKPost) e f;
+		| Neg,_ ->
+			self#texpr rvalue_any e;
+			let jsig = jsignature_of_type (follow e.etype) in
+			jm#cast jsig;
+			begin match jsig with
+			| TLong -> code#lneg;
+			| TDouble -> code#dneg;
+			| TByte | TShort | TInt -> code#ineg;
+			| _ -> jm#invokestatic haxe_jvm_path "opNeg" (method_sig [object_sig] (Some object_sig))
+			end;
+			self#cast e.etype;
+		| Not,_ ->
+			jm#if_then_else
+				(self#apply_cmp (self#condition e))
+				(fun () -> code#bconst false)
+				(fun () -> code#bconst true)
+		| NegBits,_ ->
+			let jsig = jsignature_of_type (follow e.etype) in
+			self#texpr rvalue_any e;
+			jm#cast jsig;
+			begin match jsig with
+			| TByte | TShort | TInt ->
+				code#iconst Int32.minus_one;
+				code#ixor;
+			| TLong ->
+				code#lconst Int64.minus_one;
+				code#lxor_;
+			| _ ->
+				jm#invokestatic haxe_jvm_path "opNegBits" (method_sig [object_sig] (Some object_sig))
+			end;
+			self#cast e.etype;
+
+	(* calls *)
+
+	method get_argument_signatures t el =
+		match jsignature_of_type t with
+		| TMethod(jsigs,r) -> jsigs,r
+		| _ -> List.map (fun _ -> object_sig) el,(Some object_sig)
+
+	method call_arguments t el =
+		let tl,tr = self#get_argument_signatures t el in
+		let rec loop acc tl el = match tl,el with
+			| jsig :: tl,e :: el ->
+					self#texpr (rvalue_sig jsig) e;
+					jm#cast jsig;
+					loop (jsig :: acc) tl el
+			| _,[] -> List.rev acc
+			| [],e :: el ->
+				(* TODO: this sucks *)
+				self#texpr rvalue_any e;
+				loop (self#vtype e.etype :: acc) [] el
+		in
+		let tl = loop [] tl el in
+		tl,tr
+
+	method call ret tr e1 el =
+		let retype tr = match tr with None -> [] | Some t -> [t] in
+		let tro = match (Texpr.skip e1).eexpr with
+		| TField(_,FStatic({cl_path = ["haxe";"jvm"],"Jvm"},({cf_name = "referenceEquals"} as cf))) ->
+			let tl,tr = self#call_arguments cf.cf_type el in
+			begin match tl with
+				| [t1;t2] -> self#boolop (CmpSpecial (fun () -> code#if_acmp_ne_ref t1 t2))
+				| _ -> assert false
+			end;
+			tr
+		| TField(_,FStatic({cl_path = ["haxe";"jvm"],"Jvm"},({cf_name = "instanceof"}))) ->
+			begin match el with
+				| [e1;{eexpr = TTypeExpr mt;epos = pe}] ->
+					self#texpr rvalue_any e1;
+					self#expect_reference_type;
+					let path = match jsignature_of_type (type_of_module_type mt) with
+						| TObject(path,_) -> path
+						| _ -> Error.error "Class expected" pe
+					in
+					code#instanceof path;
+					Some TBool
+				| _ -> Error.error "Type expression expected" e1.epos
+			end;
+		| TField(_,FStatic({cl_path = ["haxe";"jvm"],"Jvm"},({cf_name = "invokedynamic"}))) ->
+			begin match el with
+				| e_bsm :: {eexpr = TConst (TString name)} :: {eexpr = TArrayDecl el_static_args} :: el ->
+					let t = tfun (List.map (fun e -> e.etype) el) tr in
+					let tl,tr = self#call_arguments t el in
+					let path,mname = match e_bsm.eexpr with
+						| TField(_,FStatic(c,cf)) -> c.cl_path,cf.cf_name
+						| _ -> Error.error "Reference to bootstrap method expected" e_bsm.epos
+					in
+					let rec loop consts jsigs static_args = match static_args with
+						| e :: static_args ->
+							let const,jsig =  match e.eexpr with
+							| TConst (TString s) -> pool#add_const_string s,string_sig
+							| TConst (TInt i) -> pool#add (ConstInt i),TInt
+							| TConst (TFloat f) -> pool#add (ConstDouble (float_of_string f)),TDouble
+							| TField(_,FStatic(c,cf)) ->
+								let offset = pool#add_field c.cl_path cf.cf_name (self#vtype cf.cf_type) FKMethod in
+								pool#add (ConstMethodHandle(6, offset)),method_handle_sig
+							| _ -> Error.error "Invalid static argument" e.epos
+							in
+							loop (const :: consts) (jsig :: jsigs) static_args
+						| [] ->
+							List.rev consts,List.rev jsigs
+					in
+					let consts,jsigs = loop [] [] el_static_args in
+					let mtl = method_lookup_sig :: string_sig :: method_type_sig :: jsigs in
+					let index = jc#get_bootstrap_method path mname (method_sig mtl (Some call_site_sig)) consts in
+					let jsig_method = method_sig tl tr in
+					let offset_info = pool#add_name_and_type name jsig_method FKMethod in
+					let offset = pool#add (ConstInvokeDynamic(index,offset_info)) in
+					code#invokedynamic offset tl (retype tr);
+					tr
+				| _ ->
+					Error.error "Bad invokedynamic call" e1.epos
+			end
+		| TField(_,FStatic({cl_path = (["java";"lang"],"Math")},{cf_name = ("isNaN" | "isFinite") as name})) ->
+			begin match el with
+			| [e1] ->
+				self#texpr rvalue_any e1;
+				jm#cast TDouble;
+				jm#invokestatic (["java";"lang"],"Double") name (method_sig [TDouble] (Some TBool));
+				Some TBool
+			| _ ->
+				assert false
+			end;
+		| TField(_,FStatic({cl_path = (["java";"lang"],"Math")},{cf_name = ("floor" | "ceil" | "round") as name})) ->
+			begin match el with
+			| [e1] ->
+				self#texpr rvalue_any e1;
+				jm#cast TDouble;
+				let rsig = if name = "round" then TLong else TDouble in
+				jm#invokestatic (["java";"lang"],"Math") name (method_sig [TDouble] (Some rsig));
+				jm#cast TInt;
+				Some TInt
+			| _ ->
+				assert false
+			end;
+		| TField(_,FStatic({cl_path = (["java";"lang"],"Math")} as c,({cf_name = ("ffloor" | "fceil")} as cf))) ->
+			let tl,tr = self#call_arguments cf.cf_type el in
+			jm#invokestatic c.cl_path (String.sub cf.cf_name 1 (String.length cf.cf_name - 1)) (method_sig tl tr);
+			tr
+		| TField(_,FStatic({cl_path = (["haxe";"_Int64"],"Int64_Impl_")},{cf_name = "make"})) ->
+			begin match el with
+			| [{eexpr = TConst (TInt i1)};{eexpr = TConst (TInt i2)}] ->
+				let high = Int64.of_int32 i1 in
+				let high = Int64.shift_left high 32 in
+				let low = Int64.of_int32 i2 in
+				let low = Int64.logand low (Int64.of_string "0xFFFFFFFF") in
+				let i = Int64.logor high low in
+				jm#get_code#lconst i;
+				Some TLong
+			| [e1;e2] ->
+				self#texpr (rvalue_sig TLong) e1;
+				jm#cast TLong;
+				jm#get_code#iconst (Int32.of_int 32);
+				jm#get_code#lshl;
+				self#texpr (rvalue_sig TLong) e2;
+				jm#cast TLong;
+				jm#get_code#lconst (Int64.of_string "0xFFFFFFFF");
+				jm#get_code#land_;
+				jm#get_code#lor_;
+				Some TLong
+			| _ ->
+				assert false
+			end
+		| TIdent "__array__" | TField(_,FStatic({cl_path = (["java"],"NativeArray")},{cf_name = "make"})) ->
+			begin match follow tr with
+			| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
+				let jsig = self#vtype t in
+				self#new_native_array jsig el;
+				Some (array_sig jsig)
+			| _ ->
+				Error.error (Printf.sprintf "Bad __array__ type: %s" (s_type (print_context()) tr)) e1.epos;
+			end
+		| TField(e1,FStatic(c,({cf_kind = Method (MethNormal | MethInline)} as cf))) ->
+			let tl,tr = self#call_arguments cf.cf_type el in
+			jm#invokestatic c.cl_path cf.cf_name (method_sig tl tr);
+			tr
+		| TField(e1,FInstance(c,tl,({cf_kind = Method (MethNormal | MethInline)} as cf))) ->
+			let is_super = match e1.eexpr with
+			| TConst TSuper ->
+				code#aload jc#get_jsig 0;
+				true
+			| _ ->
+				self#texpr rvalue_any e1;
+				false
+			in
+			begin match find_overload_rec false (apply_params c.cl_params tl) c cf el with
+			| None -> Error.error "Could not find overload" e1.epos
+			| Some(c,cf) ->
+				let tl,tr = self#call_arguments cf.cf_type el in
+				(if is_super then jm#invokespecial else if c.cl_interface then jm#invokeinterface else jm#invokevirtual) c.cl_path cf.cf_name (self#vtype cf.cf_type);
+				tr
+			end
+		| TField(_,FEnum(en,ef)) ->
+			let tl,_ = self#call_arguments ef.ef_type el in
+			let tr = self#vtype tr in
+			jm#invokestatic en.e_path ef.ef_name (method_sig tl (Some tr));
+			Some tr
+		| TConst TSuper ->
+			let c,cf = match gctx.current_field_info with
+				| Some ({super_call_fields = hd :: tl} as info) ->
+					info.super_call_fields <- tl;
+					hd
+				| _ ->
+					Error.error "Something went wrong" e1.epos
+			in
+			let kind = get_construction_mode c cf in
+			begin match kind with
+				| ConstructInitPlusNew when jm#get_name = "<init>" ->
+					jm#load_this;
+					jm#get_code#aconst_null haxe_empty_constructor_sig;
+					jm#call_super_ctor ConstructInit (method_sig [haxe_empty_constructor_sig] None);
+				| _ ->
+					()
+			end;
+			jm#load_this;
+			let tl,_ = self#call_arguments cf.cf_type el in
+			jm#call_super_ctor kind (method_sig tl None);
+			None
+		| TIdent "__lock__" ->
+			begin match el with
+				| [e1;e2] ->
+					self#texpr rvalue_any e1;
+					jm#get_code#dup;
+					let _,load,save = jm#add_local "tmp" (self#vtype e1.etype) VarWillInit in
+					save();
+					jm#get_code#monitorenter;
+					let f_exit () =
+						load();
+						jm#get_code#monitorexit;
+					in
+					block_exits <- (ExitExecute f_exit) :: block_exits;
+					let fp_from = jm#get_code#get_fp in
+					self#texpr RVoid e2;
+					let term_try = jm#is_terminated in
+					if not jm#is_terminated then f_exit();
+					let fp_to = jm#get_code#get_fp in
+					let r_try = jm#maybe_make_jump in
+					let fp_target = jm#get_code#get_fp in
+					let pop_scope = jm#push_scope in
+					code#get_stack#push throwable_sig;
+					jm#add_stack_frame;
+					jm#add_exception {
+						exc_start_pc = fp_from;
+						exc_end_pc = fp_to;
+						exc_handler_pc = fp_target;
+						exc_catch_type = None;
+					};
+					begin
+						let _,load,save = jm#add_local "tmp" throwable_sig VarWillInit in
+						save();
+						f_exit();
+						jm#add_exception {
+							exc_start_pc = fp_target;
+							exc_end_pc = jm#get_code#get_fp;
+							exc_handler_pc = fp_target;
+							exc_catch_type = None;
+						};
+						load();
+						jm#get_code#athrow;
+					end;
+					pop_scope();
+					block_exits <- List.tl block_exits;
+					if not term_try then jm#add_stack_frame;
+					jm#close_jumps true ([term_try,r_try]);
+					None
+				| _ -> assert false
+			end
+		| _ ->
+			let rec has_unknown_args jsig =
+				is_dynamic_at_runtime jsig || match jsig with
+					| TMethod(jsigs,_) -> List.exists has_unknown_args jsigs
+					| _ -> false
+			in
+			if has_unknown_args (jsignature_of_type e1.etype) then begin
+				self#texpr rvalue_any e1;
+				jm#cast method_handle_sig;
+				self#new_native_array object_sig el;
+				jm#invokestatic haxe_jvm_path "call" (method_sig [method_handle_sig;array_sig object_sig] (Some object_sig));
+				Some object_sig
+			end else begin
+				self#texpr rvalue_any e1;
+				jm#cast method_handle_sig;
+				let tl,tr = self#call_arguments e1.etype el in
+				jm#invokevirtual method_handle_path "invoke" (method_sig tl tr);
+				tr
+			end
+		in
+		match ret = RVoid,tro with
+		| true,Some _ -> code#pop
+		| true,None -> ()
+		| false,Some _ -> self#cast tr;
+		| false,None -> assert false
+
+	(* exceptions *)
+
+	method throw vt =
+		jm#expect_reference_type;
+		jm#invokestatic (["haxe";"jvm"],"Exception") "wrap" (method_sig [object_sig] (Some exception_sig));
+		code#athrow;
+		jm#set_terminated true
+
+	method try_catch ret e1 catches =
+		let restore = jm#start_branch in
+		let fp_from = code#get_fp in
+		let old_exceptions = caught_exceptions in
+		let excl = List.map (fun (v,e) ->
+			let exc = new haxe_exception gctx v.v_type in
+			caught_exceptions <- exc :: caught_exceptions;
+			exc,v,e
+		) catches in
+		self#texpr ret e1;
+		caught_exceptions <- old_exceptions;
+		let term_try = jm#is_terminated in
+		let r_try = jm#maybe_make_jump in
+		let fp_to = code#get_fp in
+		let unwrap () =
+			code#dup;
+			code#instanceof haxe_exception_path;
+			jm#if_then_else
+				(fun () -> code#if_ref CmpEq)
+				(fun () ->
+					jm#cast haxe_exception_sig;
+					jm#getfield (["haxe";"jvm"],"Exception") "value" object_sig;
+				)
+				(fun () -> jm#cast object_sig);
+		in
+		let start_exception_block path jsig =
+			restore();
+			let fp_target = code#get_fp in
+			let offset = pool#add_path path in
+			jm#add_exception {
+				exc_start_pc = fp_from;
+				exc_end_pc = fp_to;
+				exc_handler_pc = fp_target;
+				exc_catch_type = Some offset;
+			};
+			code#get_stack#push jsig;
+			jm#add_stack_frame;
+			jm#get_code#dup;
+			jm#invokestatic haxe_exception_path "setException" (method_sig [throwable_sig] None);
+		in
+		let run_catch_expr v e =
+			let pop_scope = jm#push_scope in
+			let _,_,store = self#add_local v VarWillInit in
+			store();
+			self#texpr ret e;
+			pop_scope();
+			jm#is_terminated
+		in
+		let add_catch (exc,v,e) =
+			start_exception_block exc#get_native_exception_path exc#get_native_exception_type;
+			if not exc#is_native_exception then begin
+				unwrap();
+				self#cast v.v_type
+			end;
+			let term = run_catch_expr v e in
+			let r = jm#maybe_make_jump in
+			term,r
+		in
+		let commit_instanceof_checks excl =
+			start_exception_block throwable_path throwable_sig;
+			let pop_scope = jm#push_scope in
+			let _,load,save = jm#add_local "exc" throwable_sig VarWillInit in
+			code#dup;
+			save();
+			unwrap();
+			let restore = jm#start_branch in
+			let rl = ref [] in
+			let rec loop excl = match excl with
+				| [] ->
+					code#pop;
+					load();
+					code#athrow;
+					jm#set_terminated true
+				| (_,v,e) :: excl ->
+					code#dup;
+					let path = match self#vtype (self#mknull v.v_type) with TObject(path,_) -> path | _ -> assert false in
+					if path = object_path then begin
+						code#pop;
+						restore();
+						let term = run_catch_expr v e in
+						rl := (term,ref 0) :: !rl;
+					end else begin
+						code#instanceof path;
+						jm#if_then_else
+							(fun () -> code#if_ref CmpEq)
+							(fun () ->
+								restore();
+								self#cast v.v_type;
+								let term = run_catch_expr v e in
+								rl := (term,ref 0) :: !rl;
+							)
+							(fun () -> loop excl)
+					end
+			in
+			loop excl;
+			pop_scope();
+			!rl
+		in
+		let rec loop acc excl = match excl with
+			| (exc,v,e) :: excl ->
+				if List.exists (fun (exc',_,_) -> exc'#is_assignable_to exc) excl || excl = [] && not exc#is_native_exception then begin
+					let res = commit_instanceof_checks ((exc,v,e) :: excl) in
+					acc @ res
+				end else begin
+					let res = add_catch (exc,v,e) in
+					loop (res :: acc) excl
+				end
+			| [] ->
+				acc
+		in
+		let rl = loop [] excl in
+		jm#close_jumps true ((term_try,r_try) :: rl)
+
+	method emit_block_exits is_loop_exit =
+		let rec loop stack = match stack with
+			| [] ->
+				()
+			| ExitLoop :: stack ->
+				if not is_loop_exit then loop stack
+			| ExitExecute f :: stack ->
+				f();
+				loop stack
+		in
+		loop block_exits
+
+	(* texpr *)
+
+	method const ret t ct = match ct with
+		| Type.TInt i32 -> code#iconst i32
+		| TFloat f ->
+			begin match ret with
+			| RValue (Some (TFloat | TObject((["java";"lang"],"Float"),_))) -> code#fconst (float_of_string f)
+			| _ -> code#dconst (float_of_string f)
+			end
+		| TBool true -> code#bconst true
+		| TBool false -> code#bconst false
+		| TNull -> code#aconst_null (self#vtype t)
+		| TThis ->
+			let _,load,_ = self#get_local_by_id (0,"this") in
+			load()
+		| TString s -> jm#string s
+		| TSuper -> failwith "Invalid super access"
+
+	method new_native_array jsig el =
+		jm#new_native_array jsig (List.map (fun e -> fun () -> self#texpr (rvalue_sig jsig) e) el)
+
+	method basic_type_path name =
+		let offset = pool#add_field (["java";"lang"],name) "TYPE" java_class_sig FKField in
+		code#getstatic offset java_class_sig
+
+	method type_expr = function
+		| TByte -> self#basic_type_path "Byte"
+		| TChar -> self#basic_type_path "Character"
+		| TDouble -> self#basic_type_path "Double"
+		| TFloat -> self#basic_type_path "Float"
+		| TInt -> self#basic_type_path "Integer"
+		| TLong -> self#basic_type_path "Long"
+		| TShort -> self#basic_type_path "Short"
+		| TBool -> self#basic_type_path "Boolean"
+		| TObject(path,_) ->
+			let offset = pool#add_path path in
+			let t = object_path_sig path in
+			code#ldc offset (TObject(java_class_path,[TType(WNone,t)]))
+		| TMethod _ ->
+			let offset = pool#add_path method_handle_path in
+			code#ldc offset (TObject(java_class_path,[TType(WNone,method_handle_sig)]))
+		| TTypeParameter _ ->
+			let offset = pool#add_path object_path in
+			code#ldc offset (TObject(java_class_path,[TType(WNone,object_sig)]))
+		| TArray _ as t ->
+			(* TODO: this seems hacky *)
+			let offset = pool#add_path ([],generate_signature false t) in
+			code#ldc offset (TObject(java_class_path,[TType(WNone,object_sig)]))
+		| jsig ->
+			print_endline (generate_signature false jsig);
+			assert false
+
+	method texpr ret e =
+		try
+			if not jm#is_terminated then self#texpr' ret e
+		with Failure s ->
+			raise (HarderFailure (Printf.sprintf "Expr %s\n%s" (s_expr_pretty false "" false (s_type (print_context())) e) s))
+
+	method texpr' ret e =
+		code#set_line (Lexer.get_error_line e.epos);
+		match e.eexpr with
+		| TVar(v,Some e1) ->
+			self#texpr (rvalue_type v.v_type) e1;
+			self#cast v.v_type;
+			let _,_,store = self#add_local v VarWillInit in
+			store()
+		| TVar(v,None) ->
+			ignore(self#add_local v VarNeedDefault);
+		| TLocal _ | TConst _  | TTypeExpr _ when ret = RVoid ->
+			()
+		| TLocal v ->
+			let _,load,_ = self#get_local v in
+			load()
+		| TTypeExpr mt ->
+			self#type_expr (jsignature_of_type (type_of_module_type mt))
+		| TUnop(op,flag,e1) ->
+			begin match op with
+			| Not | Neg | NegBits when ret = RVoid -> self#texpr ret e1
+			| _ -> self#unop ret op flag e1
+			end
+		| TBinop(OpAdd,e1,e2) when ExtType.is_string (follow e.etype) ->
+			let string_builder_path = (["java";"lang"],"StringBuilder") in
+			let string_builder_sig = object_path_sig string_builder_path in
+			jm#construct ConstructInit string_builder_path (fun () -> []);
+			let rec loop e = match e.eexpr with
+				| TBinop(OpAdd,e1,e2) when ExtType.is_string (follow e.etype) ->
+					loop e1;
+					loop e2;
+				| _ ->
+					self#texpr rvalue_any e;
+					let jsig = jm#get_code#get_stack#top in
+					let jsig = match jsig with
+						| TByte | TBool | TChar | TShort | TInt | TLong ->
+							jsig
+						| TFloat | TDouble ->
+							(* Haxe mandates 2.0 be printed as 2, so we have to wrap here... *)
+							jm#expect_reference_type;
+							jm#invokestatic haxe_jvm_path "toString" (method_sig [object_sig] (Some string_sig));
+							string_sig
+						| _ ->
+							object_sig
+					in
+					jm#invokevirtual string_builder_path "append" (method_sig [jsig] (Some string_builder_sig));
+			in
+			loop e1;
+			loop e2;
+			jm#invokevirtual string_builder_path "toString" (method_sig [] (Some string_sig));
+		| TBinop(op,e1,e2) ->
+			begin match op with
+			| OpAssign | OpAssignOp _ -> self#binop ret op e1 e2
+			| _ when ret = RVoid ->
+				self#texpr ret e1;
+				self#texpr ret e2;
+			| _ ->
+				self#binop ret op e1 e2
+			end
+		| TConst ct ->
+			self#const ret e.etype ct
+		| TIf(e1,e2,None) ->
+			jm#if_then
+				(self#apply_cmp (self#condition e1))
+				(fun () -> self#texpr RVoid (mk_block e2))
+		| TIf(e1,e2,Some e3) ->
+			jm#if_then_else
+				(self#apply_cmp (self#condition e1))
+				(fun () ->
+					self#texpr ret (mk_block e2);
+					if ret <> RVoid then self#cast e.etype
+				)
+				(fun () ->
+					self#texpr ret (mk_block e3);
+					if ret <> RVoid then self#cast e.etype;
+				)
+		| TSwitch(e1,cases,def) ->
+			self#switch ret e1 cases def
+		| TWhile(e1,e2,flag) -> (* TODO: do-while *)
+			(* TODO: could optimize a bit *)
+			block_exits <- ExitLoop :: block_exits;
+			let is_true_loop = match (Texpr.skip e1).eexpr with TConst (TBool true) -> true | _ -> false in
+			jm#add_stack_frame;
+			let fp = code#get_fp in
+			let old_continue = continue in
+			continue <- fp;
+			let old_breaks = breaks in
+			breaks <- [];
+			let restore = jm#start_branch in
+			let jump_then = if not is_true_loop then self#apply_cmp (self#condition e1) () else ref 0 in
+			let pop_scope = jm#push_scope in
+			self#texpr RVoid e2;
+			if not jm#is_terminated then code#goto (ref (fp - code#get_fp));
+			pop_scope();
+			restore();
+			if not is_true_loop || breaks <> [] then begin
+				jump_then := code#get_fp - !jump_then;
+				let fp' = code#get_fp in
+				List.iter (fun r -> r := fp' - !r) breaks;
+				jm#add_stack_frame
+			end else
+				jm#set_terminated true;
+			continue <- old_continue;
+			breaks <- old_breaks;
+			block_exits <- List.tl block_exits;
+		| TBreak ->
+			self#emit_block_exits true;
+			let r = ref (code#get_fp) in
+			code#goto r;
+			breaks <- r :: breaks;
+			jm#set_terminated true;
+		| TContinue ->
+			self#emit_block_exits true;
+			code#goto (ref (continue - code#get_fp));
+			jm#set_terminated true;
+		| TTry(e1,catches) ->
+			self#try_catch ret e1 catches
+		| TField(e1,fa) ->
+			if ret = RVoid then self#texpr ret e1
+			else self#read (fun () -> self#cast_expect ret e.etype) e1 fa;
+		| TCall(e1,el) ->
+			self#call ret e.etype e1 el
+		| TNew({cl_path = (["java"],"NativeArray")},[t],[e1]) ->
+			self#texpr (match ret with RVoid -> RVoid | _ -> rvalue_any) e1;
+			(* Technically this could throw... but whatever *)
+			if ret <> RVoid then ignore(NativeArray.create jm#get_code jc#get_pool (jsignature_of_type t))
+		| TNew(c,tl,el) ->
+			begin match get_constructor (fun cf -> cf.cf_type) c with
+			|_,cf ->
+				begin match find_overload_rec true (apply_params c.cl_params tl) c cf el with
+				| None -> Error.error "Could not find overload" e.epos
+				| Some (c',cf) ->
+					let f () =
+						let tl,_ = self#call_arguments  cf.cf_type el in
+						tl
+					in
+					jm#construct ~no_value:(if ret = RVoid then true else false) (get_construction_mode c' cf) c.cl_path f
+				end
+			end
+		| TReturn None ->
+			self#emit_block_exits false;
+			code#return_void;
+			jm#set_terminated true;
+		| TReturn (Some e1) ->
+			self#texpr rvalue_any e1;
+			self#cast return_type;
+			let vt = self#vtype return_type in
+			self#emit_block_exits false;
+			code#return_value vt;
+			jm#set_terminated true;
+		| TFunction tf ->
+			begin match self#tfunction e tf with
+			| None ->
+				()
+			| Some ctx_class ->
+				begin match ctx_class#get_args with
+				| [(arg,jsig)] ->
+					let _,load,_ = self#get_local_by_id arg in
+					load();
+					self#expect_reference_type;
+					jm#invokevirtual method_handle_path "bindTo" (method_sig [object_sig] (Some method_handle_sig));
+				| args ->
+					let f () =
+						let tl = List.map (fun (arg,jsig) ->
+							let _,load,_ = self#get_local_by_id arg in
+							load();
+							jm#cast jsig;
+							jsig
+						) args in
+						tl
+					in
+					jm#construct ConstructInit ctx_class#get_path f;
+					jm#invokevirtual method_handle_path "bindTo" (method_sig [object_sig] (Some method_handle_sig));
+				end
+			end
+		| TArrayDecl el when ret = RVoid ->
+			List.iter (self#texpr ret) el
+		| TArrayDecl el ->
+			begin match follow e.etype with
+			| TInst({cl_path = ([],"Array")},[t]) ->
+				self#new_native_array (jsignature_of_type (self#mknull t)) el;
+				jm#invokestatic ([],"Array") "ofNative" (method_sig [array_sig object_sig] (Some (object_path_sig ([],"Array"))));
+				self#cast e.etype
+			| _ ->
+				assert false
+			end
+		| TArray(e1,e2) when ret = RVoid ->
+			(* Array access never throws so this should be fine... *)
+			self#texpr ret e1;
+			self#texpr ret e2;
+		| TArray(e1,e2) ->
+			begin match follow e1.etype with
+			| TInst({cl_path = ([],"Array")} as c,[t]) ->
+				self#texpr rvalue_any e1;
+				self#texpr (rvalue_sig TInt) e2;
+				jm#cast TInt;
+				jm#invokevirtual c.cl_path "__get" (method_sig [TInt] (Some object_sig));
+				self#cast e.etype
+			| TInst({cl_path = (["java"],"NativeArray")},[t]) ->
+				self#texpr rvalue_any e1;
+				let vt = self#vtype e1.etype in
+				let vte = self#vtype t in
+				self#texpr rvalue_any e2;
+				self#read_native_array vt vte
+			| t ->
+				self#texpr rvalue_any e1;
+				self#texpr rvalue_any e2;
+				jm#cast TInt;
+				jm#invokestatic (["haxe";"jvm"],"Jvm") "arrayRead" (method_sig [object_sig;TInt] (Some object_sig));
+				self#cast e.etype;
+			end
+		| TBlock [] ->
+			if ret = RReturn && not jm#is_terminated then code#return_void;
+		| TBlock el ->
+			let rec loop el = match el with
+				| [] -> assert false
+				| [e1] ->
+					self#texpr (if ret = RReturn then RVoid else ret) e1;
+					if ret = RReturn && not jm#is_terminated then code#return_void;
+				| e1 :: el ->
+					self#texpr RVoid e1;
+					loop el
+			in
+			let pop_scope = jm#push_scope in
+			loop el;
+			pop_scope();
+		| TCast(e1,None) ->
+			self#texpr ret e1;
+			if ret <> RVoid then self#cast e.etype
+		| TCast(e1,Some mt) ->
+			self#texpr rvalue_any e1;
+			let jsig = jsignature_of_type (type_of_module_type mt) in
+			if is_unboxed jsig || is_unboxed jm#get_code#get_stack#top then jm#cast jsig
+			else code#checkcast (t_infos mt).mt_path;
+			if ret = RVoid then code#pop;
+		| TParenthesis e1 | TMeta(_,e1) ->
+			self#texpr ret e1
+		| TFor(v,e1,e2) ->
+			self#texpr ret (Texpr.for_remap com.basic v e1 e2 e.epos)
+		| TEnumIndex e1 ->
+			self#texpr rvalue_any e1;
+			jm#invokevirtual java_enum_path "ordinal" (method_sig [] (Some TInt))
+		| TEnumParameter(e1,ef,i) ->
+			self#texpr rvalue_any e1;
+			let path,name,jsig_arg = match follow ef.ef_type with
+				| TFun(tl,TEnum(en,_)) ->
+					let n,_,t = List.nth tl i in
+					en.e_path,n,self#vtype t
+				| _ -> assert false
+			in
+			let cpath = ((fst path),Printf.sprintf "%s$%s" (snd path) ef.ef_name) in
+			let jsig = (object_path_sig cpath) in
+			jm#cast jsig;
+			jm#getfield cpath name jsig_arg;
+			self#cast e.etype;
+		| TThrow e1 ->
+			self#texpr rvalue_any e1;
+			let exc = new haxe_exception gctx e1.etype in
+			if not (List.exists (fun exc' -> exc#is_assignable_to exc') caught_exceptions) then jm#add_thrown_exception exc#get_native_exception_path;
+			if not exc#is_native_exception then begin
+				let vt = self#vtype (self#mknull e1.etype) in
+				self#throw vt
+			end else begin
+				code#athrow;
+				jm#set_terminated true
+			end
+		| TObjectDecl fl ->
+			begin match follow e.etype with
+			(* The guard is here because in the case of quoted fields like `"a-b"`, the field is not part of the
+			   type. In this case we have to do full dynamic construction. *)
+			| TAnon an when List.for_all (fun ((name,_,_),_) -> PMap.mem name an.a_fields) fl ->
+				let path,fl' = TAnonIdentifiaction.identify gctx an.a_fields in
+				jm#construct ConstructInit path (fun () ->
+					(* We have to respect declaration order, so let's temp var where necessary *)
+					let rec loop fl fl' ok acc = match fl,fl' with
+						| ((name,_,_),e) :: fl,(name',jsig) :: fl' ->
+							if ok && name = name' then begin
+								self#texpr rvalue_any e;
+								jm#cast jsig;
+								loop fl fl' ok acc
+							end else begin
+								let load = match (Texpr.skip e).eexpr with
+								| TConst _ | TTypeExpr _ | TFunction _ ->
+									(fun () -> self#texpr rvalue_any e)
+								| _ ->
+									let _,load,save = jm#add_local (Printf.sprintf "_hx_tmp_%s" name) (self#vtype e.etype) VarWillInit in
+									self#texpr rvalue_any e;
+									save();
+									load
+								in
+								loop fl fl' false ((name,load) :: acc)
+							end
+						| [],[] ->
+							acc
+						| (_,e) :: fl,[] ->
+							self#texpr RVoid e;
+							loop fl fl' ok acc
+						| [],(_,jsig) :: fl' ->
+							jm#load_default_value jsig;
+							loop [] fl' ok acc
+					in
+					let vars = loop fl fl' true [] in
+					let vars = List.sort (fun (name1,_) (name2,_) -> compare name1 name2) vars in
+					List.iter (fun (_,load) ->
+						load();
+					) vars;
+					List.map snd fl';
+				)
+			| _ ->
+				jm#construct ConstructInit haxe_dynamic_object_path (fun () -> []);
+				List.iter (fun ((name,_,_),e) ->
+					code#dup;
+					jm#string name;
+					self#texpr rvalue_any e;
+					self#expect_reference_type;
+					jm#invokevirtual haxe_dynamic_object_path "_hx_setField" (method_sig [string_sig;object_sig] None);
+				) fl;
+			end
+		| TIdent _ ->
+			Error.error (s_expr_ast false "" (s_type (print_context())) e) e.epos;
+
+	(* api *)
+
+	method object_constructor =
+		let path = jc#get_super_path in
+		let offset = pool#add_field path "<init>" (method_sig [] None) FKMethod in
+		jm#load_this;
+		code#invokespecial offset jc#get_jsig [] [];
+		jm#set_this_initialized
+end
+
+type super_ctor_mode =
+	| SCNone
+	| SCJava
+	| SCHaxe
+
+let failsafe p f =
+	try
+		f ()
+	with Failure s | HarderFailure s ->
+		Error.error s p
+
+let generate_dynamic_access gctx (jc : JvmClass.builder) fields is_anon =
+	begin match fields with
+	| [] ->
+		()
+	| _ ->
+		let jsig = method_sig [string_sig] (Some object_sig) in
+		let jm = jc#spawn_method "_hx_getField" jsig [MPublic;MSynthetic] in
+		let _,load,_ = jm#add_local "name" string_sig VarArgument in
+		jm#finalize_arguments;
+		load();
+		jm#invokevirtual string_path "hashCode" (method_sig [] (Some TInt));
+		let cases = List.map (fun (name,jsig,kind) ->
+			let hash = java_hash name in
+			[hash],(fun () ->
+				begin match kind with
+					| Method (MethNormal | MethInline) ->
+						jm#read_closure false jc#get_this_path name jsig;
+						jm#load_this;
+						jm#invokevirtual method_handle_path "bindTo" (method_sig [object_sig] (Some method_handle_sig));
+					| _ ->
+						jm#load_this;
+						jm#getfield jc#get_this_path name jsig;
+						jm#expect_reference_type;
+				end;
+				ignore(jm#get_code#get_stack#pop);
+				jm#get_code#get_stack#push object_sig;
+			)
+		) fields in
+		let def = (fun () ->
+			jm#load_this;
+			load();
+			jm#invokespecial jc#get_super_path "_hx_getField" jsig;
+		) in
+		jm#int_switch false cases (Some def);
+		jm#return
+	end;
+	let fields = List.filter (fun (_,_,kind) -> match kind with
+		| Method (MethNormal | MethInline) -> false
+		| Var {v_write = AccNever} -> false
+		| _ -> true
+	) fields in
+	begin match fields with
+	| [] ->
+		()
+	| _ ->
+		let jsig = method_sig [string_sig;object_sig] None in
+		let jm = jc#spawn_method "_hx_setField" jsig [MPublic;MSynthetic] in
+		let _,load1,_ = jm#add_local "name" string_sig VarArgument in
+		let _,load2,_ = jm#add_local "value" object_sig VarArgument in
+		jm#finalize_arguments;
+		load1();
+		jm#invokevirtual string_path "hashCode" (method_sig [] (Some TInt));
+		let def = (fun () ->
+			jm#load_this;
+			load1();
+			load2();
+			jm#invokespecial jc#get_super_path "_hx_setField" jsig;
+		) in
+		let cases = List.map (fun (name,jsig,_) ->
+			let hash = java_hash name in
+			[hash],(fun () ->
+				jm#load_this;
+				load2();
+				jm#cast jsig;
+				jm#putfield jc#get_this_path name jsig;
+				(* Have to deal with Reflect.deleteField crap here... *)
+				if is_anon then begin
+					jm#load_this;
+					jm#getfield jc#get_this_path "_hx_deletedAField" boolean_sig;
+					jm#if_then
+						(fun () -> jm#get_code#if_null_ref boolean_sig)
+						(fun () ->
+							def();
+						)
+				end;
+			)
+		) fields in
+		jm#int_switch false cases (Some def);
+		jm#return
+	end
+
+class tclass_to_jvm gctx c = object(self)
+	val is_annotation = Meta.has Meta.Annotation c.cl_meta
+	val field_inits = DynArray.create ()
+	val delayed_field_inits = DynArray.create ()
+
+	val jc = new JvmClass.builder c.cl_path (match c.cl_super with
+		| Some(c,_) -> c.cl_path
+		| None ->
+			if c.cl_interface || Meta.has Meta.NativeGen c.cl_meta then object_path else haxe_object_path
+		)
+
+	method private set_access_flags =
+		jc#add_access_flag 1; (* public *)
+		if c.cl_final then jc#add_access_flag 0x10;
+		if c.cl_interface then begin
+			jc#add_access_flag 0x200;
+			jc#add_access_flag 0x400;
+		end;
+		if is_annotation then begin
+			jc#add_access_flag 0x2000;
+			jc#add_interface (["java";"lang";"annotation"],"Annotation");
+			(* TODO: this should be done via Haxe metadata instead of hardcoding it here *)
+			jc#add_annotation retention_path ["value",(AEnum(retention_policy_sig,"RUNTIME"))];
+		end;
+		if c.cl_path = (["haxe";"jvm"],"Enum") then jc#add_access_flag 0x4000; (* enum *)
+
+	method private handle_relation_type_params =
+		let map_type_params t =
+			let has_type_param = ref false in
+			let rec loop t = match follow t with
+				| TInst({cl_kind = KTypeParameter tl},_) ->
+					has_type_param := true;
+					begin match tl with
+					| [t] -> t
+					| _ -> t_dynamic
+					end
+				| _ -> Type.map loop t
+			in
+			let t = match follow t with
+				| TFun(tl,tr) ->
+					let tl = List.map (fun (n,o,t) -> n,o,loop t) tl in
+					let tr = loop tr in
+					TFun(tl,tr)
+				| _ ->
+					assert false
+			in
+			if !has_type_param then Some t else None
+		in
+		let make_bridge cf cf_impl t =
+			let jsig = jsignature_of_type t in
+			if not (jc#has_method cf.cf_name jsig) then begin
+				begin match follow t with
+				| TFun(tl,tr) ->
+					let jm = jc#spawn_method cf.cf_name jsig [MPublic;MSynthetic;MBridge] in
+					jm#load_this;
+					let jsig_impl = jsignature_of_type cf_impl.cf_type in
+					let jsigs,_ = match jsig_impl with TMethod(jsigs,jsig) -> jsigs,jsig | _ -> assert false in
+					List.iter2 (fun (n,_,t) jsig ->
+						let _,load,_ = jm#add_local n (jsignature_of_type t) VarArgument in
+						load();
+						jm#cast jsig;
+					) tl jsigs;
+					jm#invokevirtual c.cl_path cf.cf_name jsig_impl;
+					if not (ExtType.is_void (follow tr)) then jm#cast (jsignature_of_type tr);
+					jm#return;
+				| _ ->
+					()
+				end
+			end
+		in
+		let check cf cf_impl map_type =
+			match cf.cf_kind with
+			| Method (MethNormal | MethInline) ->
+				begin match map_type_params cf.cf_type with
+				| Some t -> make_bridge cf cf_impl t
+				| None -> ()
+				end
+			| _ ->
+				()
+		in
+		let check cf cf_impl map_type =
+			check cf cf_impl map_type;
+			List.iter (fun cf -> check cf cf_impl map_type) cf.cf_overloads
+		in
+		let rec loop map_type c_int =
+			List.iter (fun (c_int,tl) ->
+				let map_type t = apply_params c_int.cl_params tl (map_type t) in
+				List.iter (fun cf ->
+					match raw_class_field (fun cf -> map_type cf.cf_type) c (List.map snd c.cl_params) cf.cf_name with
+					| Some(c',_),_,cf_impl when c' == c -> check cf cf_impl map_type
+					| _ -> ()
+				) c_int.cl_ordered_fields;
+				loop map_type c_int
+			) c_int.cl_implements
+		in
+		loop (fun t -> t) c;
+		begin match c.cl_overrides,c.cl_super with
+		| [],_ ->
+			()
+		| fields,Some(c_sup,tl) ->
+			List.iter (fun cf_impl ->
+				match raw_class_field (fun cf -> apply_params c_sup.cl_params tl cf.cf_type) c_sup tl cf_impl.cf_name with
+				| Some(c,tl),_,cf -> check cf cf_impl (apply_params c.cl_params tl)
+				| _ -> assert false
+			) fields
+		| _ ->
+			assert false
+		end
+
+	method private set_interfaces =
+		List.iter (fun (c_int,tl) ->
+			if is_annotation && c_int.cl_path = (["java";"lang";"annotation"],"Annotation") then
+				()
+			else begin
+				jc#add_interface c_int.cl_path
+			end
+		) c.cl_implements
+
+	method private generate_empty_ctor =
+		let jsig_empty = method_sig [haxe_empty_constructor_sig] None in
+		let jm_empty_ctor = jc#spawn_method "<init>" jsig_empty [MPublic] in
+		let _,load,_ = jm_empty_ctor#add_local "_" haxe_empty_constructor_sig VarArgument in
+		jm_empty_ctor#load_this;
+		if c.cl_constructor = None then begin
+			let handler = new texpr_to_jvm gctx jc jm_empty_ctor gctx.com.basic.tvoid in
+			DynArray.iter (fun e ->
+				handler#texpr RVoid e;
+			) field_inits;
+		end;
+		begin match c.cl_super with
+		| None ->
+			(* Haxe type with no parent class, call Object.<init>() *)
+			jm_empty_ctor#call_super_ctor ConstructInit (method_sig [] None)
+		| _ ->
+			(* Parent class exists, call SuperClass.<init>(EmptyConstructor) *)
+			load();
+			jm_empty_ctor#call_super_ctor ConstructInit jsig_empty
+		end;
+		if c.cl_constructor = None then begin
+			let handler = new texpr_to_jvm gctx jc jm_empty_ctor gctx.com.basic.tvoid in
+			DynArray.iter (fun e ->
+				handler#texpr RVoid e;
+			) delayed_field_inits;
+		end;
+		jm_empty_ctor#get_code#return_void;
+
+	method private generate_implicit_ctors =
+		try
+			let sm = Hashtbl.find gctx.implicit_ctors c.cl_path in
+			PMap.iter (fun _ (c,cf) ->
+				let cmode = get_construction_mode c cf in
+				let jm = jc#spawn_method (if cmode = ConstructInit then "<init>" else "new") (jsignature_of_type cf.cf_type) [MPublic] in
+				let handler = new texpr_to_jvm gctx jc jm gctx.com.basic.tvoid in
+				jm#load_this;
+				DynArray.iter (fun e ->
+					handler#texpr RVoid e;
+				) field_inits;
+				let tl = match follow cf.cf_type with TFun(tl,_) -> tl | _ -> assert false in
+				List.iter (fun (n,_,t) ->
+					let _,load,_ = jm#add_local n (jsignature_of_type t) VarArgument in
+					load();
+				) tl;
+				jm#call_super_ctor cmode jm#get_jsig;
+				DynArray.iter (fun e ->
+					handler#texpr RVoid e;
+				) delayed_field_inits;
+				jm#return
+			) sm
+		with Not_found ->
+			()
+
+	method generate_expr gctx jc jm e is_main is_method scmode mtype =
+		let e,args,tr = match e.eexpr with
+			| TFunction tf when is_method ->
+				tf.tf_expr,tf.tf_args,tf.tf_type
+			| _ ->
+				e,[],t_dynamic
+		in
+		let handler = new texpr_to_jvm gctx jc jm tr in
+		if is_main then begin
+			let _,load,_ = jm#add_local "args" (TArray(string_sig,None)) VarArgument in
+			if has_feature gctx.com "Sys.args" then begin
+				load();
+				jm#putstatic ([],"Sys") "_args" (TArray(string_sig,None))
+			end
+		end;
+		List.iter (fun (v,_) ->
+			ignore(handler#add_local v VarArgument);
+		) args;
+		jm#finalize_arguments;
+		begin match mtype with
+		| MConstructor ->
+			DynArray.iter (fun e ->
+				handler#texpr RVoid e;
+			) field_inits;
+			begin match scmode with
+			| SCJava ->
+				handler#object_constructor
+			| SCHaxe ->
+				jm#load_this;
+				jm#get_code#aconst_null jc#get_jsig;
+				jm#call_super_ctor ConstructInit (method_sig [haxe_empty_constructor_sig] None);
+			| SCNone ->
+				()
+			end;
+			DynArray.iter (fun e ->
+				handler#texpr RVoid e;
+			) delayed_field_inits;
+		| _ ->
+			()
+		end;
+			handler#texpr RReturn e
+
+	method generate_method gctx jc c mtype cf =
+		gctx.current_field_info <- get_field_info gctx cf.cf_meta;
+		let jsig = if cf.cf_name = "main" then
+			method_sig [array_sig string_sig] None
+		else
+			jsignature_of_type cf.cf_type
+		in
+		let flags = [MPublic] in
+		let flags = if c.cl_interface then MAbstract :: flags else flags in
+		let flags = if mtype = MStatic then MethodAccessFlags.MStatic :: flags else flags in
+		let flags = if has_class_field_flag cf CfFinal then MFinal :: flags else flags in
+		let name,scmode,flags = match mtype with
+			| MConstructor ->
+				let rec has_super_ctor c = match c.cl_super with
+					| None -> false
+					| Some(c,_) -> c.cl_constructor <> None || has_super_ctor c
+				in
+				let get_scmode () = if c.cl_super = None then SCJava else if not (has_super_ctor c) then SCHaxe else SCNone in
+				if get_construction_mode c cf = ConstructInit then "<init>",get_scmode(),flags
+				else cf.cf_name,SCNone,flags
+			| _ -> cf.cf_name,SCNone,flags
+		in
+		let jm = jc#spawn_method name jsig flags in
+		begin match cf.cf_expr with
+		| None -> ()
+		| Some e ->
+			self#generate_expr gctx jc jm e (cf.cf_name = "main") true scmode mtype;
+		end;
+		begin match cf.cf_params with
+			| [] when c.cl_params = [] ->
+				()
+			| _ ->
+				let stl = String.concat "" (List.map (fun (n,_) ->
+					Printf.sprintf "%s:Ljava/lang/Object;" n
+				) cf.cf_params) in
+				let ssig = generate_method_signature true (jsignature_of_type cf.cf_type) in
+				let s = if cf.cf_params = [] then ssig else Printf.sprintf "<%s>%s" stl ssig in
+				let offset = jc#get_pool#add_string s in
+				jm#add_attribute (AttributeSignature offset);
+		end;
+		AnnotationHandler.generate_annotations (jm :> JvmBuilder.base_builder) cf.cf_meta;
+
+	method generate_field gctx (jc : JvmClass.builder) c mtype cf =
+		let jsig = jsignature_of_type cf.cf_type in
+		let flags = [FdPublic] in
+		let flags = if mtype = MStatic then FdStatic :: flags else flags in
+		let jm = jc#spawn_field cf.cf_name jsig flags in
+		let default e =
+			let p = null_pos in
+			let efield = Texpr.Builder.make_static_field c cf p in
+			let eop = mk (TBinop(OpAssign,efield,e)) cf.cf_type p in
+			begin match c.cl_init with
+			| None -> c.cl_init <- Some eop
+			| Some e -> c.cl_init <- Some (concat e eop)
+			end
+		in
+		begin match cf.cf_expr with
+			| None ->
+				if c.cl_path = (["haxe"],"Resource") && cf.cf_name = "content" then begin
+					let el = Hashtbl.fold (fun name _ acc ->
+						Texpr.Builder.make_string gctx.com.basic name null_pos :: acc
+					) gctx.com.resources [] in
+					let e = mk (TArrayDecl el) (gctx.com.basic.tarray gctx.com.basic.tstring) null_pos in
+					default e;
+				end;
+			| Some e when mtype <> MStatic ->
+				let tl = List.map snd c.cl_params in
+				let ethis = mk (TConst TThis) (TInst(c,tl)) null_pos in
+				let efield = mk (TField(ethis,FInstance(c,tl,cf))) cf.cf_type null_pos in
+				let eop = mk (TBinop(OpAssign,efield,e)) cf.cf_type null_pos in
+				DynArray.add (match cf.cf_kind with Method MethDynamic -> delayed_field_inits | _ -> field_inits) eop;
+			| Some e ->
+				match e.eexpr with
+				| TConst ct ->
+					begin match ct with
+					| TInt i32 when not (is_nullable cf.cf_type) ->
+						let offset = jc#get_pool#add (ConstInt i32) in
+						jm#add_attribute (AttributeConstantValue offset);
+					| TString s ->
+						let offset = jc#get_pool#add_const_string s in
+						jm#add_attribute (AttributeConstantValue offset);
+					| _ ->
+						default e;
+					end
+				| _ ->
+					default e;
+		end;
+		let ssig = generate_signature true (jsignature_of_type cf.cf_type) in
+		let offset = jc#get_pool#add_string ssig in
+		jm#add_attribute (AttributeSignature offset)
+
+		method private generate_fields =
+			let field mtype cf = match cf.cf_kind with
+				| Method (MethNormal | MethInline) ->
+					List.iter (fun cf ->
+						failsafe cf.cf_pos (fun () -> self#generate_method gctx jc c mtype cf)
+					) (cf :: List.filter (fun cf -> Meta.has Meta.Overload cf.cf_meta) cf.cf_overloads)
+				| _ ->
+					if not c.cl_interface && is_physical_field cf then failsafe cf.cf_pos (fun () -> self#generate_field gctx jc c mtype cf)
+			in
+			List.iter (field MStatic) c.cl_ordered_statics;
+			List.iter (field MInstance) c.cl_ordered_fields;
+			begin match c.cl_constructor,c.cl_super with
+				| Some cf,Some _ -> field MConstructor cf
+				| Some cf,None -> field MConstructor cf
+				| None,_ -> ()
+			end;
+			begin match c.cl_init with
+				| None ->
+					()
+				| Some e ->
+					let cf = mk_field "<clinit>" (tfun [] gctx.com.basic.tvoid) null_pos null_pos in
+					cf.cf_kind <- Method MethNormal;
+					let tf = {
+						tf_args = [];
+						tf_type = gctx.com.basic.tvoid;
+						tf_expr = mk_block e;
+					} in
+					let e = mk (TFunction tf) cf.cf_type null_pos in
+					cf.cf_expr <- Some e;
+					field MStatic cf
+			end
+
+	method private generate_signature =
+			let stl = match c.cl_params with
+				| [] -> ""
+				| params ->
+					let stl = String.concat "" (List.map (fun (n,_) ->
+						Printf.sprintf "%s:Ljava/lang/Object;" n
+					) c.cl_params) in
+					Printf.sprintf "<%s>" stl
+			in
+			let ssuper = match c.cl_super with
+				| Some(c,tl) -> generate_method_signature true (jsignature_of_type (TInst(c,tl)))
+				| None -> generate_method_signature true object_sig
+			in
+			let sinterfaces = String.concat "" (List.map (fun(c,tl) ->
+				generate_method_signature true (jsignature_of_type (TInst(c,tl)))
+			) c.cl_implements) in
+			let s = Printf.sprintf "%s%s%s" stl ssuper sinterfaces in
+			let offset = jc#get_pool#add_string s in
+			jc#add_attribute (AttributeSignature offset)
+
+	method generate_annotations =
+		AnnotationHandler.generate_annotations (jc :> JvmBuilder.base_builder) c.cl_meta;
+		jc#add_annotation (["haxe";"jvm";"annotation"],"ClassReflectionInformation") (["hasSuperClass",(ABool (c.cl_super <> None))])
+
+	method generate =
+		self#set_access_flags;
+		self#generate_fields;
+		self#set_interfaces;
+		if not c.cl_interface then begin
+			self#generate_empty_ctor;
+			self#generate_implicit_ctors;
+			self#handle_relation_type_params;
+		end;
+		self#generate_signature;
+		if not (Meta.has Meta.NativeGen c.cl_meta) then
+			generate_dynamic_access gctx jc (List.map (fun cf -> cf.cf_name,jsignature_of_type cf.cf_type,cf.cf_kind) c.cl_ordered_fields) false;
+		self#generate_annotations;
+		jc#add_attribute (AttributeSourceFile (jc#get_pool#add_string c.cl_pos.pfile));
+		let jc = jc#export_class gctx.default_export_config in
+		write_class gctx.jar c.cl_path jc
+end
+
+let generate_class gctx c =
+	let conv = new tclass_to_jvm gctx c in
+	conv#generate
+
+let generate_enum gctx en =
+	let jc_enum = new JvmClass.builder en.e_path haxe_enum_path in
+	jc_enum#add_access_flag 0x1; (* public *)
+	jc_enum#add_access_flag 0x400; (* abstract *)
+	jc_enum#add_access_flag 0x4000; (* enum *)
+	begin
+		let jsig = haxe_enum_sig (object_path_sig en.e_path) in
+		let s = generate_signature true jsig in
+		let offset = jc_enum#get_pool#add_string s in
+		jc_enum#add_attribute (AttributeSignature offset)
+	end;
+	let jsig_enum_ctor = method_sig [TInt;string_sig] None in
+	(* Create base constructor *)
+	 begin
+		let jm_ctor = jc_enum#spawn_method "<init>" jsig_enum_ctor [MProtected] in
+		jm_ctor#load_this;
+		let _,load1,_ = jm_ctor#add_local "index" TInt VarArgument in
+		let _,load2,_ = jm_ctor#add_local "name" string_sig VarArgument in
+		load1();
+		load2();
+		jm_ctor#call_super_ctor ConstructInit jsig_enum_ctor;
+		jm_ctor#get_code#return_void;
+	end;
+	let inits = DynArray.create () in
+	let names = List.map (fun name ->
+		let ef = PMap.find name en.e_constrs in
+		let args = match follow ef.ef_type with
+			| TFun(tl,_) -> List.map (fun (n,_,t) -> n,jsignature_of_type t) tl
+			| _ -> []
+		in
+		let jsigs = List.map snd args in
+		(* Create class for constructor *)
+		let jc_ctor = begin
+			let jc_ctor = jc_enum#spawn_inner_class None jc_enum#get_this_path (Some ef.ef_name) in
+			jc_ctor#add_access_flag 0x10; (* final *)
+			jc_ctor#add_access_flag 0x4000; (* enum *)
+			let jsig_method = method_sig jsigs None in
+			let jm_ctor = jc_ctor#spawn_method "<init>" jsig_method [MPublic] in
+			jm_ctor#load_this;
+			jm_ctor#get_code#iconst (Int32.of_int ef.ef_index);
+			jm_ctor#string ef.ef_name;
+			jm_ctor#call_super_ctor ConstructInit jsig_enum_ctor;
+			List.iter (fun (n,jsig) ->
+				jm_ctor#add_argument_and_field n jsig
+			) args;
+			jm_ctor#get_code#return_void;
+			jc_ctor#add_annotation (["haxe";"jvm";"annotation"],"EnumValueReflectionInformation") (["argumentNames",AArray (List.map (fun (name,_) -> AString name) args)]);
+			if args <> [] then begin
+				let jm_params = jc_ctor#spawn_method "_hx_getParameters" (method_sig [] (Some (array_sig object_sig))) [MPublic] in
+				let fl = List.map (fun (n,jsig) ->
+					(fun () ->
+						jm_params#load_this;
+						jm_params#getfield jc_ctor#get_this_path n jsig;
+						jm_params#cast object_sig;
+					)
+				) args in
+				jm_params#new_native_array object_sig fl;
+				jm_params#return
+			end;
+			jc_ctor
+		end in
+		write_class gctx.jar jc_ctor#get_this_path (jc_ctor#export_class gctx.default_export_config);
+		begin match args with
+			| [] ->
+				(* Create static field for ctor without args *)
+				let jm_static = jc_enum#spawn_field ef.ef_name jc_enum#get_jsig [FdPublic;FdStatic;FdFinal;FdEnum] in
+				DynArray.add inits (jm_static,jc_ctor);
+			| _ ->
+				(* Create static function for ctor with args *)
+				let jsig_static = method_sig jsigs (Some jc_enum#get_jsig) in
+				let jm_static = jc_enum#spawn_method ef.ef_name jsig_static [MPublic;MStatic] in
+				jm_static#construct ConstructInit jc_ctor#get_this_path (fun () ->
+					List.iter (fun (n,jsig) ->
+						let _,load,_ = jm_static#add_local n jsig VarArgument in
+						load();
+					) args;
+					jsigs;
+				);
+				jm_static#get_code#return_value jc_enum#get_jsig;
+		end;
+		AString name
+	) en.e_names in
+	if DynArray.length inits > 0 then begin
+		(* Assign static fields for ctors without args *)
+		let jm_clinit = jc_enum#spawn_method "<clinit>" (method_sig [] None) [MStatic] in
+		let jm_values = jc_enum#spawn_method "values" (method_sig [] (Some (array_sig (object_path_sig jc_enum#get_this_path)))) [MPublic;MStatic] in
+		let inits = DynArray.to_list inits in
+		let fl = List.map (fun (jm_static,jc_ctor) ->
+			jm_clinit#construct ConstructInit jc_ctor#get_this_path (fun () -> []);
+			jm_clinit#putstatic jc_enum#get_this_path jm_static#get_name jm_static#get_jsig;
+			(fun () ->
+				jm_values#getstatic jc_enum#get_this_path jm_static#get_name jm_static#get_jsig;
+			)
+		) inits in
+		jm_values#new_native_array (object_path_sig jc_enum#get_this_path) fl;
+		jm_values#return;
+		(* Add __meta__ TODO: do this via annotations instead? *)
+		begin match Texpr.build_metadata gctx.com.basic (TEnumDecl en) with
+		| None ->
+			()
+		| Some e ->
+			ignore(jc_enum#spawn_field "__meta__" object_sig [FdStatic;FdPublic]);
+			let handler = new texpr_to_jvm gctx jc_enum jm_clinit (gctx.com.basic.tvoid) in
+			handler#texpr rvalue_any e;
+			jm_clinit#putstatic jc_enum#get_this_path "__meta__" object_sig
+		end;
+		jm_clinit#get_code#return_void;
+	end;
+	AnnotationHandler.generate_annotations (jc_enum :> JvmBuilder.base_builder) en.e_meta;
+	jc_enum#add_annotation (["haxe";"jvm";"annotation"],"EnumReflectionInformation") (["constructorNames",AArray names]);
+	write_class gctx.jar en.e_path (jc_enum#export_class gctx.default_export_config)
+
+let generate_abstract gctx a =
+	let super_path = object_path in
+	let jc = new JvmClass.builder a.a_path super_path in
+	jc#add_access_flag 1; (* public *)
+	let jc = jc#export_class gctx.default_export_config in
+	write_class gctx.jar a.a_path jc
+
+let debug_path path = match path with
+	(* | ([],"Main") | (["haxe";"jvm"],_) -> true *)
+	| (["haxe";"lang"],_) -> false (* Old Haxe/Java stuff that's weird *)
+	| _ -> true
+
+let is_extern_abstract a = match a.a_impl with
+	| Some {cl_extern = true} -> true
+	| _ -> false
+
+let generate_module_type ctx mt =
+	failsafe (t_infos mt).mt_pos (fun () ->
+		match mt with
+		| TClassDecl c when not c.cl_extern && debug_path c.cl_path -> generate_class ctx c
+		| TEnumDecl en when not en.e_extern -> generate_enum ctx en
+		| TAbstractDecl a when not (is_extern_abstract a) && Meta.has Meta.CoreType a.a_meta -> generate_abstract ctx a
+		| _ -> ()
+	)
+
+module Preprocessor = struct
+
+	let is_normal_anon an = match !(an.a_status) with
+		| Closed | Const | Opened -> true
+		| _ -> false
+
+	let check_anon gctx e = match e.etype,follow e.etype with
+		| TType(td,_),TAnon an when is_normal_anon an ->
+			ignore(TAnonIdentifiaction.identify_as gctx td.t_path an.a_fields)
+		| _ ->
+			()
+
+	let add_implicit_ctor gctx c c' cf =
+		let jsig = jsignature_of_type cf.cf_type in
+		try
+			let sm = Hashtbl.find gctx.implicit_ctors c.cl_path in
+			Hashtbl.replace gctx.implicit_ctors c.cl_path (PMap.add (c'.cl_path,jsig) (c',cf) sm);
+		with Not_found ->
+			Hashtbl.add gctx.implicit_ctors c.cl_path (PMap.add (c'.cl_path,jsig) (c',cf) PMap.empty)
+
+	let make_native cf =
+		cf.cf_meta <- (Meta.NativeGen,[],null_pos) :: cf.cf_meta
+
+	let make_haxe cf =
+		cf.cf_meta <- (Meta.HxGen,[],null_pos) :: cf.cf_meta
+
+	let preprocess_constructor_expr gctx c cf e =
+		let used_this = ref false in
+		let this_before_super = ref false in
+		let super_call_fields = DynArray.create () in
+		let is_on_current_class cf = PMap.mem cf.cf_name c.cl_fields in
+		let find_super_ctor el =
+			let csup,map_type = match c.cl_super with
+				| Some(c,tl) -> c,apply_params c.cl_params tl
+				| _ -> assert false
+			in
+			match find_overload_rec' true map_type csup "new" el with
+			| Some(c,cf) ->
+				let rec loop csup =
+					if c != csup then begin
+						match csup.cl_super with
+						| Some(c',_) ->
+							add_implicit_ctor gctx csup c' cf;
+							loop c'
+						| None -> assert false
+					end
+				in
+				loop csup;
+				(c,cf)
+			| None -> Error.error "Could not find overload constructor" e.epos
+		in
+		let rec promote_this_before_super c cf = match get_field_info gctx cf.cf_meta with
+			| None -> jerror "Something went wrong"
+			| Some info ->
+				if not info.has_this_before_super then begin
+					make_haxe cf;
+					(* print_endline (Printf.sprintf "promoted this_before_super to %s.new : %s" (s_type_path c.cl_path) (s_type (print_context()) cf.cf_type)); *)
+					info.has_this_before_super <- true;
+					List.iter (fun (c,cf) -> promote_this_before_super c cf) info.super_call_fields
+				end
+		in
+		let rec loop e =
+			check_anon gctx e;
+			begin match e.eexpr with
+			| TBinop(OpAssign,{eexpr = TField({eexpr = TConst TThis},FInstance(_,_,cf))},e2) when is_on_current_class cf->
+				(* Assigning this.field = value is fine if field is declared on our current class *)
+				loop e2;
+			| TConst TThis ->
+				used_this := true
+			| TCall({eexpr = TConst TSuper},el) ->
+				List.iter loop el;
+				if !used_this then begin
+					this_before_super := true;
+					make_haxe cf;
+					(* print_endline (Printf.sprintf "inferred this_before_super on %s.new : %s" (s_type_path c.cl_path) (s_type (print_context()) cf.cf_type)); *)
+				end;
+				let c,cf = find_super_ctor el in
+				if !this_before_super then promote_this_before_super c cf;
+				DynArray.add super_call_fields (c,cf);
+			| _ ->
+				Type.iter loop e
+			end;
+		in
+		loop e;
+		{
+			has_this_before_super = !this_before_super;
+			super_call_fields = DynArray.to_list super_call_fields;
+		}
+
+	let preprocess_expr gctx e =
+		let rec loop e =
+			check_anon gctx e;
+			Type.iter loop e
+		in
+		loop e
+
+	let check_overrides c = match c.cl_overrides with
+		| []->
+			()
+		| fields ->
+			let csup,map_type = match c.cl_super with
+				| Some(c,tl) -> c,apply_params c.cl_params tl
+				| None -> assert false
+			in
+			let fix_covariant_return cf =
+				let tl = match follow cf.cf_type with
+					| TFun(tl,_) -> tl
+					| _ -> assert false
+				in
+				match find_overload_rec' false map_type csup cf.cf_name (List.map (fun (_,_,t) -> Texpr.Builder.make_null t null_pos) tl) with
+				| Some(_,cf') ->
+					let tr = match follow cf'.cf_type with
+						| TFun(_,tr) -> tr
+						| _ -> assert false
+					in
+					cf.cf_type <- TFun(tl,tr);
+					cf.cf_expr <- begin match cf.cf_expr with
+						| Some ({eexpr = TFunction tf} as e) ->
+							Some {e with eexpr = TFunction {tf with tf_type = tr}}
+						| e ->
+							e
+					end;
+				| None ->
+					()
+					(* TODO: this should never happen if we get the unification right *)
+					(* Error.error "Could not find overload" cf.cf_pos *)
+			in
+			List.iter (fun cf ->
+				fix_covariant_return cf;
+				List.iter fix_covariant_return cf.cf_overloads
+			) fields
+
+	let rec get_constructor c =
+		match c.cl_constructor, c.cl_super with
+		| Some cf, _ -> c,cf
+		| None, None -> raise Not_found
+		| None, Some (csup,cparams) -> get_constructor csup
+
+	let preprocess_class gctx c =
+		let field cf = match cf.cf_expr with
+			| None ->
+				()
+			| Some e ->
+				preprocess_expr gctx e
+		in
+		let has_dynamic_instance_method = ref false in
+		let has_field_init = ref false in
+		let field mtype cf =
+			List.iter field (cf :: cf.cf_overloads);
+			match mtype with
+			| MConstructor ->
+				()
+			| MInstance ->
+				begin match cf.cf_kind with
+					| Method MethDynamic -> has_dynamic_instance_method := true
+					| Var _ when cf.cf_expr <> None && not !has_field_init && c.cl_constructor = None && c.cl_super = None ->
+						has_field_init := true;
+						add_implicit_ctor gctx c c (mk_field "new" (tfun [] gctx.com.basic.tvoid) null_pos null_pos)
+					| _ -> ()
+				end;
+			| MStatic ->
+				()
+		in
+		check_overrides c;
+		List.iter (field MStatic) c.cl_ordered_statics;
+		List.iter (field MInstance) c.cl_ordered_fields;
+		match c.cl_constructor with
+		| None ->
+			begin try
+				let csup,cf = get_constructor c in
+				List.iter (fun cf -> add_implicit_ctor gctx c csup cf) (cf :: cf.cf_overloads)
+			with Not_found ->
+				()
+			end;
+		| Some cf ->
+			let field cf =
+				if !has_dynamic_instance_method then make_haxe cf;
+				begin match cf.cf_expr with
+				| None ->
+					()
+				| Some e ->
+					let info = preprocess_constructor_expr gctx c cf e in
+					let index = DynArray.length gctx.field_infos in
+					DynArray.add gctx.field_infos info;
+					cf.cf_meta <- (Meta.Custom ":jvm.fieldInfo",[(EConst (Int (string_of_int index)),null_pos)],null_pos) :: cf.cf_meta;
+					if not (Meta.has Meta.HxGen cf.cf_meta) then begin
+						let rec loop next c =
+							if c.cl_extern then make_native cf
+							else match c.cl_constructor with
+								| Some cf' when Meta.has Meta.HxGen cf'.cf_meta -> make_haxe cf
+								| Some cf' when Meta.has Meta.NativeGen cf'.cf_meta -> make_native cf
+								| _ -> next c
+						in
+						let rec up c = match c.cl_super with
+							| None -> ()
+							| Some(c,_) -> loop up c
+						in
+						let rec down c = List.iter (fun c -> loop down c) c.cl_descendants in
+						loop up c;
+						loop down c
+					end;
+				end
+			in
+			List.iter field (cf :: cf.cf_overloads)
+
+	let preprocess gctx =
+		List.iter (function
+			| TClassDecl c when debug_path c.cl_path && not c.cl_interface -> preprocess_class gctx c
+			| _ -> ()
+		) gctx.com.types
+end
+
+let file_name_and_extension file =
+	match List.rev (ExtString.String.nsplit file "/") with
+	| e1 :: _ -> e1
+	| _ -> assert false
+
+let generate com =
+	mkdir_from_path com.file;
+	let jar_name,manifest_suffix = match com.main_class with
+		| Some path -> snd path,"\nMain-Class: " ^ (s_type_path path)
+		| None -> "jar",""
+	in
+	let jar_name = if com.debug then jar_name ^ "-Debug" else jar_name in
+	let jar_dir = add_trailing_slash com.file in
+	let jar_path = Printf.sprintf "%s%s.jar" jar_dir jar_name in
+	let gctx = {
+		com = com;
+		jar = Zip.open_out jar_path;
+		t_exception = TInst(resolve_class com (["java";"lang"],"Exception"),[]);
+		t_throwable = TInst(resolve_class com (["java";"lang"],"Throwable"),[]);
+		anon_lut = Hashtbl.create 0;
+		anon_path_lut = Hashtbl.create 0;
+		anon_num = 0;
+		implicit_ctors = Hashtbl.create 0;
+		field_infos = DynArray.create();
+		current_field_info = None;
+		default_export_config = {
+			export_debug = com.debug;
+		}
+	} in
+	Std.finally (Timer.timer ["generate";"java";"preprocess"]) Preprocessor.preprocess gctx;
+	let class_paths = ExtList.List.filter_map (fun (file,std,_,_,_) ->
+		if std then None
+		else begin
+			let dir = Printf.sprintf "%slib/" jar_dir in
+			Path.mkdir_from_path dir;
+			let name = file_name_and_extension file in
+			let ch_in = open_in_bin file in
+			let ch_out = open_out_bin (Printf.sprintf "%s%s" dir name) in
+			let b = IO.read_all (IO.input_channel ch_in) in
+			output_string ch_out b;
+			close_in ch_in;
+			close_out ch_out;
+			Some (Printf.sprintf "lib/%s" name)
+		end
+	) com.java_libs in
+	let manifest_content =
+		"Manifest-Version: 1.0\n" ^
+		(match class_paths with [] -> "" | _ -> "Class-Path: " ^ (String.concat " " class_paths ^ "\n")) ^
+		"Created-By: Haxe (Haxe Foundation)" ^
+		manifest_suffix ^
+		"\n\n"
+	in
+	Zip.add_entry manifest_content gctx.jar "META-INF/MANIFEST.MF";
+	Hashtbl.iter (fun name v ->
+		let filename = Codegen.escape_res_name name true in
+		Zip.add_entry v gctx.jar filename;
+	) com.resources;
+	List.iter (generate_module_type gctx) com.types;
+	Hashtbl.iter (fun fields path ->
+		let jc = new JvmClass.builder path haxe_dynamic_object_path in
+		jc#add_access_flag 0x1;
+		begin
+			let jm_ctor = jc#spawn_method "<init>" (method_sig (List.map snd fields) None) [MPublic] in
+			jm_ctor#load_this;
+			jm_ctor#get_code#aconst_null haxe_empty_constructor_sig;
+			jm_ctor#call_super_ctor ConstructInit (method_sig [haxe_empty_constructor_sig] None);
+			List.iter (fun (name,jsig) ->
+				jm_ctor#add_argument_and_field name jsig;
+			) fields;
+			jm_ctor#get_code#return_void;
+		end;
+		begin
+			let string_map_path = (["haxe";"ds"],"StringMap") in
+			let string_map_sig = object_path_sig string_map_path in
+			let jm_fields = jc#spawn_method "_hx_getKnownFields" (method_sig [] (Some string_map_sig)) [MProtected] in
+			let _,load,save = jm_fields#add_local "tmp" string_map_sig VarWillInit in
+			jm_fields#construct ConstructInit string_map_path (fun () -> []);
+			save();
+			List.iter (fun (name,jsig) ->
+				load();
+				let offset = jc#get_pool#add_const_string name in
+				jm_fields#get_code#sconst (string_sig) offset;
+				jm_fields#load_this;
+				jm_fields#getfield jc#get_this_path name jsig;
+				jm_fields#expect_reference_type;
+				jm_fields#invokevirtual string_map_path "set" (method_sig [string_sig;object_sig] None);
+			) fields;
+			load();
+			jm_fields#get_code#return_value string_map_sig
+		end;
+		generate_dynamic_access gctx jc (List.map (fun (name,jsig) -> name,jsig,Var {v_write = AccNormal;v_read = AccNormal}) fields) true;
+		write_class gctx.jar path (jc#export_class gctx.default_export_config)
+	) gctx.anon_lut;
+	Zip.close_out gctx.jar

+ 229 - 0
src/generators/jvm/jvmAttribute.ml

@@ -0,0 +1,229 @@
+open JvmGlobals
+open JvmData
+open JvmVerificationTypeInfo
+open JvmWriter
+
+type j_annotation = {
+	ann_type : int;
+	ann_elements : (jvm_constant_pool_index * j_annotation_element_value) array;
+}
+
+and j_annotation_element_value = char * j_annotation_value
+
+and j_annotation_value =
+	| ValConst of int (* B, C, D, E, F, I, J, S, Z, s *)
+	| ValEnum of int * int (* e *)
+	| ValClass of int (* c *) (* V -> Void *)
+	| ValAnnotation of j_annotation (* @ *)
+	| ValArray of j_annotation_element_value array (* [ *)
+
+(* https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.4 *)
+type j_stack_map_frame =
+	| StackSame of int
+	| Stack1StackItem of int * JvmVerificationTypeInfo.t
+	| Stack1StackItemExtended of int * JvmVerificationTypeInfo.t
+	| StackChop1 of int
+	| StackChop2 of int
+	| StackChop3 of int
+	| StackSameExtended of int
+	| StackAppend1 of int * JvmVerificationTypeInfo.t
+	| StackAppend2 of int * JvmVerificationTypeInfo.t * JvmVerificationTypeInfo.t
+	| StackAppend3 of int * JvmVerificationTypeInfo.t * JvmVerificationTypeInfo.t * JvmVerificationTypeInfo.t
+	| StackFull of int * JvmVerificationTypeInfo.t array * JvmVerificationTypeInfo.t array
+
+(* https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.13 *)
+type jvm_local_debug = {
+	ld_start_pc : int;
+	ld_length : int;
+	ld_name_index : jvm_constant_pool_index;
+	ld_descriptor_index : jvm_constant_pool_index;
+	ld_index : int;
+}
+
+type jvm_inner_class = {
+	ic_inner_class_info_index : int;
+	ic_outer_class_info_index : int;
+	ic_inner_name_index : int;
+	ic_inner_class_access_flags : int;
+}
+
+type j_bootstrap_method = {
+	bm_method_ref : jvm_constant_pool_index;
+	bm_arguments : jvm_constant_pool_index array;
+}
+
+(* https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7 *)
+type j_attribute =
+	| AttributeConstantValue of jvm_constant_pool_index
+	| AttributeCode of jvm_code
+	| AttributeStackMapTable of j_stack_map_frame array
+	| AttributeExceptions of jvm_constant_pool_index array
+	| AttributeSourceFile of jvm_constant_pool_index
+	| AttributeLineNumberTable of (int * int) array
+	| AttributeSignature of jvm_constant_pool_index
+	| AttributeLocalVariableTable of jvm_local_debug array
+	| AttributeLocalVariableTypeTable of jvm_local_debug array
+	| AttributeInnerClasses of jvm_inner_class array
+	| AttributeEnclosingMethod of jvm_constant_pool_index * jvm_constant_pool_index
+	| AttributeRuntimeVisibleAnnotations of j_annotation array
+	| AttributeBootstrapMethods of j_bootstrap_method array
+
+let write_verification_type ch = function
+	| VTop -> write_byte ch 0
+	| VInteger -> write_byte ch 1
+	| VFloat -> write_byte ch 2
+	| VDouble -> write_byte ch 3
+	| VLong -> write_byte ch 4
+	| VNull -> write_byte ch 5
+	| VUninitializedThis -> write_byte ch 6
+	| VObject i ->
+		write_byte ch 7;
+		write_ui16 ch i;
+	| VUninitialized i ->
+		write_byte ch 8;
+		write_ui16 ch i
+
+let write_stack_map_frame ch = function
+	| StackSame i ->
+		assert (i < 64);
+		write_byte ch i
+	| Stack1StackItem(i,t) ->
+		assert (i < 64);
+		write_byte ch (i + 64);
+		write_verification_type ch t;
+	| Stack1StackItemExtended(i,t) ->
+		write_byte ch 247;
+		write_ui16 ch i;
+		write_verification_type ch t;
+	| StackChop1 i1 ->
+		write_byte ch 250;
+		write_ui16 ch i1;
+	| StackChop2 i1 ->
+		write_byte ch 249;
+		write_ui16 ch i1;
+	| StackChop3 i1 ->
+		write_byte ch 248;
+		write_ui16 ch i1;
+	| StackSameExtended i ->
+		write_byte ch 251;
+		write_ui16 ch i
+	| StackAppend1(i1,t1) ->
+		write_byte ch 252;
+		write_ui16 ch i1;
+		write_verification_type ch t1;
+	| StackAppend2(i1,t1,t2) ->
+		write_byte ch 253;
+		write_ui16 ch i1;
+		write_verification_type ch t1;
+		write_verification_type ch t2;
+	| StackAppend3(i1,t1,t2,t3) ->
+		write_byte ch 254;
+		write_ui16 ch i1;
+		write_verification_type ch t1;
+		write_verification_type ch t2;
+		write_verification_type ch t3;
+	| StackFull(i1,tl1,tl2) ->
+		write_byte ch 255;
+		write_ui16 ch i1;
+		write_array16 ch write_verification_type tl1;
+		write_array16 ch write_verification_type tl2
+
+let write_constant pool ch const =
+	let offset = pool#add const in
+	write_ui16 ch offset
+
+let rec write_annotation ch ann =
+	write_ui16 ch ann.ann_type;
+	write_array16 ch (fun _ (i,v) ->
+		write_ui16 ch i;
+		let rec loop _ (c,v) =
+			write_byte ch (Char.code c);
+			match v with
+			| ValConst i ->
+				write_ui16 ch i
+			| ValEnum(i1,i2) ->
+				write_ui16 ch i1;
+				write_ui16 ch i2;
+			| ValClass i ->
+				write_ui16 ch i
+			| ValAnnotation a ->
+				write_annotation ch ann
+			| ValArray annl ->
+				write_array16 ch loop annl
+		in
+		loop ch v
+	) ann.ann_elements
+
+let write_attribute pool jvma =
+	let ch = IO.output_bytes () in
+	let write_local_table table =
+		write_array16 ch (fun _ d ->
+			write_ui16 ch d.ld_start_pc;
+			write_ui16 ch d.ld_length;
+			write_ui16 ch d.ld_name_index;
+			write_ui16 ch d.ld_descriptor_index;
+			write_ui16 ch d.ld_index;
+		) table
+	in
+	let name = match jvma with
+	| AttributeConstantValue const ->
+		write_ui16 ch const;
+		"ConstantValue";
+	| AttributeCode code ->
+		write_ui16 ch code.code_max_stack;
+		write_ui16 ch code.code_max_locals;
+		write_ui32 ch (Bytes.length code.code_code);
+		write_bytes ch code.code_code;
+		write_array16 ch write_exception code.code_exceptions;
+		write_jvm_attributes ch code.code_attributes;
+		"Code";
+	| AttributeStackMapTable stack_map ->
+		write_array16 ch write_stack_map_frame stack_map;
+		"StackMapTable"
+	| AttributeExceptions a ->
+		write_array16 ch (fun _ offset -> write_ui16 ch offset) a;
+		"Exceptions"
+	| AttributeInnerClasses icl ->
+		write_array16 ch (fun ch ic ->
+			write_ui16 ch ic.ic_inner_class_info_index;
+			write_ui16 ch ic.ic_outer_class_info_index;
+			write_ui16 ch ic.ic_inner_name_index;
+			write_ui16 ch ic.ic_inner_class_access_flags;
+		) icl;
+		"InnerClasses"
+	| AttributeEnclosingMethod(i1,i2) ->
+		write_ui16 ch i1;
+		write_ui16 ch i2;
+		"EnclosingMethod"
+	| AttributeSourceFile offset ->
+		write_ui16 ch offset;
+		"SourceFile";
+	| AttributeLineNumberTable a ->
+		write_array16 ch (fun _ (i1,i2) ->
+			write_ui16 ch i1;
+			write_ui16 ch i2
+		) a;
+		"LineNumberTable"
+	| AttributeSignature offset ->
+		write_ui16 ch offset;
+		"Signature"
+	| AttributeLocalVariableTable table ->
+		write_local_table table;
+		"LocalVariableTable"
+	| AttributeLocalVariableTypeTable table ->
+		write_local_table table;
+		"LocalVariableTypeTable"
+	| AttributeRuntimeVisibleAnnotations al ->
+		write_array16 ch write_annotation al;
+		"RuntimeVisibleAnnotations"
+	| AttributeBootstrapMethods a ->
+		write_array16 ch (fun _ bm ->
+			write_ui16 ch bm.bm_method_ref;
+			write_array16 ch (fun _ i -> write_ui16 ch i) bm.bm_arguments;
+		) a;
+		"BootstrapMethods"
+	in
+	{
+		attr_index = pool#add (ConstUtf8 name);
+		attr_data = IO.close_out ch
+	}

+ 70 - 0
src/generators/jvm/jvmBuilder.ml

@@ -0,0 +1,70 @@
+open JvmGlobals
+open JvmData
+open JvmSignature
+open JvmAttribute
+
+type annotation_kind =
+	| AInt of Int32.t
+	| ADouble of float
+	| AString of string
+	| ABool of bool
+	| AEnum of jsignature * string
+	| AArray of annotation_kind list
+
+and annotation = (string * annotation_kind) list
+
+type export_config = {
+	export_debug : bool;
+}
+
+class base_builder = object(self)
+	val mutable access_flags = 0
+	val attributes = DynArray.create ()
+	val annotations = DynArray.create ()
+	val mutable was_exported = false
+
+	method add_access_flag i =
+		access_flags <- i lor access_flags
+
+	method add_attribute (a : j_attribute) =
+		DynArray.add attributes a
+
+	method add_annotation (path : jpath) (a : annotation) =
+		DynArray.add annotations ((TObject(path,[])),a)
+
+	method private commit_annotations pool =
+		if DynArray.length annotations > 0 then begin
+			let open JvmAttribute in
+			let a = DynArray.to_array annotations in
+			let a = Array.map (fun (jsig,l) ->
+				let offset = pool#add_string (generate_signature false jsig) in
+				let l = List.map (fun (name,ak) ->
+					let offset = pool#add_string name in
+					let rec loop ak = match ak with
+						| AInt i32 ->
+							'I',ValConst(pool#add (ConstInt i32))
+						| ADouble f ->
+							'D',ValConst(pool#add (ConstDouble f))
+						| AString s ->
+							's',ValConst(pool#add_string s)
+						| ABool b ->
+							'Z',ValConst(pool#add (ConstInt (if b then Int32.one else Int32.zero)))
+						| AEnum(jsig,name) ->
+							'e',ValEnum(pool#add_string (generate_signature false jsig),pool#add_string name)
+						| AArray l ->
+							let l = List.map (fun ak -> loop ak) l in
+							'[',ValArray(Array.of_list l)
+					in
+					offset,loop ak
+				) l in
+				{
+					ann_type = offset;
+					ann_elements = Array.of_list l;
+				}
+			) a in
+			self#add_attribute (AttributeRuntimeVisibleAnnotations a)
+		end
+
+	method export_attributes (pool : JvmConstantPool.constant_pool) =
+		DynArray.to_array (DynArray.map (write_attribute pool) attributes)
+end

+ 169 - 0
src/generators/jvm/jvmClass.ml

@@ -0,0 +1,169 @@
+open JvmGlobals
+open JvmData
+open JvmSignature
+open NativeSignatures
+open JvmAttribute
+open JvmBuilder
+
+(* High-level class builder. *)
+
+class builder path_this path_super = object(self)
+	inherit base_builder
+	val pool = new JvmConstantPool.constant_pool
+	val jsig = TObject(path_this,[])
+	val mutable offset_this = 0
+	val mutable offset_super = 0
+	val mutable interface_offsets = []
+	val fields = DynArray.create ()
+	val methods = DynArray.create ()
+	val method_sigs = Hashtbl.create 0
+	val inner_classes = Hashtbl.create 0
+	val mutable closure_count = 0
+	val mutable bootstrap_methods = []
+	val mutable num_bootstrap_methods = 0
+	val mutable spawned_methods = []
+	val mutable field_init_method = None
+
+	method add_interface path =
+		interface_offsets <- (pool#add_path path) :: interface_offsets
+
+	method add_field (f : jvm_field) =
+		DynArray.add fields f
+
+	method get_bootstrap_method path name jsig (consts : jvm_constant_pool_index list) =
+		try
+			fst (List.assoc (path,name,consts) bootstrap_methods)
+		with Not_found ->
+			let offset = pool#add_field path name jsig FKMethod in
+			let offset = pool#add (ConstMethodHandle(6, offset)) in
+			let bm = {
+				bm_method_ref = offset;
+				bm_arguments = Array.of_list consts;
+			} in
+			bootstrap_methods <- ((path,name,consts),(offset,bm)) :: bootstrap_methods;
+			num_bootstrap_methods <- num_bootstrap_methods + 1;
+			num_bootstrap_methods - 1
+
+	method get_pool = pool
+
+	method get_this_path = path_this
+	method get_super_path = path_super
+	method get_jsig = jsig
+	method get_offset_this = offset_this
+	method get_access_flags = access_flags
+
+	method get_next_closure_name =
+		let name = Printf.sprintf "hx_closure$%i" closure_count in
+		closure_count <- closure_count + 1;
+		name
+
+	method has_method (name : string) (jsig : jsignature) =
+		Hashtbl.mem method_sigs (name,generate_method_signature false jsig)
+
+	method spawn_inner_class (jm : JvmMethod.builder option) (path_super : jpath) (name : string option) =
+		let path = match name with
+			| None -> (fst path_this,Printf.sprintf "%s$%i" (snd path_this) (Hashtbl.length inner_classes))
+			| Some name -> (fst path_this,Printf.sprintf "%s$%s" (snd path_this) name)
+		in
+		let jc = new builder path path_super in
+		jc#add_access_flag 0x01;
+		begin match jm with
+		| None ->
+			()
+		| Some jm ->
+			let pool = jc#get_pool in
+			let offset_class = pool#add_path path_this in
+			let offset_name = pool#add_string jm#get_name in
+			let offset_desc = pool#add_string (generate_signature false jm#get_jsig) in
+			let offset_info = pool#add (ConstNameAndType(offset_name,offset_desc)) in
+			jc#add_attribute (JvmAttribute.AttributeEnclosingMethod(offset_class,offset_info));
+		end;
+		let offset = pool#add_path path in
+		Hashtbl.add inner_classes offset jc;
+		jc
+
+	method spawn_method (name : string) (jsig_method : jsignature) (flags : MethodAccessFlags.t list) =
+		let jm = new JvmMethod.builder self name jsig_method in
+		let ssig_method = generate_method_signature false jsig_method in
+		if Hashtbl.mem method_sigs (name,ssig_method) then
+			jerror (Printf.sprintf "Duplicate field on class %s: %s %s" (Globals.s_type_path path_this) name ssig_method);
+		Hashtbl.add method_sigs (name,ssig_method) jm;
+		List.iter (fun flag ->
+			jm#add_access_flag (MethodAccessFlags.to_int flag)
+		) flags;
+		let pop_scope = jm#push_scope in
+		if not (jm#has_method_flag MStatic) then ignore(jm#add_local "this" (if jm#get_name = "<init>" then (TUninitialized None) else jsig) VarArgument);
+		spawned_methods <- (jm,Some pop_scope) :: spawned_methods;
+		jm
+
+	method spawn_field (name : string) (jsig_method : jsignature) (flags : FieldAccessFlags.t list) =
+		let jm = new JvmMethod.builder self name jsig_method in
+		List.iter (fun flag ->
+			jm#add_access_flag (FieldAccessFlags.to_int flag)
+		) flags;
+		spawned_methods <- (jm,None) :: spawned_methods;
+		jm
+
+	method private commit_inner_classes =
+		if Hashtbl.length pool#get_inner_classes > 0 then begin
+			let open JvmAttribute in
+			let l = Hashtbl.fold (fun (path,name) offset_class acc ->
+				(path,name,offset_class) :: acc
+			) pool#get_inner_classes [] in
+			let l = List.map (fun (path,name,offset_class) ->
+				let offset_name = pool#add_string name in
+				let flags = try (Hashtbl.find inner_classes offset_class)#get_access_flags with Not_found -> 9 in
+				let offset_outer = pool#add_path path in
+				{
+					ic_inner_class_info_index = offset_class;
+					ic_outer_class_info_index = offset_outer;
+					ic_inner_name_index = offset_name;
+					ic_inner_class_access_flags = flags;
+				}
+			) l in
+			let a = Array.of_list l in
+			self#add_attribute (AttributeInnerClasses a)
+		end
+
+	method private commit_bootstrap_methods =
+		match bootstrap_methods with
+		| [] ->
+			()
+		| _ ->
+			let l = List.fold_left (fun acc (_,(_,bm)) -> bm :: acc) [] bootstrap_methods in
+			self#add_attribute (AttributeBootstrapMethods (Array.of_list l))
+
+	method export_class (config : export_config) =
+		assert (not was_exported);
+		was_exported <- true;
+		List.iter (fun (jm,pop_scope) ->
+			begin match pop_scope with
+			| Some pop_scope ->
+				pop_scope();
+				DynArray.add methods (jm#export_method config);
+			| None ->
+				self#add_field jm#export_field
+			end;
+		) (List.rev spawned_methods);
+		self#commit_bootstrap_methods;
+		self#commit_inner_classes;
+		self#commit_annotations pool;
+		let attributes = self#export_attributes pool in
+		let pool = pool#close in
+		{
+			class_minor_version = 0;
+			class_major_version = 0x34;
+			class_constant_pool = pool;
+			class_access_flags = access_flags;
+			class_this_class = offset_this;
+			class_super_class = offset_super;
+			class_interfaces = Array.of_list interface_offsets;
+			class_fields = DynArray.to_array fields;
+			class_methods = DynArray.to_array methods;
+			class_attributes = attributes;
+		}
+
+	initializer
+		offset_this <- pool#add_path path_this;
+		offset_super <- pool#add_path path_super;
+end

+ 547 - 0
src/generators/jvm/jvmCode.ml

@@ -0,0 +1,547 @@
+open JvmGlobals
+open JvmData
+open JvmSignature
+
+(* Opcode builder with stack management *)
+
+exception EmptyStack
+
+class jvm_stack = object(self)
+	val mutable stack = [];
+	val mutable stack_size = 0;
+	val mutable max_stack_size = 0;
+
+	method push js =
+		stack <- js :: stack;
+		stack_size <- stack_size + (signature_size js);
+		if stack_size > max_stack_size then max_stack_size <- stack_size
+
+	method pop = match stack with
+		| js :: l ->
+			stack <- l;
+			stack_size <- stack_size - (signature_size js);
+			js
+		| [] ->
+			raise EmptyStack
+
+	method to_string =
+		Printf.sprintf "[%s]" (String.concat ", " (List.map (generate_signature false) stack))
+
+	method save =
+		(stack,stack_size)
+
+	method top = match stack with
+		| hd :: _ -> hd
+		| [] -> raise EmptyStack
+
+	method restore ((stack',stack_size') : (jsignature list * int)) =
+		stack <- stack';
+		stack_size <- stack_size'
+
+	method get_max_stack_size = max_stack_size
+	method set_max_stack_size value = max_stack_size <- value
+
+	method get_stack = stack
+
+	method get_stack_items i =
+		let rec loop acc i l =
+			if i = 0 then List.rev acc
+			else match l with
+			| x :: l ->
+				loop (x :: acc) (i - 1) l
+			| [] ->
+				raise EmptyStack
+		in
+		loop [] i stack
+end
+
+let s_vt = generate_method_signature true
+
+let s_vtl l = Printf.sprintf "[%s]" (String.concat ", " (List.map s_vt l))
+
+class builder pool = object(self)
+	val stack = new jvm_stack;
+	val lines = DynArray.create()
+	val mutable last_line = -1
+	val mutable current_line = -1
+
+	(* ops *)
+	val ops = DynArray.create();
+	val stack_debug = DynArray.create()
+	val mutable fp = 0
+
+	method debug_stack =
+		let l = DynArray.to_list stack_debug in
+		let opmax = ref 0 in
+		let l = List.map (fun (op,_,after,line) ->
+			let sop = JvmDebug.s_jcode pool op in
+			if String.length sop > !opmax then opmax := String.length sop;
+			let safter = s_vtl after in
+			(line,sop,safter)
+		) l in
+		let s_ops = String.concat "\n\t\t" (List.map (fun (line,sop,safter) ->
+			Printf.sprintf "%4i %*s %s" line !opmax sop safter
+		) l) in
+		s_ops
+
+	method stack_error opcode expected actual =
+		let s_ops = self#debug_stack in
+		jerror
+			(Printf.sprintf "Stack error\n\tops      :\n\t\t%s\n\t     line: %i\n\toperation: %s\n\texpected : %s\n\tactual   : %s"
+				s_ops
+				current_line
+				(JvmDebug.s_jcode pool opcode)
+				(s_vtl expected)
+				(s_vtl actual)
+			)
+
+	method op opcode length expect return =
+		if last_line <> current_line then begin
+			last_line <- current_line;
+			DynArray.add lines (fp,current_line)
+		end;
+		DynArray.add ops opcode;
+		fp <- fp + length;
+		let cur = stack#get_stack in
+		List.iter (fun js ->
+			let js' = try
+				stack#pop
+			with EmptyStack ->
+				self#stack_error opcode expect cur;
+				assert false
+			in
+			(* TODO: some unification or something? *)
+			match js,js' with
+			| (TObject _ | TTypeParameter _),(TObject _ | TTypeParameter _ | TArray _) -> () (* TODO ??? *)
+			| TMethod _,TMethod _ -> ()
+			| TMethod _,TObject((["java";"lang";"invoke"],"MethodHandle"),[]) -> ()
+			| TTypeParameter _,TMethod _ -> ()
+			| TObject _,TMethod _ -> ()
+			| TMethod _,TObject _ -> ()
+			| TArray _,TArray _ -> ()
+			| TBool,TInt -> ()
+			| TInt,TBool -> ()
+			| TDouble,TInt -> ()
+			| TInt,(TChar | TShort | TByte) -> ()
+			| (TObject _ | TTypeParameter _),TUninitialized _ -> ()
+			| _ ->
+				if js <> js' then self#stack_error opcode expect cur
+		) expect;
+		List.iter stack#push (List.rev return);
+		DynArray.add stack_debug (opcode,cur,stack#get_stack,current_line);
+
+	method op_maybe_wide op opw i tl tr = match get_numeric_range_unsigned i with
+		| Int8Range -> self#op op 2 tl tr
+		| Int16Range -> self#op (OpWide opw) 4 tl tr
+		| Int32Range -> assert false
+
+	(* variables *)
+
+	method iload ?(jsig=TInt) i = match i with
+		| 0 -> self#op OpIload_0 1 [] [jsig]
+		| 1 -> self#op OpIload_1 1 [] [jsig]
+		| 2 -> self#op OpIload_2 1 [] [jsig]
+		| 3 -> self#op OpIload_3 1 [] [jsig]
+		| i -> self#op_maybe_wide (OpIload i) (OpWIload i) i [] [jsig]
+
+	method lload i = match i with
+		| 0 -> self#op OpLload_0 1 [] [TLong]
+		| 1 -> self#op OpLload_1 1 [] [TLong]
+		| 2 -> self#op OpLload_2 1 [] [TLong]
+		| 3 -> self#op OpLload_3 1 [] [TLong]
+		| i -> self#op_maybe_wide (OpLload i) (OpWLload i) i [] [TLong]
+
+	method fload i = match i with
+		| 0 -> self#op OpFload_0 1 [] [TFloat]
+		| 1 -> self#op OpFload_1 1 [] [TFloat]
+		| 2 -> self#op OpFload_2 1 [] [TFloat]
+		| 3 -> self#op OpFload_3 1 [] [TFloat]
+		| i -> self#op_maybe_wide (OpFload i) (OpWFload i) i [] [TFloat]
+
+	method dload i = match i with
+		| 0 -> self#op OpDload_0 1 [] [TDouble]
+		| 1 -> self#op OpDload_1 1 [] [TDouble]
+		| 2 -> self#op OpDload_2 1 [] [TDouble]
+		| 3 -> self#op OpDload_3 1 [] [TDouble]
+		| i -> self#op_maybe_wide (OpDload i) (OpWDload i) i [] [TDouble]
+
+	method aload vtt i = match i with
+		| 0 -> self#op OpAload_0 1 [] [vtt]
+		| 1 -> self#op OpAload_1 1 [] [vtt]
+		| 2 -> self#op OpAload_2 1 [] [vtt]
+		| 3 -> self#op OpAload_3 1 [] [vtt]
+		| i -> self#op_maybe_wide (OpAload i) (OpWAload i) i [] [vtt]
+
+	method istore i = match i with
+		| 0 -> self#op OpIstore_0 1 [TInt] []
+		| 1 -> self#op OpIstore_1 1 [TInt] []
+		| 2 -> self#op OpIstore_2 1 [TInt] []
+		| 3 -> self#op OpIstore_3 1 [TInt] []
+		| i -> self#op_maybe_wide (OpIstore i) (OpWIstore i) i [TInt] []
+
+	method lstore i = match i with
+		| 0 -> self#op OpLstore_0 1 [TLong] []
+		| 1 -> self#op OpLstore_1 1 [TLong] []
+		| 2 -> self#op OpLstore_2 1 [TLong] []
+		| 3 -> self#op OpLstore_3 1 [TLong] []
+		| i -> self#op_maybe_wide (OpLstore i) (OpWLstore i) i [TLong] []
+
+	method fstore i = match i with
+		| 0 -> self#op OpFstore_0 1 [TFloat] []
+		| 1 -> self#op OpFstore_1 1 [TFloat] []
+		| 2 -> self#op OpFstore_2 1 [TFloat] []
+		| 3 -> self#op OpFstore_3 1 [TFloat] []
+		| i -> self#op_maybe_wide (OpFstore i) (OpWFstore i) i [TFloat] []
+
+	method dstore i = match i with
+		| 0 -> self#op OpDstore_0 1 [TDouble] []
+		| 1 -> self#op OpDstore_1 1 [TDouble] []
+		| 2 -> self#op OpDstore_2 1 [TDouble] []
+		| 3 -> self#op OpDstore_3 1 [TDouble] []
+		| i -> self#op_maybe_wide (OpDstore i) (OpWDstore i) i [TDouble] []
+
+	method astore vtt i = match i with
+		| 0 -> self#op OpAstore_0 1 [vtt] []
+		| 1 -> self#op OpAstore_1 1 [vtt] []
+		| 2 -> self#op OpAstore_2 1 [vtt] []
+		| 3 -> self#op OpAstore_3 1 [vtt] []
+		| i -> self#op_maybe_wide (OpAstore i) (OpWAstore i) i [vtt] []
+
+	method iinc index i = match get_numeric_range_unsigned index,get_numeric_range i with
+		| Int8Range,Int8Range ->
+			self#op (OpIinc(index,i)) 3 [] []
+		| (Int8Range | Int16Range),(Int8Range | Int16Range) ->
+			self#op (OpWide (OpWIinc(index,i))) 6 [] []
+		| _ ->
+			assert false
+
+	(* conversions *)
+
+	method d2f = self#op OpD2f 1 [TDouble] [TFloat]
+	method d2i = self#op OpD2i 1 [TDouble] [TInt]
+	method d2l = self#op OpD2l 1 [TDouble] [TLong]
+
+	method f2d = self#op OpF2d 1 [TFloat] [TDouble]
+	method f2i = self#op OpF2i 1 [TFloat] [TInt]
+	method f2l = self#op OpF2l 1 [TFloat] [TLong]
+
+	method i2b jsig = self#op OpI2b 1 [TInt] [jsig]
+	method i2c = self#op OpI2c 1 [TInt] [TChar]
+	method i2d = self#op OpI2d 1 [TInt] [TDouble]
+	method i2f = self#op OpI2f 1 [TInt] [TFloat]
+	method i2l = self#op OpI2l 1 [TInt] [TLong]
+	method i2s = self#op OpI2s 1 [TInt] [TShort]
+
+	method l2d = self#op OpL2d 1 [TLong] [TDouble]
+	method l2f = self#op OpL2f 1 [TLong] [TFloat]
+	method l2i = self#op OpL2i 1 [TLong] [TInt]
+
+	(* arrays *)
+
+	method newarray ta t = self#op (OpNewarray t) 2 [TInt] [ta]
+	method anewarray ta offset = self#op (OpAnewarray offset) 3 [TInt] [ta]
+
+	method arraylength ta = self#op OpArraylength 1 [ta] [TInt]
+
+	method aastore ta te = self#op OpAastore 1 [te;TInt;ta] []
+	method aaload ta te = self#op OpAaload 1 [TInt;ta] [te]
+
+	method castore ta = self#op OpCastore 1 [TChar;TInt;ta] []
+	method caload ta = self#op OpCaload 1 [TInt;ta] [TChar]
+
+	method fastore ta = self#op OpFastore 1 [TFloat;TInt;ta] []
+	method faload ta = self#op OpFaload 1 [TInt;ta] [TFloat]
+
+	method lastore ta = self#op OpLastore 1 [TLong;TInt;ta] []
+	method laload ta = self#op OpLaload 1 [TInt;ta] [TLong]
+
+	method sastore ta = self#op OpSastore 1 [TShort;TInt;ta] []
+	method saload ta = self#op OpSaload 1 [TInt;ta] [TShort]
+
+	method iastore ta = self#op OpIastore 1 [TInt;TInt;ta] []
+	method iaload ta = self#op OpIaload 1 [TInt;ta] [TInt]
+
+	method dastore ta = self#op OpDastore 1 [TDouble;TInt;ta] []
+	method daload ta = self#op OpDaload 1 [TInt;ta] [TDouble]
+
+	method bastore jsig ta = self#op OpBastore 1 [jsig;TInt;ta] []
+	method baload jsig ta = self#op OpBaload 1 [TInt;ta] [jsig]
+
+	(* fields *)
+
+	method getstatic offset t =
+		self#op (OpGetstatic offset) 3 [] [t]
+
+	method putstatic offset t =
+		self#op (OpPutstatic offset) 3 [t] []
+
+	method getfield offset tobj t =
+		self#op (OpGetfield offset) 3 [tobj] [t]
+
+	method putfield offset tobj t =
+		self#op (OpPutfield offset) 3 [t;tobj] []
+
+	(* calls *)
+
+	method invokestatic offset tl tr =
+		self#op (OpInvokestatic offset) 3 (List.rev tl) tr
+
+	method invokevirtual offset t1 tl tr =
+		self#op (OpInvokevirtual offset) 3 (List.rev (t1 :: tl)) tr
+
+	method invokeinterface offset t1 tl tr =
+		let count = List.fold_left (fun count jsig -> count + signature_size jsig) 1 tl in
+		self#op (OpInvokeinterface(offset,count)) 5 (List.rev (t1 :: tl)) tr
+
+	method invokespecial offset t1 tl tr =
+		self#op (OpInvokespecial offset) 3 (List.rev (t1 :: tl)) tr
+
+	method invokedynamic offset tl tr =
+		self#op (OpInvokedynamic(offset)) 5 (List.rev tl) tr
+
+	method new_ offset =
+		self#op (OpNew offset) 3 [] [TUninitialized (Some fp)]
+
+	(* return *)
+
+	method return_void =
+		self#op OpReturn 1 [] []
+
+	method return_value t =
+		let op = match t with
+		| TInt | TBool | TByte | TChar | TShort -> OpIreturn
+		| TLong -> OpLreturn
+		| TFloat -> OpFreturn
+		| TDouble -> OpDreturn
+		| _ -> OpAreturn
+		in
+		self#op op 1 [t] []
+
+	method athrow =
+		self#op OpAthrow 1 [stack#top] []
+
+	(* control flow *)
+
+	method if_ cmp r = self#op (OpIf(cmp,r)) 3 [TBool] []
+	method if_ref cmp = let r = ref fp in self#if_ cmp r; r
+
+	method if_icmp cmp r = self#op (OpIf_icmp(cmp,r)) 3 [TInt;TInt] []
+	method if_icmp_ref cmp = let r = ref fp in self#if_icmp cmp r; r
+
+	method if_acmp_eq t1 t2 r = self#op (OpIf_acmpeq r) 3 [t1;t2] []
+	method if_acmp_eq_ref t1 t2 = let r = ref fp in self#if_acmp_eq t1 t2 r; r
+
+	method if_acmp_ne t1 t2 r = self#op (OpIf_acmpne r) 3 [t1;t2] []
+	method if_acmp_ne_ref t1 t2 = let r = ref fp in self#if_acmp_ne t1 t2 r; r
+
+	method if_null t r = self#op (OpIfnull r) 3 [t] []
+	method if_null_ref t = let r = ref fp in self#if_null t r; r
+
+	method if_nonnull t r = self#op (OpIfnonnull r) 3 [t] []
+	method if_nonnull_ref t = let r = ref fp in self#if_nonnull t r; r
+
+	method goto r = self#op (OpGoto r) 3 [] []
+
+	method lookupswitch offset_def offsets =
+		let pad = (fp + 1) mod 4 in
+		let pad = if pad = 0 then pad else 4 - pad in
+		self#op (OpLookupswitch(pad,offset_def,offsets)) (9 + pad + (Array.length offsets * 8)) [TInt] []
+
+	method tableswitch offset_def low high offsets =
+		let pad = (fp + 1) mod 4 in
+		let pad = if pad = 0 then pad else 4 - pad in
+		self#op (OpTableswitch(pad,offset_def,low,high,offsets)) (13 + pad + (Array.length offsets * 4)) [TInt] []
+
+	(* compare *)
+
+	method dcmpg = self#op OpDcmpg 1 [TDouble;TDouble] [TInt]
+	method dcmpl = self#op OpDcmpl 1 [TDouble;TDouble] [TInt]
+
+	method fcmpg = self#op OpFcmpg 1 [TFloat;TFloat] [TInt]
+	method fcmpl = self#op OpFcmpl 1 [TFloat;TFloat] [TInt]
+
+	method lcmpl = self#op OpLcmp 1 [TLong;TLong] [TInt]
+
+	(* ops *)
+
+	method iadd = self#op OpIadd 1 [TInt;TInt] [TInt]
+	method isub = self#op OpIsub 1 [TInt;TInt] [TInt]
+	method imul = self#op OpImul 1 [TInt;TInt] [TInt]
+	method iand = self#op OpIand 1 [TInt;TInt] [TInt]
+	method ior = self#op OpIor 1 [TInt;TInt] [TInt]
+	method ixor = self#op OpIxor 1 [TInt;TInt] [TInt]
+	method ishl = self#op OpIshl 1 [TInt;TInt] [TInt]
+	method ishr = self#op OpIshr 1 [TInt;TInt] [TInt]
+	method iushr = self#op OpIushr 1 [TInt;TInt] [TInt]
+	method irem = self#op OpIrem 1 [TInt;TInt] [TInt]
+	method ineg = self#op OpIneg 1 [TInt] [TInt]
+
+	method ladd = self#op OpLadd 1 [TLong;TLong] [TLong]
+	method lsub = self#op OpLsub 1 [TLong;TLong] [TLong]
+	method lmul = self#op OpLmul 1 [TLong;TLong] [TLong]
+	method ldiv = self#op OpLdiv 1 [TLong;TLong] [TLong]
+	method land_ = self#op OpLand 1 [TLong;TLong] [TLong]
+	method lor_ = self#op OpLor 1 [TLong;TLong] [TLong]
+	method lxor_ = self#op OpLxor 1 [TLong;TLong] [TLong]
+	method lshl = self#op OpLshl 1 [TInt;TLong] [TLong]
+	method lshr = self#op OpLshr 1 [TInt;TLong] [TLong]
+	method lushr = self#op OpLushr 1 [TInt;TLong] [TLong]
+	method lrem = self#op OpLrem 1 [TLong;TLong] [TLong]
+	method lneg = self#op OpLneg 1 [TLong] [TLong]
+
+	method fadd = self#op OpFadd 1 [TFloat;TFloat] [TFloat]
+	method fsub = self#op OpFsub 1 [TFloat;TFloat] [TFloat]
+	method fmul = self#op OpFmul 1 [TFloat;TFloat] [TFloat]
+	method fdiv = self#op OpFdiv 1 [TFloat;TFloat] [TFloat]
+	method frem = self#op OpFrem 1 [TFloat;TFloat] [TFloat]
+	method fneg = self#op OpFneg 1 [TFloat] [TFloat]
+
+	method dadd = self#op OpDadd 1 [TDouble;TDouble] [TDouble]
+	method dsub = self#op OpDsub 1 [TDouble;TDouble] [TDouble]
+	method dmul = self#op OpDmul 1 [TDouble;TDouble] [TDouble]
+	method ddiv = self#op OpDdiv 1 [TDouble;TDouble] [TDouble]
+	method drem = self#op OpDrem 1 [TDouble;TDouble] [TDouble]
+	method dneg = self#op OpDneg 1 [TDouble] [TDouble]
+
+	(* stack *)
+
+	method pop =
+		let top = stack#top in
+		self#op (if signature_size top = 1 then OpPop else OpPop2) 1 [top] []
+
+	method dup = match stack#top with
+		| TLong | TDouble -> self#op OpDup2 1 [] [stack#top]
+		| _ -> self#op OpDup 1 [] [stack#top]
+
+	method dup_x1 =
+		let tl = stack#get_stack_items 2 in
+		let tl2 = (List.hd tl :: (List.rev tl)) in
+		match tl with
+		| (TLong | TDouble) :: _ -> self#op OpDup2_x1 1 tl tl2
+		| _ ->  self#op OpDup_x1 1 tl tl2
+
+	method dup_x2 =
+		let tl = stack#get_stack_items 3 in
+		let sizes = List.map signature_size tl in
+		match sizes,tl with
+		| 2 :: 2 :: _,vt1 :: vt2 :: _ -> self#op OpDup2_x2 1 [vt1;vt2] [vt1;vt2;vt1]
+		| 1 :: 2 :: _,vt1 :: vt2 :: _ -> self#op OpDup_x2 1 [vt1;vt2] [vt1;vt2;vt1]
+		| 1 :: 1 :: 1 :: _,vt1 :: vt2 :: vt3 :: _ -> self#op OpDup_x2 1 tl [vt1;vt2;vt3;vt1]
+		| 2 :: 1 :: 1 :: _,vt1 :: vt2 :: vt3 :: _ -> self#op OpDup2_x2 1 tl [vt1;vt2;vt3;vt1]
+		| _ -> jerror "???"
+
+	method swap =
+		let tl = stack#get_stack_items 2 in
+		self#op OpSwap 1 tl (List.rev tl)
+
+	method checkcast_sig jsig =
+		let offset = pool#add_type (generate_signature false jsig) in
+		self#op (OpCheckcast offset) 3 [stack#top] [jsig]
+
+	method checkcast path =
+		let offset = pool#add_path path in
+		let jsig = match path with
+			| [],"Int" -> TInt
+			| ["haxe" | "java"],"Int64" -> TLong
+			| _ -> TObject(path,[])
+		in
+		self#op (OpCheckcast offset) 3 [stack#top] [jsig]
+
+	method instanceof path =
+		let offset = pool#add_path path in
+		self#op (OpInstanceof offset) 3 [stack#top] [TInt]
+
+	(* monitor *)
+
+	method monitorenter =
+		self#op OpMonitorenter 1 [stack#top] []
+
+	method monitorexit =
+		self#op OpMonitorexit 1 [stack#top] []
+
+	(* constants *)
+
+	method aconst_null t = self#op OpAconst_null 1 [] [t]
+
+	method ldc offset vt =
+		if offset < 0xFF then
+			self#op (OpLdc offset) 2 [] [vt]
+		else
+			self#op (OpLdc_w offset) 3 [] [vt]
+
+	method bconst b =
+		self#op (if b then OpIconst_1 else OpIconst_0) 1 [] [TBool]
+
+	method iconst i32 =
+		let instr,count = match Int32.to_int i32 with
+			| -1 -> OpIconst_m1,1
+			| 0 -> OpIconst_0,1
+			| 1 -> OpIconst_1,1
+			| 2 -> OpIconst_2,1
+			| 3 -> OpIconst_3,1
+			| 4 -> OpIconst_4,1
+			| 5 -> OpIconst_5,1
+			| i ->
+				match get_numeric_range i with
+				| Int8Range ->
+					OpBipush i,2
+				| Int16Range ->
+					OpSipush i,3
+				| Int32Range ->
+					let offset = pool#add (ConstInt i32) in
+					if offset < 0xFF then
+						OpLdc offset,2
+					else
+						OpLdc_w offset,3
+		in
+		self#op instr count [] [TInt]
+
+	method dconst f =
+		let instr,count = match f with
+			| 0.0 -> OpDconst_0,1
+			| 1.0 -> OpDconst_1,1
+			| _ ->
+				let offset = pool#add (ConstDouble f) in
+				OpLdc2_w offset,3
+		in
+		self#op instr count [] [TDouble]
+
+	method lconst d =
+		let instr,count = if d = Int64.zero then
+			OpLconst_0,1
+		else if d = Int64.one then
+			OpLconst_1,1
+		else begin
+			let offset = pool#add (ConstLong d) in
+			OpLdc2_w offset,3
+		end in
+		self#op instr count [] [TLong]
+
+	method fconst f =
+		let instr,count = match f with
+			| 0.0 -> OpFconst_0,1
+			| 1.0 -> OpFconst_1,1
+			| 2.0 -> OpFconst_2,1
+			| _ ->
+				let offset = pool#add (ConstFloat f) in
+				OpLdc_w offset,3
+		in
+		self#op instr count [] [TFloat]
+
+	method sconst t offset =
+		self#ldc offset t
+
+	method get_pool : JvmConstantPool.constant_pool = pool
+	method get_fp = fp
+	method get_ops = ops
+	method get_stack = stack
+	method get_max_stack_size = stack#get_max_stack_size
+
+	method get_lines = lines
+	method set_line line = current_line <- line
+
+	method export_code =
+		let ch = IO.output_bytes () in
+		DynArray.iter (JvmWriter.write_opcode ch) ops;
+		IO.close_out ch
+end

+ 173 - 0
src/generators/jvm/jvmConstantPool.ml

@@ -0,0 +1,173 @@
+open IO
+open IO.BigEndian
+open JvmGlobals
+open JvmSignature
+
+(* High-level constant pool *)
+
+let utf8jvm (input : string) : bytes =
+	let channel = IO.output_bytes () in
+	UTF8.iter (fun c ->
+		let code = UChar.code c in
+		match code with
+			| b when (b > 0 && b <= 0x7F) ->
+			IO.write_byte channel b
+			(* includes null byte: *)
+			| b when (b <= 0x7FF) ->
+			IO.write_byte channel (0xC0 lor ((b lsr  6)          ));
+			IO.write_byte channel (0x80 lor ((b       ) land 0x3F))
+			| b when (b <= 0xFFFF) ->
+			IO.write_byte channel (0xE0 lor ((b lsr 12)          ));
+			IO.write_byte channel (0x80 lor ((b lsr  6) land 0x3F));
+			IO.write_byte channel (0x80 lor ((b       ) land 0x3F))
+			| b ->
+			IO.write_byte channel 0xED;
+			IO.write_byte channel (0xA0 lor ((b lsr 16) - 1      ));
+			IO.write_byte channel (0x80 lor ((b lsr 10) land 0x3F));
+			IO.write_byte channel 0xED;
+			IO.write_byte channel (0xB0 lor ((b lsr  6) land 0x0F));
+			IO.write_byte channel (0x80 lor ((b       ) land 0x3F))
+	) input;
+	IO.close_out channel
+;;
+
+class constant_pool = object(self)
+	val pool = DynArray.create ();
+	val lut = Hashtbl.create 0;
+	val luti = Hashtbl.create 0;
+	val mutable next_index = 1;
+	val mutable closed = false
+	val inner_classes = Hashtbl.create 0
+
+	method add const =
+		try
+			Hashtbl.find lut const
+		with Not_found ->
+			assert (not closed);
+			let i = next_index in
+			next_index <- next_index + 1;
+			DynArray.add pool const;
+			Hashtbl.add lut const i;
+			Hashtbl.add luti i (DynArray.length pool - 1);
+			match const with
+			| ConstDouble _ | ConstLong _ ->
+				next_index <- next_index + 1;
+				i
+			| _ ->
+				i
+
+	method get i =
+		DynArray.get pool (Hashtbl.find luti i)
+
+	method private s_type_path (p,s) = match p with [] -> s | _ -> String.concat "/" p ^ "/" ^ s
+
+	method add_type s =
+		let offset = self#add (ConstUtf8 s) in
+		self#add (ConstClass offset);
+
+	method add_path path =
+		let s = self#s_type_path path in
+		let offset = self#add_type s in
+		if String.contains (snd path) '$' then begin
+			let name1,name2 = ExtString.String.split (snd path) "$" in
+			Hashtbl.replace inner_classes ((fst path,name1),name2) offset;
+		end;
+		offset
+
+	method add_string s =
+		self#add (ConstUtf8 s)
+
+	method add_const_string s =
+		let offset = self#add_string s in
+		self#add (ConstString offset)
+
+	method add_name_and_type name jsig field_kind =
+		let offset_name = self#add_string name in
+		let offset_desc = self#add_string ((if field_kind = FKField then generate_signature else generate_method_signature) false jsig) in
+		self#add (ConstNameAndType(offset_name,offset_desc))
+
+	method add_field path name jsig field_kind =
+		let offset_class = self#add_path path in
+		let offset_info = self#add_name_and_type name jsig field_kind in
+		let const = match field_kind with
+			| FKField -> ConstFieldref(offset_class,offset_info)
+			| FKMethod -> ConstMethodref(offset_class,offset_info)
+			| FKInterfaceMethod -> ConstInterfaceMethodref(offset_class,offset_info)
+		in
+		self#add const
+
+	method get_inner_classes = inner_classes
+
+	method private write_i64 ch i64 =
+		write_real_i32 ch (Int64.to_int32 i64);
+		write_real_i32 ch (Int64.to_int32 (Int64.shift_right_logical i64 32))
+
+	method private write ch =
+		write_ui16 ch next_index;
+		DynArray.iter (function
+			| ConstUtf8 s ->
+				write_byte ch 1;
+				let b = utf8jvm s in
+				write_ui16 ch (Bytes.length b);
+				nwrite ch b
+			| ConstInt i32 ->
+				write_byte ch 3;
+				write_real_i32 ch i32;
+			| ConstFloat f ->
+				write_byte ch 4;
+				(match classify_float f with
+				| FP_normal | FP_subnormal | FP_zero ->
+					write_real_i32 ch (Int32.bits_of_float f)
+				| FP_infinite when f > 0.0 ->
+					write_real_i32 ch 0x7f800000l
+				| FP_infinite ->
+					write_real_i32 ch 0xff800000l
+				| FP_nan ->
+					write_real_i32 ch 0x7f800001l)
+			| ConstLong i64 ->
+				write_byte ch 5;
+				write_i64 ch i64;
+			| ConstDouble d ->
+				write_byte ch 6;
+				write_double ch d
+			| ConstClass offset ->
+				write_byte ch 7;
+				write_ui16 ch offset;
+			| ConstString offset ->
+				write_byte ch 8;
+				write_ui16 ch offset;
+			| ConstFieldref (offset1,offset2) ->
+				write_byte ch 9;
+				write_ui16 ch offset1;
+				write_ui16 ch offset2;
+			| ConstMethodref (offset1,offset2) ->
+				write_byte ch 10;
+				write_ui16 ch offset1;
+				write_ui16 ch offset2;
+			| ConstInterfaceMethodref (offset1,offset2) ->
+				write_byte ch 11;
+				write_ui16 ch offset1;
+				write_ui16 ch offset2;
+			| ConstNameAndType (offset1,offset2) ->
+				write_byte ch 12;
+				write_ui16 ch offset1;
+				write_ui16 ch offset2;
+			| ConstMethodHandle (i,offset) ->
+				write_byte ch 15;
+				write_byte ch i;
+				write_ui16 ch offset;
+			| ConstMethodType offset ->
+				write_byte ch 16;
+				write_ui16 ch offset;
+			| ConstInvokeDynamic (offset1,offset2) ->
+				write_byte ch 18;
+				write_ui16 ch offset1;
+				write_ui16 ch offset2;
+		) pool
+
+	method close =
+		closed <- true;
+		let ch = IO.output_bytes () in
+		self#write ch;
+		IO.close_out ch
+end

+ 275 - 0
src/generators/jvm/jvmData.ml

@@ -0,0 +1,275 @@
+open JvmGlobals
+
+(* Low-level data that is written out. *)
+
+type jvm_attribute = {
+	attr_index : jvm_constant_pool_index;
+	attr_data  : bytes;
+}
+
+type jvm_field = {
+	field_access_flags     : int;
+	field_name_index       : int;
+	field_descriptor_index : int;
+	field_attributes       : jvm_attribute array;
+}
+
+type jvm_class = {
+	class_minor_version : int;
+	class_major_version : int;
+	class_constant_pool : bytes;
+	class_access_flags  : int;
+	class_this_class    : int;
+	class_super_class   : int;
+	class_interfaces    : int array;
+	class_fields        : jvm_field array;
+	class_methods       : jvm_field array;
+	class_attributes    : jvm_attribute array;
+}
+
+type jvm_exception = {
+	exc_start_pc   : int;
+	exc_end_pc     : int;
+	exc_handler_pc : int;
+	exc_catch_type : jvm_constant_pool_index option;
+}
+
+type jvm_code = {
+	code_max_stack  : int;
+	code_max_locals : int;
+	code_code       : bytes;
+	code_exceptions : jvm_exception array;
+	code_attributes : jvm_attribute array;
+}
+
+(* Opcodes *)
+
+type jbyte = int
+type jshort = int
+type jbranchoffset = int
+
+type jcmp =
+	| CmpEq
+	| CmpNe
+	| CmpLt
+	| CmpGe
+	| CmpGt
+	| CmpLe
+
+type opwide =
+	| OpWIinc of jshort * jshort
+	| OpWIload of jshort
+	| OpWFload of jshort
+	| OpWAload of jshort
+	| OpWLload of jshort
+	| OpWDload of jshort
+	| OpWIstore of jshort
+	| OpWFstore of jshort
+	| OpWAstore of jshort
+	| OpWLstore of jshort
+	| OpWDstore of jshort
+
+and jopcode =
+	(* double *)
+	| OpD2f
+	| OpD2i
+	| OpD2l
+	| OpDadd
+	| OpDaload
+	| OpDastore
+	| OpDcmpg
+	| OpDcmpl
+	| OpDdiv
+	| OpDconst_0
+	| OpDconst_1
+	| OpDload_0
+	| OpDload_1
+	| OpDload_2
+	| OpDload_3
+	| OpDload of jbyte
+	| OpDmul
+	| OpDneg
+	| OpDrem
+	| OpDreturn
+	| OpDstore_0
+	| OpDstore_1
+	| OpDstore_2
+	| OpDstore_3
+	| OpDstore of jbyte
+	| OpDsub
+	(* float *)
+	| OpF2d
+	| OpF2i
+	| OpF2l
+	| OpFadd
+	| OpFaload
+	| OpFastore
+	| OpFcmpg
+	| OpFcmpl
+	| OpFdiv
+	| OpFconst_0
+	| OpFconst_1
+	| OpFconst_2
+	| OpFload_0
+	| OpFload_1
+	| OpFload_2
+	| OpFload_3
+	| OpFload of jbyte
+	| OpFmul
+	| OpFneg
+	| OpFrem
+	| OpFreturn
+	| OpFstore_0
+	| OpFstore_1
+	| OpFstore_2
+	| OpFstore_3
+	| OpFstore of jbyte
+	| OpFsub
+	(* int *)
+	| OpI2b
+	| OpI2c
+	| OpI2d
+	| OpI2f
+	| OpI2l
+	| OpI2s
+	| OpIadd
+	| OpIaload
+	| OpIand
+	| OpIastore
+	| OpIconst_m1
+	| OpIconst_0
+	| OpIconst_1
+	| OpIconst_2
+	| OpIconst_3
+	| OpIconst_4
+	| OpIconst_5
+	| OpIdiv
+	| OpIload_0
+	| OpIload_1
+	| OpIload_2
+	| OpIload_3
+	| OpIload of jbyte
+	| OpImul
+	| OpIneg
+	| OpIor
+	| OpIrem
+	| OpIreturn
+	| OpIshl
+	| OpIshr
+	| OpIstore_0
+	| OpIstore_1
+	| OpIstore_2
+	| OpIstore_3
+	| OpIstore of jbyte
+	| OpIsub
+	| OpIushr
+	| OpIxor
+	(* long *)
+	| OpL2d
+	| OpL2f
+	| OpL2i
+	| OpLadd
+	| OpLaload
+	| OpLand
+	| OpLastore
+	| OpLconst_0
+	| OpLconst_1
+	| OpLcmp
+	| OpLdiv
+	| OpLload_0
+	| OpLload_1
+	| OpLload_2
+	| OpLload_3
+	| OpLload of jbyte
+	| OpLmul
+	| OpLneg
+	| OpLor
+	| OpLrem
+	| OpLreturn
+	| OpLshl
+	| OpLshr
+	| OpLstore_0
+	| OpLstore_1
+	| OpLstore_2
+	| OpLstore_3
+	| OpLstore of jbyte
+	| OpLsub
+	| OpLushr
+	| OpLxor
+	(* short *)
+	| OpSaload
+	| OpSastore
+	| OpSipush of jshort
+	(* array *)
+	| OpAaload
+	| OpAastore
+	| OpAnewarray of jvm_constant_pool_index
+	| OpArraylength
+	| OpBaload
+	| OpBastore
+	| OpBipush of jbyte
+	| OpCaload
+	| OpCastore
+	| OpMultianewarray of jvm_constant_pool_index * jbyte
+	| OpNewarray of jbyte
+	(* reference *)
+	| OpAload_0
+	| OpAload_1
+	| OpAload_2
+	| OpAload_3
+	| OpAload of jbyte
+	| OpAreturn
+	| OpAstore_0
+	| OpAstore_1
+	| OpAstore_2
+	| OpAstore_3
+	| OpAstore of jbyte
+	(* object *)
+	| OpNew of jvm_constant_pool_index
+	| OpInstanceof of jvm_constant_pool_index
+	| OpCheckcast of jvm_constant_pool_index
+	| OpInvokedynamic of jvm_constant_pool_index
+	| OpInvokeinterface of jvm_constant_pool_index * jbyte
+	| OpInvokespecial of jvm_constant_pool_index
+	| OpInvokestatic of jvm_constant_pool_index
+	| OpInvokevirtual of jvm_constant_pool_index
+	| OpGetfield of jvm_constant_pool_index
+	| OpGetstatic of jvm_constant_pool_index
+	| OpPutfield of jvm_constant_pool_index
+	| OpPutstatic of jvm_constant_pool_index
+	(* branching *)
+	| OpIf_acmpeq of jbranchoffset ref
+	| OpIf_acmpne of jbranchoffset ref
+	| OpIf_icmp of jcmp * jbranchoffset ref
+	| OpIf of jcmp * jbranchoffset ref
+	| OpIfnonnull of jbranchoffset ref
+	| OpIfnull of jbranchoffset ref
+	| OpGoto of jbranchoffset ref
+	| OpGoto_w of jbranchoffset ref
+	| OpJsr of jbranchoffset ref
+	| OpJsr_w of jbranchoffset ref
+	(* stack *)
+	| OpAconst_null
+	| OpDup
+	| OpDup_x1
+	| OpDup_x2
+	| OpDup2
+	| OpDup2_x1
+	| OpDup2_x2
+	| OpLdc of jbyte
+	| OpLdc_w of jvm_constant_pool_index
+	| OpLdc2_w of jvm_constant_pool_index
+	| OpNop
+	| OpPop
+	| OpPop2
+	| OpSwap
+	(* other *)
+	| OpAthrow
+	| OpIinc of jbyte * jbyte
+	| OpLookupswitch of int (* num pad bytes *) * jbranchoffset ref (* default *) * (int * jbranchoffset ref) array
+	| OpTableswitch of int (* num pad bytes *) * jbranchoffset ref (* default *) * int (* low *) * int (* high *) * jbranchoffset ref array
+	| OpMonitorenter
+	| OpMonitorexit
+	| OpRet of jbyte
+	| OpReturn
+	| OpWide of opwide

+ 262 - 0
src/generators/jvm/jvmDebug.ml

@@ -0,0 +1,262 @@
+open JvmGlobals
+open JvmData
+
+(* Printing debug functions *)
+
+let s_const pool const =
+	let rec loop depth const =
+		if depth = 3 then
+			"..."
+		else begin
+			let rec_find i = loop (depth + 1) (pool#get i) in
+			match const with
+			| ConstUtf8 s -> Printf.sprintf "Utf8 \"%s\"" s
+			| ConstInt i32 -> Printf.sprintf "Int %i" (Int32.to_int i32)
+			| ConstFloat f -> Printf.sprintf "Float %f" f
+			| ConstLong i64 -> Printf.sprintf "Int %i" (Int64.to_int i64)
+			| ConstDouble f -> Printf.sprintf "Double %f" f
+			| ConstClass i -> Printf.sprintf "Class(%s)" (rec_find i)
+			| ConstString i -> Printf.sprintf "String(%s)" (rec_find i)
+			| ConstFieldref(i1,i2) -> Printf.sprintf "Fieldref(%s, %s)" (rec_find i1) (rec_find i2)
+			| ConstMethodref(i1,i2) -> Printf.sprintf "Methodref(%s, %s)" (rec_find i1) (rec_find i2)
+			| ConstInterfaceMethodref(i1,i2) -> Printf.sprintf "InterfaceMethodref(%s, %s)" (rec_find i1) (rec_find i2)
+			| ConstNameAndType(i1,i2) -> Printf.sprintf "NameAndType(%s, %s)" (rec_find i1) (rec_find i2)
+			| ConstMethodHandle(i1,i2) -> Printf.sprintf "MethodHandle(%i, %s)" i1 (rec_find i2)
+			| ConstMethodType i -> Printf.sprintf "MethodType(%s)" (rec_find i)
+			| ConstInvokeDynamic(i1,i2) -> Printf.sprintf "InvokeDynamic(%i, %s)" i1 (rec_find i2)
+		end
+	in
+	loop 0 const
+
+let s_const_nice pool const =
+	let rec loop depth const =
+		if depth = 3 then
+			"..."
+		else begin
+			let rec_find i = loop (depth + 1) (pool#get i) in
+			match const with
+			| ConstUtf8 s -> Printf.sprintf "%s" s
+			| ConstInt i32 -> Printf.sprintf "%i" (Int32.to_int i32)
+			| ConstFloat f -> Printf.sprintf "%f" f
+			| ConstLong i64 -> Printf.sprintf "%i" (Int64.to_int i64)
+			| ConstDouble f -> Printf.sprintf "%f" f
+			| ConstClass i -> Printf.sprintf "%s" (rec_find i)
+			| ConstString i -> Printf.sprintf "%s" (rec_find i)
+			| ConstFieldref(i1,i2) -> Printf.sprintf "%s.%s" (rec_find i1) (rec_find i2)
+			| ConstMethodref(i1,i2) -> Printf.sprintf "%s.%s" (rec_find i1) (rec_find i2)
+			| ConstInterfaceMethodref(i1,i2) -> Printf.sprintf "%s.%s" (rec_find i1) (rec_find i2)
+			| ConstNameAndType(i1,i2) -> Printf.sprintf "%s:%s" (rec_find i1) (rec_find i2)
+			| ConstMethodHandle(i1,i2) -> Printf.sprintf "MethodHandle(%i, %s)" i1 (rec_find i2)
+			| ConstMethodType i -> Printf.sprintf "MethodType(%s)" (rec_find i)
+			| ConstInvokeDynamic(i1,i2) -> Printf.sprintf "InvokeDynamic(%i, %s)" i1 (rec_find i2)
+		end
+	in
+	loop 0 const
+
+let s_jcode pool code =
+  let wi s i = Printf.sprintf "%s %i" s i in
+  let sc i = s_const_nice pool (pool#get i) in
+  match code with
+  (* double *)
+  | OpD2f -> "d2f"
+  | OpD2i -> "d2i"
+  | OpD2l -> "d2l"
+  | OpDadd -> "dadd"
+  | OpDaload -> "daload"
+  | OpDastore -> "dastore"
+  | OpDcmpg -> "dcmpg"
+  | OpDcmpl -> "dcmpl"
+  | OpDdiv -> "ddiv"
+  | OpDconst_0 -> "dconst_0"
+  | OpDconst_1 -> "dconst_1"
+  | OpDload_0 -> "dload_0"
+  | OpDload_1 -> "dload_1"
+  | OpDload_2 -> "dload_2"
+  | OpDload_3 -> "dload_3"
+  | OpDload i -> wi "dload" i
+  | OpDmul -> "dmul"
+  | OpDneg -> "dneg"
+  | OpDrem -> "drem"
+  | OpDreturn -> "dreturn"
+  | OpDstore_0 -> "dstore_0"
+  | OpDstore_1 -> "dstore_1"
+  | OpDstore_2 -> "dstore_2"
+  | OpDstore_3 -> "dstore_3"
+  | OpDstore i -> wi "dstore" i
+  | OpDsub -> "dsub"
+  (* float *)
+  | OpF2d -> "f2d"
+  | OpF2i -> "f2i"
+  | OpF2l -> "f2l"
+  | OpFadd -> "fadd"
+  | OpFaload -> "faload"
+  | OpFastore -> "fastore"
+  | OpFcmpg -> "fcmpg"
+  | OpFcmpl -> "fcmpl"
+  | OpFdiv -> "fdiv"
+  | OpFconst_0 -> "fconst_0"
+  | OpFconst_1 -> "fconst_1"
+  | OpFconst_2 -> "fconst_2"
+  | OpFload_0 -> "fload_0"
+  | OpFload_1 -> "fload_1"
+  | OpFload_2 -> "fload_2"
+  | OpFload_3 -> "fload_3"
+  | OpFload i -> wi "fload" i
+  | OpFmul -> "fmul"
+  | OpFneg -> "fneg"
+  | OpFrem -> "frem"
+  | OpFreturn -> "freturn"
+  | OpFstore_0 -> "fstore_0"
+  | OpFstore_1 -> "fstore_1"
+  | OpFstore_2 -> "fstore_2"
+  | OpFstore_3 -> "fstore_3"
+  | OpFstore i -> wi "fstore" i
+  | OpFsub -> "fsub"
+  (* int *)
+  | OpI2b -> "i2b"
+  | OpI2c -> "i2c"
+  | OpI2d -> "i2d"
+  | OpI2f -> "i2f"
+  | OpI2l -> "i2l"
+  | OpI2s -> "i2s"
+  | OpIadd -> "iadd"
+  | OpIaload -> "iaload"
+  | OpIand -> "iand"
+  | OpIastore -> "iastore"
+  | OpIconst_m1 -> "iconst_m1"
+  | OpIconst_0 -> "iconst_0"
+  | OpIconst_1 -> "iconst_1"
+  | OpIconst_2 -> "iconst_2"
+  | OpIconst_3 -> "iconst_3"
+  | OpIconst_4 -> "iconst_4"
+  | OpIconst_5 -> "iconst_5"
+  | OpIdiv -> "idiv"
+  | OpIload_0 -> "iload_0"
+  | OpIload_1 -> "iload_1"
+  | OpIload_2 -> "iload_2"
+  | OpIload_3 -> "iload_3"
+  | OpIload i -> wi "iload" i
+  | OpImul -> "imul"
+  | OpIneg -> "ineg"
+  | OpIor -> "ior"
+  | OpIrem -> "irem"
+  | OpIreturn -> "ireturn"
+  | OpIshl -> "ishl"
+  | OpIshr -> "ishr"
+  | OpIstore_0 -> "istore_0"
+  | OpIstore_1 -> "istore_1"
+  | OpIstore_2 -> "istore_2"
+  | OpIstore_3 -> "istore_3"
+  | OpIstore i -> wi "istore" i
+  | OpIsub -> "isub"
+  | OpIushr -> "iushr"
+  | OpIxor -> "ixor"
+  (* long *)
+  | OpL2d -> "l2d"
+  | OpL2f -> "l2f"
+  | OpL2i -> "l2i"
+  | OpLadd -> "ladd"
+  | OpLaload -> "laload"
+  | OpLand -> "land"
+  | OpLastore -> "lastore"
+  | OpLconst_0 -> "lconst_0"
+  | OpLconst_1 -> "lconst_1"
+  | OpLcmp -> "lcmp"
+  | OpLdiv -> "ldiv"
+  | OpLload_0 -> "lload_0"
+  | OpLload_1 -> "lload_1"
+  | OpLload_2 -> "lload_2"
+  | OpLload_3 -> "lload_3"
+  | OpLload i -> wi "lload" i
+  | OpLmul -> "lmul"
+  | OpLneg -> "lneg"
+  | OpLor -> "lor"
+  | OpLrem -> "lrem"
+  | OpLreturn -> "lreturn"
+  | OpLshl -> "lshl"
+  | OpLshr -> "lshr"
+  | OpLstore_0 -> "lstore_0"
+  | OpLstore_1 -> "lstore_1"
+  | OpLstore_2 -> "lstore_2"
+  | OpLstore_3 -> "lstore_3"
+  | OpLstore i -> wi "lstore" i
+  | OpLsub -> "lsub"
+  | OpLushr -> "lushr"
+  | OpLxor -> "lxor"
+  (* short *)
+  | OpSaload -> "saload"
+  | OpSastore -> "sastore"
+  | OpSipush i -> wi "sipush" i
+  (* array *)
+  | OpAaload -> "aaload"
+  | OpAastore -> "aastore"
+  | OpAnewarray offset -> wi "anewarray" offset
+  | OpArraylength -> "arraylength"
+  | OpBaload -> "baload"
+  | OpBastore -> "bastore"
+  | OpBipush i -> wi "bipush" i
+  | OpCaload -> "caload"
+  | OpCastore -> "castore"
+  | OpMultianewarray(path,i) -> "multinewarray" (* TODO *)
+  | OpNewarray(jsig) -> "newarray" (* TODO *)
+  (* reference *)
+  | OpAload_0 -> "aload_0"
+  | OpAload_1 -> "aload_1"
+  | OpAload_2 -> "aload_2"
+  | OpAload_3 -> "aload_3"
+  | OpAload i -> wi "aload" i
+  | OpAreturn -> "areturn"
+  | OpAstore_0 -> "astore_0"
+  | OpAstore_1 -> "astore_1"
+  | OpAstore_2 -> "astore_2"
+  | OpAstore_3 -> "astore_3"
+  | OpAstore i -> wi "astore" i
+  (* object *)
+  | OpNew offset -> wi "new" offset
+  | OpInstanceof offset -> wi "instanceof" offset
+  | OpCheckcast offset -> wi "checkcast" offset
+  | OpInvokedynamic arg -> "invokedynamic"
+  | OpInvokeinterface(arg1,arg2) -> "invokeinterface"
+  | OpInvokespecial arg1 -> Printf.sprintf "invokespecial %s" (sc arg1)
+  | OpInvokestatic arg1 -> Printf.sprintf "invokestatic %s" (sc arg1)
+  | OpInvokevirtual arg1 -> Printf.sprintf "invokevirtual %s" (sc arg1)
+  | OpGetfield arg1 -> Printf.sprintf "getfield %s" (sc arg1)
+  | OpGetstatic arg1 -> Printf.sprintf "getstatic %s" (sc arg1)
+  | OpPutfield arg1 -> Printf.sprintf "putfield %s" (sc arg1)
+  | OpPutstatic arg1 -> Printf.sprintf "putstatic %s" (sc arg1)
+  (* branching *)
+  | OpIf_acmpeq i -> wi "acmpeq" !i
+  | OpIf_acmpne i -> wi "acmpne" !i
+  | OpIf_icmp(cmp,i) -> wi "if_icmp" !i (* TODO *)
+  | OpIf(cmp,i) -> wi "if" !i (* TODO *)
+  | OpIfnonnull i -> wi "ifnotnull" !i
+  | OpIfnull i -> wi "ifnull" !i
+  | OpGoto i -> wi "goto" !i
+  | OpGoto_w i -> wi "goto_w" !i
+  | OpJsr i -> wi "jsr" !i
+  | OpJsr_w i -> wi "jsr_w" !i
+  (* stack *)
+  | OpAconst_null -> "aconst_null"
+  | OpDup -> "dup"
+  | OpDup_x1 -> "dup_x1"
+  | OpDup_x2 -> "dup_x2"
+  | OpDup2 -> "dup2"
+  | OpDup2_x1 -> "dup2_x1"
+  | OpDup2_x2 -> "dup2_x2"
+  | OpLdc i -> wi "ldc" i
+  | OpLdc_w i -> wi "ldc_w" i
+  | OpLdc2_w i -> wi "ldc2_w" i
+  | OpNop -> "nop"
+  | OpPop -> "pop"
+  | OpPop2 -> "pop2"
+  | OpSwap -> "swap"
+  (* other *)
+  | OpAthrow -> "athrow"
+  | OpIinc(i1,i2) -> wi "iinc" i1 (* TODO *)
+  | OpLookupswitch _ -> "lookupswitch"
+  | OpMonitorenter -> "monitorenter"
+  | OpMonitorexit -> "monitorexit"
+  | OpRet i -> wi "ret" i
+  | OpReturn -> "return"
+  | OpTableswitch _ -> "tableswitch"
+  | OpWide _ -> "wide"

+ 145 - 0
src/generators/jvm/jvmGlobals.ml

@@ -0,0 +1,145 @@
+type jvm_constant_pool_index = int
+
+(* Constants *)
+
+type jvm_bootstrap_method_index = int
+
+type jvm_constant =
+	| ConstUtf8 of string
+	| ConstInt of int32
+	| ConstFloat of float
+	| ConstLong of int64
+	| ConstDouble of float
+	| ConstClass of jvm_constant_pool_index
+	| ConstString of jvm_constant_pool_index
+	| ConstFieldref of jvm_constant_pool_index * jvm_constant_pool_index
+	| ConstMethodref of jvm_constant_pool_index * jvm_constant_pool_index
+	| ConstInterfaceMethodref of jvm_constant_pool_index * jvm_constant_pool_index
+	| ConstNameAndType of jvm_constant_pool_index * jvm_constant_pool_index
+	| ConstMethodHandle of int * jvm_constant_pool_index
+	| ConstMethodType of jvm_constant_pool_index
+	| ConstInvokeDynamic of jvm_bootstrap_method_index * jvm_constant_pool_index
+
+type field_kind =
+	| FKField
+	| FKMethod
+	| FKInterfaceMethod
+
+type numeric_range =
+	| Int8Range
+	| Int16Range
+	| Int32Range
+
+let get_numeric_range i =
+	if i >= -128 && i <= 127 then Int8Range
+	else if i >= -32768 && i <= 32767 then Int16Range
+	else Int32Range
+
+let get_numeric_range_unsigned i =
+	if i <= 0xFF then Int8Range
+	else if i <= 0xFFFF then Int16Range
+	else Int32Range
+
+let in_range unsigned range i = match (if unsigned then get_numeric_range_unsigned else get_numeric_range) i,range with
+	| Int8Range,(Int8Range | Int16Range | Int32Range) -> true
+	| Int16Range,(Int16Range | Int32Range) -> true
+	| Int32Range,Int32Range -> true
+	| _ -> false
+
+let jerror s =
+	failwith s
+
+module ClassAccessFlags = struct
+	type t =
+		| CPublic
+		| CFinal
+		| CSuper
+		| CInterface
+		| CAbstract
+		| CSynthetic
+		| CAnnotation
+		| CEnum
+
+	let to_int = function
+		| CPublic -> 0x1
+		| CFinal -> 0x10
+		| CSuper -> 0x20
+		| CInterface -> 0x200
+		| CAbstract -> 0x400
+		| CSynthetic -> 0x1000
+		| CAnnotation -> 0x2000
+		| CEnum -> 0x4000
+
+	let has_flag b flag =
+		b land (to_int flag) <> 0
+end
+
+module MethodAccessFlags = struct
+	type t =
+		| MPublic
+		| MPrivate
+		| MProtected
+		| MStatic
+		| MFinal
+		| MSynchronized
+		| MBridge
+		| MVarargs
+		| MNative
+		| MAbstract
+		| MStrict
+		| MSynthetic
+
+	let to_int = function
+		| MPublic -> 0x1
+		| MPrivate -> 0x2
+		| MProtected -> 0x4
+		| MStatic -> 0x8
+		| MFinal -> 0x10
+		| MSynchronized -> 0x20
+		| MBridge -> 0x40
+		| MVarargs -> 0x80
+		| MNative -> 0x100
+		| MAbstract -> 0x400
+		| MStrict -> 0x800
+		| MSynthetic -> 0x1000
+
+	let has_flag b flag =
+		b land (to_int flag) <> 0
+end
+
+module FieldAccessFlags = struct
+	type t =
+		| FdPublic
+		| FdPrivate
+		| FdProtected
+		| FdStatic
+		| FdFinal
+		| FdVolatile
+		| FdTransient
+		| FdSynthetic
+		| FdEnum
+
+	let to_int = function
+		| FdPublic -> 0x1
+		| FdPrivate -> 0x2
+		| FdProtected -> 0x4
+		| FdStatic -> 0x8
+		| FdFinal -> 0x10
+		| FdVolatile -> 0x40
+		| FdTransient -> 0x80
+		| FdSynthetic -> 0x1000
+		| FdEnum -> 0x4000
+
+	let has_flag b flag =
+		b land (to_int flag) <> 0
+end
+
+let write_byte ch i = IO.write_byte ch i
+let write_bytes ch b = IO.nwrite ch b
+let write_ui16 ch i = IO.BigEndian.write_ui16 ch i
+let write_ui32 ch i = IO.BigEndian.write_real_i32 ch (Int32.of_int i)
+let write_string ch s = IO.nwrite_string ch s
+
+let write_array16 ch f a =
+	write_ui16 ch (Array.length a);
+	Array.iter (f ch) a

+ 824 - 0
src/generators/jvm/jvmMethod.ml

@@ -0,0 +1,824 @@
+open Globals
+open JvmGlobals
+open JvmData
+open JvmAttribute
+open JvmSignature
+open JvmSignature.NativeSignatures
+open JvmBuilder
+
+(* High-level method builder. *)
+
+type var_init_state =
+	| VarArgument
+	| VarWillInit
+	| VarNeedDefault
+
+type construction_kind =
+	| ConstructInitPlusNew
+	| ConstructInit
+
+module NativeArray = struct
+	let read code ja je = match je with
+		| TBool -> code#baload TBool ja
+		| TByte -> code#baload TByte ja
+		| TChar -> code#caload ja
+		| TDouble -> code#daload ja
+		| TFloat -> code#faload ja
+		| TInt -> code#iaload ja
+		| TLong -> code#laload ja
+		| TShort -> code#saload ja
+		| _ -> code#aaload ja je
+
+	let write code ja je = match je with
+		| TBool -> code#bastore TBool ja
+		| TByte -> code#bastore TByte ja
+		| TChar -> code#castore ja
+		| TDouble -> code#dastore ja
+		| TFloat -> code#fastore ja
+		| TInt -> code#iastore ja
+		| TLong -> code#lastore ja
+		| TShort -> code#sastore ja
+		| _ -> code#aastore ja je
+
+	let create code pool je =
+		let ja = (TArray(je,None)) in
+		let primitive i =
+			code#newarray ja i
+		in
+		let reference path =
+			let offset = pool#add_path path in
+			code#anewarray ja offset;
+		in
+		begin match je with
+		| TBool -> primitive 4
+		| TChar -> primitive 5
+		| TFloat -> primitive 6
+		| TDouble -> primitive 7
+		| TByte -> primitive 8
+		| TShort -> primitive 9
+		| TInt -> primitive 10
+		| TLong -> primitive 11
+		| TObject(path,_) -> reference path
+		| TMethod _ -> reference NativeSignatures.method_handle_path
+		| TTypeParameter _ -> reference NativeSignatures.object_path
+		| TArray _ ->
+			let offset = pool#add_type (generate_signature false je) in
+			code#anewarray ja offset
+		| TObjectInner _ | TUninitialized _ -> assert false
+		end;
+		ja
+end
+
+class builder jc name jsig = object(self)
+	inherit base_builder
+	val code = new JvmCode.builder jc#get_pool
+
+	val mutable max_num_locals = 0
+	val mutable debug_locals = []
+	val mutable stack_frames = []
+	val mutable exceptions = []
+	val mutable argument_locals = []
+	val mutable thrown_exceptions = Hashtbl.create 0
+
+	(* per-branch *)
+	val mutable terminated = false
+
+	(* per-frame *)
+	val mutable locals = []
+	val mutable local_offset = 0
+
+	method has_method_flag flag =
+		MethodAccessFlags.has_flag access_flags flag
+
+	(** Pushes a new scope onto the stack. Returns a function which when called reverts to the previous state. **)
+	method push_scope =
+		let old_locals = locals in
+		let old_offset = local_offset in
+		(fun () ->
+			let delta = local_offset - old_offset in
+			let fp_end = code#get_fp in
+			let rec loop i l =
+				if i = 0 then
+					()
+				else begin match l with
+					| (fpo,name,t) :: l ->
+						let fp = match !fpo with
+							| None -> failwith ("Uninitialized local " ^ name);
+							| Some fp -> fp
+						in
+						let ld = {
+							ld_start_pc = fp;
+							ld_length = fp_end - fp;
+							ld_name_index = jc#get_pool#add_string name;
+							ld_descriptor_index = jc#get_pool#add_string (generate_signature false t);
+							ld_index = old_offset + i - 1;
+						} in
+						debug_locals <- ld :: debug_locals;
+						loop (i - (signature_size t)) l
+					| [] ->
+						assert false
+				end
+			in
+			loop delta locals;
+			locals <- old_locals;
+			local_offset <- old_offset;
+		)
+
+	method private get_locals_for_stack_frame locals =
+		List.map (fun (init,_,t) ->
+			match !init with
+			| None -> JvmVerificationTypeInfo.VTop
+			| _ -> JvmVerificationTypeInfo.of_signature jc#get_pool t
+		) locals
+
+	(** Adds the current state of locals and stack as a stack frame. This has to be called on every branch target. **)
+	method add_stack_frame =
+		let locals = self#get_locals_for_stack_frame locals in
+		let astack = List.map (JvmVerificationTypeInfo.of_signature jc#get_pool) (code#get_stack#get_stack) in
+		let r = code#get_fp in
+		let ff = (r,locals,astack) in
+		(* If we already have a frame at the same position, overwrite it. This can happen in the case of nested branches. *)
+		stack_frames <- (match stack_frames with
+			| (r',_,_) :: stack_frames when r' = r -> ff :: stack_frames
+			| _ -> ff :: stack_frames)
+
+	(** Adds [exc] as an exception. This will be added to the Code attribute. **)
+	method add_exception (exc : jvm_exception) =
+		exceptions <- exc :: exceptions
+
+	(** Adds [path] as a thrown exception for this method. Deals with duplicates. **)
+	method add_thrown_exception (path : jpath) =
+		Hashtbl.replace thrown_exceptions (jc#get_pool#add_path path) true
+
+	(* Convenience *)
+
+	(** Adds [s] as a string constant to the constant pool and emits an instruction to load it. **)
+	method string s =
+		let offset = jc#get_pool#add_const_string s in
+		code#sconst (string_sig) offset
+
+	(** Emits an invokevirtual instruction to invoke method [name] on [path] with signature [jsigm]. **)
+	method invokevirtual (path : jpath) (name : string) (jsigm : jsignature) = match jsigm with
+		| TMethod(tl,tr) ->
+			let offset = code#get_pool#add_field path name jsigm FKMethod in
+			code#invokevirtual offset (object_path_sig path) tl (match tr with None -> [] | Some tr -> [tr])
+		| _ -> assert false
+
+	(** Emits an invokeinterface instruction to invoke method [name] on [path] with signature [jsigm]. **)
+	method invokeinterface (path : jpath) (name : string) (jsigm : jsignature) = match jsigm with
+		| TMethod(tl,tr) ->
+			let offset = code#get_pool#add_field path name jsigm FKInterfaceMethod in
+			code#invokeinterface offset (object_path_sig path) tl (match tr with None -> [] | Some tr -> [tr])
+		| _ -> assert false
+
+	(** Emits an invokespecial instruction to invoke method [name] on [path] with signature [jsigm]. **)
+	method invokespecial (path : jpath) (name : string) (jsigm : jsignature) = match jsigm with
+		| TMethod(tl,tr) ->
+			let offset = code#get_pool#add_field path name jsigm FKMethod in
+			code#invokespecial offset (object_path_sig path) tl (match tr with None -> [] | Some tr -> [tr])
+		| _ -> assert false
+
+	(** Emits an invokestatic instruction to invoke method [name] on [path] with signature [jsigm]. **)
+	method invokestatic (path : jpath) (name : string) (jsigm : jsignature) = match jsigm with
+		| TMethod(tl,tr) ->
+			let offset = code#get_pool#add_field path name jsigm FKMethod in
+			code#invokestatic offset tl (match tr with None -> [] | Some tr -> [tr])
+		| _ -> assert false
+
+	(** Emits a getfield instruction to get the value of field [name] on object [path] with signature [jsigf]. **)
+	method getfield (path : jpath) (name : string) (jsigf : jsignature) =
+		let offset = code#get_pool#add_field path name jsigf FKField in
+		code#getfield offset (object_path_sig path) jsigf
+
+	(** Emits a putfield instruction to set the value of field [name] on object [path] with signature [jsigf]. **)
+	method putfield (path : jpath) (name : string) (jsigf : jsignature) =
+		let offset = code#get_pool#add_field path name jsigf FKField in
+		code#putfield offset (object_path_sig path) jsigf
+
+	(** Emits a getstatic instruction to get the value of field [name] on Class [path] with signature [jsigf]. **)
+	method getstatic (path : jpath) (name : string) (jsigf : jsignature) =
+		let offset = code#get_pool#add_field path name jsigf FKField in
+		code#getstatic offset jsigf
+
+	(** Emits a putstatic instruction to set the value of field [name] on Class [path] with signature [jsigf]. **)
+	method putstatic (path : jpath) (name : string) (jsigf : jsignature) =
+		let offset = code#get_pool#add_field path name jsigf FKField in
+		code#putstatic offset jsigf
+
+	(** Loads `this` **)
+	method load_this =
+		code#aload self#get_this_sig 0
+
+	(** Calls the parent constructor with signature [jsig_method] using the specified construction_kind [kind]. **)
+	method call_super_ctor (kind : construction_kind) (jsig_method : jsignature) =
+		assert (not (self#has_method_flag MStatic));
+		match kind with
+		| ConstructInitPlusNew ->
+			self#invokespecial jc#get_super_path "new" jsig_method;
+		| ConstructInit ->
+			self#invokespecial jc#get_super_path "<init>" jsig_method;
+			self#set_this_initialized
+
+	(** Adds a field named [name] with signature [jsig_field] to the enclosing class, and adds an argument with the same name
+	    to this method. The argument value is loaded and stored into the field immediately. **)
+	method add_argument_and_field (name : string) (jsig_field : jsignature) =
+		assert (not (self#has_method_flag MStatic));
+		let jf = new builder jc name jsig_field in
+		jf#add_access_flag 1;
+		jc#add_field jf#export_field;
+		let _,load,_ = self#add_local name jsig_field VarArgument in
+		self#load_this;
+		load();
+		self#putfield jc#get_this_path name jsig_field;
+
+	(** Constructs a [path] object using the specified construction_kind [kind].
+
+	    The function [f] is invokved to handle the call arguments. Its returned jsignature list is then used as the
+		method type of the constructor to invoke.
+
+	    If [no_value] is true, this function ensures that the stack is neutral. Otherwise, the created object is pushed
+		onto the stack.
+	**)
+	method construct ?(no_value=false) (kind : construction_kind) (path : jpath) (f : unit -> jsignature list) =
+		let pool = code#get_pool in
+		let offset = pool#add_path path in
+		code#new_ offset;
+		match kind with
+		| ConstructInitPlusNew ->
+			code#dup;
+			code#aconst_null haxe_empty_constructor_sig;
+			self#invokespecial path "<init>" (method_sig [haxe_empty_constructor_sig] None);
+			if not no_value then self#set_top_initialized (object_path_sig path);
+			if not no_value then code#dup;
+			let jsigs = f () in
+			self#invokevirtual path "new" (method_sig jsigs None);
+		| ConstructInit ->
+			if not no_value then code#dup;
+			let jsigs = f () in
+			self#invokespecial path "<init>" (method_sig jsigs None);
+			if not no_value then self#set_top_initialized (object_path_sig path)
+
+	(** Loads the default value corresponding to a given signature. **)
+	method load_default_value = function
+		| TByte | TBool | TChar | TShort | TInt ->
+			code#iconst Int32.zero;
+		| TFloat -> code#fconst 0.
+		| TDouble -> code#dconst 0.
+		| TLong -> code#lconst Int64.zero
+		| jsig -> code#aconst_null jsig
+
+	(** Constructs a new native array with element type [jsig].
+
+	    Iterates over [fl] and invokes the functions to handle the array elements.
+	**)
+	method new_native_array (jsig : jsignature) (fl : (unit -> unit) list) =
+		code#iconst (Int32.of_int (List.length fl));
+		let jasig = NativeArray.create code jc#get_pool jsig in
+		List.iteri (fun i f ->
+			code#dup;
+			code#iconst (Int32.of_int i);
+			f();
+			self#cast jsig;
+			NativeArray.write code jasig jsig
+		) fl
+
+	(** Adds a closure to method [name] ob [path] with signature [jsig_method] to the constant pool.
+
+	    Also emits an instruction to load the closure.
+	**)
+	method read_closure is_static path name jsig_method =
+		let offset = code#get_pool#add_field path name jsig_method FKMethod in
+		let offset = code#get_pool#add (ConstMethodHandle((if is_static then 6 else 5), offset)) in
+		code#ldc offset jsig_method
+
+	(**
+		Emits a return instruction.
+	**)
+	method return = match jsig with
+		| TMethod(_,tr) ->
+			begin match tr with
+			| None ->
+				code#return_void
+			| Some jsig ->
+				code#return_value jsig
+			end
+		| _ ->
+			assert false
+
+	(* casting *)
+
+	(** Checks if the stack top is a basic type and wraps accordingly. **)
+	method expect_reference_type =
+		let wrap_null jsig name =
+			let path = (["java";"lang"],name) in
+			self#invokestatic path "valueOf" (method_sig [jsig] (Some (object_path_sig path)))
+		in
+		match code#get_stack#top with
+		| TByte as t -> wrap_null t "Byte"
+		| TChar as t -> wrap_null t "Character"
+		| TDouble as t -> wrap_null t "Double"
+		| TFloat as t -> wrap_null t "Float"
+		| TInt as t -> wrap_null t "Integer"
+		| TLong as t -> wrap_null t "Long"
+		| TShort as t -> wrap_null t "Short"
+		| TBool as t -> wrap_null t "Boolean"
+		| _ -> ()
+
+	method private expect_basic_type ?(not_null=false) jsig =
+		if not_null then begin
+			let unwrap_null tname name =
+				let path = (["java";"lang"],tname) in
+				self#cast (get_boxed_type jsig);
+				self#invokevirtual path name (method_sig [] (Some jsig))
+			in
+			match jsig with
+			| TByte -> unwrap_null "Number" "byteValue"
+			| TChar -> unwrap_null "Character" "charValue"
+			| TDouble -> unwrap_null "Number" "doubleValue"
+			| TFloat -> unwrap_null "Number" "floatValue"
+			| TInt -> unwrap_null "Number" "intValue"
+			| TLong -> unwrap_null "Number" "longValue"
+			| TShort -> unwrap_null "Number" "shortValue"
+			| TBool -> unwrap_null "Boolean" "booleanValue"
+			| _ -> ()
+		end else begin
+			let unwrap_null tname name =
+				self#invokestatic (["haxe";"jvm"],"Jvm") name (method_sig [object_sig] (Some jsig))
+			in
+			match jsig with
+			| TByte -> unwrap_null "Byte" "toByte"
+			| TChar -> unwrap_null "Character" "toChar"
+			| TDouble -> unwrap_null "Double" "toDouble"
+			| TFloat -> unwrap_null "Float" "toFloat"
+			| TInt -> unwrap_null "Integer" "toInt"
+			| TLong -> unwrap_null "Long" "toLong"
+			| TShort -> unwrap_null "Short" "toShort"
+			| TBool -> unwrap_null "Boolean" "toBoolean"
+			| _ -> ()
+		end
+
+	method adapt_method jsig =
+		()
+		(* let offset = code#get_pool#add_string (generate_method_signature false jsig) in
+		let offset = code#get_pool#add (ConstMethodType offset) in
+		self#get_code#dup;
+		self#if_then
+			(fun () -> self#get_code#if_null_ref jsig)
+			(fun () ->
+				code#ldc offset method_type_sig;
+				self#invokevirtual method_handle_path "asType" (method_sig [method_type_sig] (Some method_handle_sig))
+			);
+		ignore(code#get_stack#pop);
+		code#get_stack#push jsig; *)
+
+	(** Casts the top of the stack to [jsig]. If [allow_to_string] is true, Jvm.toString is called. **)
+	method cast ?(not_null=false) ?(allow_to_string=false) jsig =
+		let jsig' = code#get_stack#top in
+		begin match jsig,jsig' with
+		| TObject((["java";"lang"],"Double"),_),TInt ->
+			code#i2d;
+			self#expect_reference_type;
+		| TObject((["java";"lang"],"Double"),_),TObject((["java";"lang"],"Integer"),_) ->
+			self#invokestatic (["haxe";"jvm"],"Jvm") "nullIntToNullFloat" (method_sig [integer_sig] (Some double_sig))
+		| TObject((["java";"lang"],"Double"),_),TObject((["java";"lang"],"Object"),_) ->
+			self#invokestatic (["haxe";"jvm"],"Jvm") "dynamicToNullFloat" (method_sig [object_sig] (Some double_sig))
+		(* from double *)
+		| TFloat,TDouble ->
+			code#d2f
+		| TInt,TDouble ->
+			code#d2i;
+		| TLong,TDouble ->
+			code#d2l;
+		(* from float *)
+		| TDouble,TFloat ->
+			code#f2d
+		| TInt,TFloat ->
+			code#f2i;
+		| TLong,TFloat ->
+			code#f2l;
+		(* from int *)
+		| TBool,TInt ->
+			ignore(code#get_stack#pop);
+			code#get_stack#push TBool;
+		| TByte,TInt ->
+			code#i2b TByte
+		| TChar,TInt ->
+			code#i2c
+		| TDouble,TInt ->
+			code#i2d;
+		| TFloat,TInt ->
+			code#i2f
+		| TLong,TInt ->
+			code#i2l;
+		| TShort,TInt ->
+			code#i2s
+		(* from long *)
+		| TDouble,TLong ->
+			code#l2d;
+		| TFloat,TLong ->
+			code#l2f
+		| TInt,TLong ->
+			code#l2i;
+		(* widening *)
+		| TInt,(TByte | TShort | TChar) ->
+			(* No cast, but rewrite stack top *)
+			ignore(code#get_stack#pop);
+			code#get_stack#push jsig;
+		| TObject(path1,_),TObject(path2,_) when path1 = path2 ->
+			()
+		| TObject((["java";"lang"],"String"),_),_ when allow_to_string ->
+			self#expect_reference_type;
+			self#invokestatic (["haxe";"jvm"],"Jvm") "toString" (method_sig [object_sig] (Some string_sig))
+		| TObject(path1,_),TObject(path2,_) ->
+			if path1 = object_path then begin
+				(* We should never need a checkcast to Object, but we should adjust the stack so stack maps are wide enough *)
+				ignore(code#get_stack#pop);
+				code#get_stack#push object_sig
+			end else
+				code#checkcast path1;
+		| TObject(path,_),TTypeParameter _ ->
+			code#checkcast path
+		| TMethod _,TMethod _ ->
+			if jsig <> jsig' then self#adapt_method jsig;
+		| TMethod _,TObject((["java";"lang";"invoke"],"MethodHandle"),_) ->
+			self#adapt_method jsig;
+		| TObject((["java";"lang";"invoke"],"MethodHandle"),_),TMethod _ ->
+			()
+		| TMethod _,_ ->
+			code#checkcast (["java";"lang";"invoke"],"MethodHandle");
+		| TArray(jsig1,_),TArray(jsig2,_) when jsig1 = jsig2 ->
+			()
+		| TArray _,_ ->
+			code#checkcast_sig jsig
+		| t1,t2 ->
+			match is_unboxed t1,is_unboxed t2 with
+			| true,false -> self#expect_basic_type ~not_null t1
+			| false,true -> self#expect_reference_type
+			| _ -> ()
+		end
+
+	(* branches *)
+
+	(** Starts a branch. Returns a restore function which reverts the stack and termination status back
+	    to the previous state. The restore function can be called multiple times for multiple branches.
+
+		This function has no effect on locals. Use [push_scope] for them.
+	**)
+	method start_branch =
+		let save = code#get_stack#save in
+		let old_terminated = terminated in
+		(fun () ->
+			code#get_stack#restore save;
+			terminated <- old_terminated;
+		)
+
+	(** Generates code which executes [f_if()] and then branches into [f_then()] and [f_else()]. **)
+	method if_then_else (f_if : unit -> jbranchoffset ref) (f_then : unit -> unit) (f_else : unit -> unit) =
+		let jump_then = f_if () in
+		let restore = self#start_branch in
+		let pop = self#push_scope in
+		f_then();
+		pop();
+		let r_then = ref code#get_fp in
+		let term_then = self#is_terminated in
+		if not term_then then code#goto r_then;
+		jump_then := code#get_fp - !jump_then;
+		restore();
+		self#add_stack_frame;
+		let pop = self#push_scope in
+		f_else();
+		pop();
+		self#set_terminated (term_then && self#is_terminated);
+		r_then := code#get_fp - !r_then;
+		if not self#is_terminated then self#add_stack_frame
+
+	(** Generates code which executes [f_if()] and then branches into [f_then()], if the condition holds. **)
+	method if_then (f_if : unit -> jbranchoffset ref) (f_then : unit -> unit) =
+		let jump_then = f_if () in
+		let restore = self#start_branch in
+		let pop = self#push_scope in
+		f_then();
+		pop();
+		restore();
+		jump_then := code#get_fp - !jump_then;
+		self#add_stack_frame
+
+	(**
+		Returns an instruction offset and emits a goto instruction to it if this method isn't terminated.
+	**)
+	method maybe_make_jump =
+		let r = ref code#get_fp in
+		if not self#is_terminated then code#goto r;
+		r
+
+	(**
+		Closes the list of instruction offsets [rl], effectively making them jump to the current fp.
+
+		Also generates a stack map frame unless [is_exhaustive] is true and all branches terminated.
+	**)
+	method close_jumps is_exhaustive rl =
+		let fp' = code#get_fp in
+		let term = List.fold_left (fun term (term',r) ->
+			r := fp' - !r;
+			term && term'
+		) true rl in
+		let term = is_exhaustive && term in
+		self#set_terminated term;
+		if not term then self#add_stack_frame;
+
+
+	(**
+		Emits a tableswitch or lookupswitch instruction, depending on which one makes more sense.
+
+		The switch subject is expected to be on the stack before calling this function.
+
+		If [is_exhaustive] is true and [def] is None, the first case is used as the default case.
+	**)
+	method int_switch (is_exhaustive : bool) (cases : (Int32.t list * (unit -> unit)) list) (def : (unit -> unit) option) =
+		let def,cases = match def,cases with
+			| None,(_,ec) :: cases when is_exhaustive ->
+				Some ec,cases
+			| _ ->
+				def,cases
+		in
+		let flat_cases = DynArray.create () in
+		let case_lut = ref IntMap.empty in
+		let fp = code#get_fp in
+		let imin = ref max_int in
+		let imax = ref min_int in
+		let cases = List.map (fun (il,f) ->
+			let rl = List.map (fun i32 ->
+				let r = ref fp in
+				let i = Int32.to_int i32 in
+				if i < !imin then imin := i;
+				if i > !imax then imax := i;
+				DynArray.add flat_cases (i,r);
+				case_lut := IntMap.add i r !case_lut;
+				r
+			) il in
+			(rl,f)
+		) cases in
+		let offset_def = ref fp in
+		(* No idea what's a good heuristic here... *)
+		let use_tableswitch = (!imax - !imin) < (DynArray.length flat_cases + 10) in
+		if use_tableswitch then begin
+			let offsets = Array.init (!imax - !imin + 1) (fun i ->
+				try IntMap.find (i + !imin) !case_lut
+				with Not_found -> offset_def
+			) in
+			code#tableswitch offset_def !imin !imax offsets
+		end else begin
+			let a = DynArray.to_array flat_cases in
+			Array.sort (fun (i1,_) (i2,_) -> compare i1 i2) a;
+			code#lookupswitch offset_def a;
+		end;
+		let restore = self#start_branch in
+		let def_term,r_def = match def with
+			| None ->
+				true,ref 0
+			| Some f ->
+				offset_def := code#get_fp - !offset_def;
+				self#add_stack_frame;
+				let pop_scope = self#push_scope in
+				f();
+				pop_scope();
+				self#is_terminated,self#maybe_make_jump
+		in
+
+		let rec loop acc cases = match cases with
+		| (rl,f) :: cases ->
+			restore();
+			self#add_stack_frame;
+			List.iter (fun r -> r := code#get_fp - !r) rl;
+			let pop_scope = self#push_scope in
+			f();
+			pop_scope();
+			let r = if cases = [] then ref 0 else self#maybe_make_jump in
+			loop ((self#is_terminated,r) :: acc) cases
+		| [] ->
+			List.rev acc
+		in
+		let rl = loop [] cases in
+		self#close_jumps (def <> None) ((def_term,if def = None then offset_def else r_def) :: rl)
+
+	(** Adds a local with a given [name], signature [jsig] and an [init_state].
+	    This function returns a tuple consisting of:
+
+		  * The slot of the local
+		  * The function to load the value
+		  * The function to store a value
+
+		If [init_state = VarNeedDefault], the local is initialized to a default value matching [jsig].
+		If [init_state = VarArgument], the local is considered initialized.
+		If [init_state = VarWillInit], this function assumes that the returned [store] function will be called appropriately.
+	**)
+	method add_local (name : string) (jsig : jsignature) (init_state : var_init_state) =
+		let slot = local_offset in
+		let load,store,d = match jsig with
+			| TInt | TBool | TByte | TShort | TChar ->
+				if init_state = VarNeedDefault then begin
+					code#iconst Int32.zero;
+					code#istore slot
+				end;
+				(fun () -> code#iload ~jsig slot),(fun () -> code#istore slot),1
+			| TLong ->
+				if init_state = VarNeedDefault then begin
+					code#lconst Int64.zero;
+					code#lstore slot
+				end;
+				(fun () -> code#lload slot),(fun () -> code#lstore slot),2
+			| TFloat ->
+				if init_state = VarNeedDefault then begin
+					code#fconst 0.;
+					code#fstore slot
+				end;
+				(fun () -> code#fload slot),(fun () -> code#fstore slot),1
+			| TDouble ->
+				if init_state = VarNeedDefault then begin
+					code#dconst 0.;
+					code#dstore slot
+				end;
+				(fun () -> code#dload slot),(fun () -> code#dstore slot),2
+			| _ ->
+				if init_state = VarNeedDefault then begin
+					code#aconst_null jsig;
+					code#astore jsig slot
+				end;
+				(fun () -> code#aload jsig slot),(fun () -> code#astore jsig slot),1
+		in
+		let init = ref None in
+		locals <- (init,name,jsig) :: locals;
+		local_offset <- local_offset + d;
+		if local_offset > max_num_locals then max_num_locals <- local_offset;
+		let check_store =
+			let did_store = ref false in
+			(fun () ->
+				if not !did_store then begin
+					did_store := true;
+					init := Some (code#get_fp)
+				end
+			)
+		in
+		begin match init_state with
+		| VarArgument | VarNeedDefault -> check_store();
+		| _ -> ()
+		end;
+		slot,
+		load,
+		(fun () ->
+			store();
+			check_store();
+		)
+
+	method get_this_sig =
+		let rec loop locals = match locals with
+			| [(_,_,jsig)] -> jsig
+			| _ :: locals -> loop locals
+			| [] -> assert false
+		in
+		loop locals
+
+	method set_this_initialized =
+		let rec loop acc locals = match locals with
+			| [(init,name,_)] -> List.rev ((init,name,jc#get_jsig) :: acc)
+			| [] -> assert false
+			| l :: locals -> loop (l :: acc) locals
+		in
+		locals <- loop [] locals
+
+	method set_top_initialized jsig =
+		ignore(code#get_stack#pop);
+		code#get_stack#push jsig
+
+	(** This function has to be called once all arguments are declared. *)
+	method finalize_arguments =
+		argument_locals <- locals
+
+	method private get_stack_map_table =
+		let argument_locals = self#get_locals_for_stack_frame argument_locals in
+		let stack_map = List.fold_left (fun ((last_offset,last_locals,last_locals_length),acc) (offset,locals,stack) ->
+			let cur = offset - last_offset - 1 in
+			let a_locals = Array.of_list (List.rev locals) in
+			let locals_length = Array.length a_locals in
+			let default () =
+				StackFull(cur,a_locals,Array.of_list (List.rev stack))
+			in
+			let entry = match stack,locals_length - last_locals_length with
+			| [],0 ->
+				if last_locals = locals then begin
+					if cur < 64 then StackSame cur
+					else StackSameExtended cur
+				end else
+					default()
+			| [vt],0 ->
+				if last_locals = locals then begin
+					if cur < 64 then Stack1StackItem(cur,vt)
+					else Stack1StackItemExtended(cur,vt)
+				end else
+					default()
+			| [],1 ->
+				begin match locals with
+				| vt1 :: locals when locals = last_locals -> StackAppend1(cur,vt1)
+				| _ -> default()
+				end
+			| [],2 ->
+				begin match locals with
+				| vt1 :: vt2 :: locals when locals = last_locals -> StackAppend2(cur,vt2,vt1)
+				| _ -> default()
+				end
+			| [],3 ->
+				begin match locals with
+				| vt1 :: vt2 :: vt3 :: locals when locals = last_locals -> StackAppend3(cur,vt3,vt2,vt1)
+				| _ -> default()
+				end
+			| [],-1 ->
+				begin match last_locals with
+				| _ :: last_locals when locals = last_locals -> StackChop1 cur
+				| _ -> default()
+				end
+			| [],-2 ->
+				begin match last_locals with
+				| _ :: _ :: last_locals when locals = last_locals -> StackChop2 cur
+				| _ -> default()
+				end
+			| [],-3 ->
+				begin match last_locals with
+				| _ :: _ :: _ :: last_locals when locals = last_locals -> StackChop3 cur
+				| _ -> default()
+				end
+			| _ ->
+				default()
+			in
+			((offset,locals,locals_length),entry :: acc)
+		) ((-1,argument_locals,List.length argument_locals),[]) (List.rev stack_frames) in
+		Array.of_list (List.rev (snd stack_map))
+
+	method get_code = code
+	method is_terminated = terminated
+	method get_name = name
+	method get_jsig = jsig
+	method set_terminated b = terminated <- b
+
+	method private get_jcode (config : export_config) =
+		let attributes = DynArray.create () in
+		let lines = code#get_lines in
+		if config.export_debug && DynArray.length lines > 0 then
+			DynArray.add attributes (AttributeLineNumberTable (DynArray.to_array lines));
+		let stack_map_table = self#get_stack_map_table in
+		if Array.length stack_map_table > 0 then
+			DynArray.add attributes (AttributeStackMapTable stack_map_table);
+		let exceptions = Array.of_list (List.rev exceptions) in
+		let attributes = List.map (JvmAttribute.write_attribute jc#get_pool) (DynArray.to_list attributes) in
+		{
+			code_max_stack = code#get_max_stack_size;
+			code_max_locals = max_num_locals;
+			code_code = code#export_code;
+			code_exceptions = exceptions;
+			code_attributes = Array.of_list attributes;
+		}
+
+	(** Exports the method as a [jvm_field]. No other functions should be called on this object afterwards. *)
+	method export_method (config : export_config) =
+		assert (not was_exported);
+		was_exported <- true;
+		self#commit_annotations jc#get_pool;
+		if code#get_fp > 0 then begin
+			let code = self#get_jcode config in
+			self#add_attribute (AttributeCode code);
+		end;
+		if Hashtbl.length thrown_exceptions > 0 then
+			self#add_attribute (AttributeExceptions (Array.of_list (Hashtbl.fold (fun k _ c -> k :: c) thrown_exceptions [])));
+		if config.export_debug then begin match debug_locals with
+		| [] ->
+			()
+		| _ ->
+			let a = Array.of_list debug_locals in
+			self#add_attribute (AttributeLocalVariableTable a);
+		end;
+		let attributes = self#export_attributes jc#get_pool in
+		let offset_name = jc#get_pool#add_string name in
+		let jsig = generate_method_signature false jsig in
+		let offset_desc = jc#get_pool#add_string jsig in
+		{
+			field_access_flags = access_flags;
+			field_name_index = offset_name;
+			field_descriptor_index = offset_desc;
+			field_attributes = attributes;
+		}
+
+	(** Exports the method as a [jvm_field]. No other functions should be called on this object afterwards. *)
+	method export_field =
+		assert (code#get_fp = 0);
+		assert (not was_exported);
+		was_exported <- true;
+		let attributes = self#export_attributes jc#get_pool in
+		let offset_name = jc#get_pool#add_string name in
+		let jsig = generate_signature false jsig in
+		let offset_desc = jc#get_pool#add_string jsig in
+		{
+			field_access_flags = access_flags;
+			field_name_index = offset_name;
+			field_descriptor_index = offset_desc;
+			field_attributes = attributes;
+		}
+end

+ 275 - 0
src/generators/jvm/jvmSignature.ml

@@ -0,0 +1,275 @@
+open JvmGlobals
+
+type jpath = (string list) * string
+
+type jwildcard =
+	| WExtends (* + *)
+	| WSuper (* -  *)
+	| WNone
+
+type jtype_argument =
+	| TType of jwildcard * jsignature
+	| TAny (* * *)
+
+and jsignature =
+	| TByte (* B *)
+	| TChar (* C *)
+	| TDouble (* D *)
+	| TFloat (* F *)
+	| TInt (* I *)
+	| TLong (* J *)
+	| TShort (* S *)
+	| TBool (* Z *)
+	| TObject of jpath * jtype_argument list (* L Classname *)
+	| TObjectInner of (string list) * (string * jtype_argument list) list (* L Classname ClassTypeSignatureSuffix *)
+	| TArray of jsignature * int option (* [ *)
+	| TMethod of jmethod_signature (* ( *)
+	| TTypeParameter of string (* T *)
+	| TUninitialized of int option
+
+(* ( jsignature list ) ReturnDescriptor (| V | jsignature) *)
+and jmethod_signature = jsignature list * jsignature option
+
+let s_wildcard = function
+	| WExtends -> "WExtends"
+	| WSuper -> "WSuper"
+	| WNone -> "WNone"
+
+let rec s_signature_kind = function
+	| TByte -> "TByte"
+	| TChar -> "TChar"
+	| TDouble -> "TDouble"
+	| TFloat -> "TFloat"
+	| TInt -> "TInt"
+	| TLong -> "TLong"
+	| TShort -> "TShort"
+	| TBool -> "TBool"
+	| TObject(path,params) -> Printf.sprintf "TObject(%s,[%s])" (Globals.s_type_path path) (String.concat "," (List.map s_signature_param_kind params))
+	| TObjectInner _ -> "TObjectInner"
+	| TArray(jsig,io) -> Printf.sprintf "TArray(%s,%s)" (s_signature_kind jsig) (Option.map_default string_of_int "None" io)
+	| TMethod(jsigs,jsig) -> Printf.sprintf "TMethod([%s],%s)" (String.concat "," (List.map s_signature_kind jsigs)) (Option.map_default s_signature_kind "None" jsig)
+	| TTypeParameter name -> Printf.sprintf "TTypeParameter(%s)" name
+	| TUninitialized io -> Printf.sprintf "TUninitilaized(%s)" (Option.map_default string_of_int "None" io)
+
+and s_signature_param_kind = function
+	| TAny -> "TAny"
+	| TType(wc,jsig) -> Printf.sprintf "TType(%s,%s)" (s_wildcard wc) (s_signature_kind jsig)
+
+let encode_path (pack,name) =
+	String.concat "/" (pack @ [name])
+
+let rec write_param full ch param = match param with
+	| TAny -> write_byte ch (Char.code '*')
+	| TType(w, s) ->
+		begin match w with
+			| WExtends -> write_byte ch (Char.code '+')
+			| WSuper -> write_byte ch (Char.code '-')
+			| WNone -> ()
+		end;
+		write_signature full ch s
+
+and write_signature full ch jsig = match jsig with
+	| TByte -> write_byte ch (Char.code 'B')
+	| TChar -> write_byte ch (Char.code 'C')
+	| TDouble -> write_byte ch (Char.code 'D')
+	| TFloat -> write_byte ch (Char.code 'F')
+	| TInt -> write_byte ch (Char.code 'I')
+	| TLong -> write_byte ch (Char.code 'J')
+	| TShort -> write_byte ch (Char.code 'S')
+	| TBool -> write_byte ch (Char.code 'Z')
+	| TObject(path, params) ->
+		write_byte ch (Char.code 'L');
+		write_string ch (encode_path path);
+		if params <> [] && full then begin
+			write_byte ch (Char.code '<');
+			List.iter (write_param full ch) params;
+			write_byte ch (Char.code '>')
+		end;
+		write_byte ch (Char.code ';')
+	| TObjectInner(pack, inners) ->
+		write_byte ch (Char.code 'L');
+		List.iter (fun p ->
+			write_string ch p;
+			write_byte ch (Char.code '/')
+		) pack;
+		let first = ref true in
+		List.iter (fun (name,params) ->
+			(if !first then first := false else write_byte ch (Char.code '.'));
+			write_string ch name;
+			if params <> [] then begin
+				write_byte ch (Char.code '<');
+				List.iter (write_param full ch) params;
+				write_byte ch (Char.code '>')
+			end;
+		) inners;
+		write_byte ch (Char.code ';')
+	| TArray(s,size) ->
+		write_byte ch (Char.code '[');
+		begin match size with
+			| Some size ->
+				write_string ch (string_of_int size);
+			| None -> ()
+		end;
+		write_signature full ch s
+	| TMethod _ ->
+		write_signature full ch (TObject((["java";"lang";"invoke"],"MethodHandle"),[]))
+	| TTypeParameter name ->
+		if full then begin
+			write_byte ch (Char.code 'T');
+			write_string ch name;
+			write_byte ch (Char.code ';')
+		end else
+			write_string ch "Ljava/lang/Object;"
+	| TUninitialized _ ->
+		()
+
+let generate_signature full jsig =
+	let ch = IO.output_bytes () in
+	write_signature full ch jsig;
+	Bytes.unsafe_to_string (IO.close_out ch)
+
+let generate_method_signature full jsig =
+	let ch = IO.output_bytes () in
+	begin match jsig with
+	| TMethod(args, ret) ->
+		write_byte ch (Char.code '(');
+		List.iter (write_signature full ch) args;
+		write_byte ch (Char.code ')');
+		begin match ret with
+			| None -> write_byte ch (Char.code 'V')
+			| Some jsig -> write_signature full ch jsig
+		end
+	| _ ->
+		write_signature full ch jsig;
+	end;
+	Bytes.unsafe_to_string (IO.close_out ch)
+
+let signature_size = function
+	| TDouble | TLong -> 2
+	| _ -> 1
+
+module NativeSignatures = struct
+	let object_path = ["java";"lang"],"Object"
+	let object_sig = TObject(object_path,[])
+
+	let string_path = ["java";"lang"],"String"
+	let string_sig = TObject(string_path,[])
+
+	let boolean_path = ["java";"lang"],"Boolean"
+	let boolean_sig = TObject(boolean_path,[])
+
+	let character_path = ["java";"lang"],"Character"
+	let character_sig = TObject(character_path,[])
+
+	let method_handle_path = (["java";"lang";"invoke"],"MethodHandle")
+	let method_handle_sig = TObject(method_handle_path,[])
+
+	let method_type_path = (["java";"lang";"invoke"],"MethodType")
+	let method_type_sig = TObject(method_type_path,[])
+
+	let method_lookup_path = (["java";"lang";"invoke"],"MethodHandles$Lookup")
+	let method_lookup_sig = TObject(method_lookup_path,[])
+
+	let call_site_path = (["java";"lang";"invoke"],"CallSite")
+	let call_site_sig = TObject(call_site_path,[])
+
+	let java_class_path = ["java";"lang"],"Class"
+	let java_class_sig = TObject(java_class_path,[TType(WNone,object_sig)])
+
+	let haxe_jvm_path = ["haxe";"jvm"],"Jvm"
+
+	let haxe_dynamic_object_path = ["haxe";"jvm"],"DynamicObject"
+	let haxe_dynamic_object_sig = TObject(haxe_dynamic_object_path,[])
+
+	let haxe_exception_path = ["haxe";"jvm"],"Exception"
+	let haxe_exception_sig = TObject(haxe_exception_path,[])
+
+	let haxe_object_path = ["haxe";"jvm"],"Object"
+	let haxe_object_sig = TObject(haxe_object_path,[])
+
+	let throwable_path = (["java";"lang"],"Throwable")
+	let throwable_sig = TObject(throwable_path,[])
+
+	let exception_path = (["java";"lang"],"Exception")
+	let exception_sig = TObject(exception_path,[])
+
+	let retention_path = (["java";"lang";"annotation"],"Retention")
+	let retention_sig = TObject(retention_path,[])
+
+	let retention_policy_path = (["java";"lang";"annotation"],"RetentionPolicy")
+	let retention_policy_sig = TObject(retention_policy_path,[])
+
+	let java_enum_path = (["java";"lang"],"Enum")
+	let java_enum_sig jsig = TObject(java_enum_path,[TType(WNone,jsig)])
+
+	let haxe_enum_path = (["haxe";"jvm"],"Enum")
+	let haxe_enum_sig jsig = TObject(haxe_enum_path,[TType(WNone,jsig)])
+
+	let haxe_empty_constructor_path = (["haxe";"jvm"],"EmptyConstructor")
+	let haxe_empty_constructor_sig = TObject(haxe_empty_constructor_path,[])
+
+	(* numeric *)
+
+	let byte_path = ["java";"lang"],"Byte"
+	let byte_sig = TObject(byte_path,[])
+
+	let short_path = ["java";"lang"],"Short"
+	let short_sig = TObject(short_path,[])
+
+	let integer_path = ["java";"lang"],"Integer"
+	let integer_sig = TObject(integer_path,[])
+
+	let long_path = ["java";"lang"],"Long"
+	let long_sig = TObject(long_path,[])
+
+	let float_path = ["java";"lang"],"Float"
+	let float_sig = TObject(float_path,[])
+
+	let double_path = ["java";"lang"],"Double"
+	let double_sig = TObject(double_path,[])
+
+	(* compound *)
+
+	let array_sig jsig = TArray(jsig,None)
+
+	let method_sig jsigs jsig = TMethod(jsigs,jsig)
+
+	let object_path_sig path = TObject(path,[])
+
+	let get_boxed_type jsig = match jsig with
+		| TBool -> boolean_sig
+		| TChar -> character_sig
+		| TByte -> byte_sig
+		| TShort -> short_sig
+		| TInt -> integer_sig
+		| TLong -> long_sig
+		| TFloat -> float_sig
+		| TDouble -> double_sig
+		| _ -> jsig
+
+	let get_unboxed_type jsig = match jsig with
+		| TObject((["java";"lang"],"Boolean"),_) -> TBool
+		| TObject((["java";"lang"],"Charcter"),_) -> TChar
+		| TObject((["java";"lang"],"Byte"),_) -> TByte
+		| TObject((["java";"lang"],"Short"),_) -> TShort
+		| TObject((["java";"lang"],"Integer"),_) -> TInt
+		| TObject((["java";"lang"],"Long"),_) -> TLong
+		| TObject((["java";"lang"],"Float"),_) -> TFloat
+		| TObject((["java";"lang"],"Double"),_) -> TDouble
+		| _ -> jsig
+
+	let is_unboxed jsig = match jsig with
+		| TBool | TChar
+		| TByte | TShort | TInt | TLong
+		| TFloat | TDouble ->
+			true
+		| _ ->
+			false
+
+	let is_dynamic_at_runtime = function
+		| TObject((["java";"lang"],"Object"),_)
+		| TTypeParameter _ ->
+			true
+		| _ ->
+			false
+end

+ 37 - 0
src/generators/jvm/jvmVerificationTypeInfo.ml

@@ -0,0 +1,37 @@
+open JvmGlobals
+open JvmSignature
+
+type t =
+	| VTop
+	| VInteger
+	| VFloat
+	| VDouble
+	| VLong
+	| VNull
+	| VUninitializedThis
+	| VObject of jvm_constant_pool_index
+	| VUninitialized of int
+
+let of_signature pool jsig = match jsig with
+    | TByte | TChar | TBool | TShort | TInt -> VInteger
+    | TFloat -> VFloat
+    | TLong -> VLong
+    | TDouble -> VDouble
+    | TObject(path,_) -> VObject (pool#add_path path)
+	| TMethod _ -> VObject (pool#add_path (["java";"lang";"invoke"],"MethodHandle"))
+	| TArray _ -> VObject (pool#add_path ([],generate_signature false jsig))
+	| TTypeParameter _ -> VObject (pool#add_path (["java";"lang"],"Object"))
+	| TUninitialized (Some i) -> VUninitialized i
+	| TUninitialized None -> VUninitializedThis
+    | _ -> assert false
+
+let to_string vtt = match vtt with
+	| VTop -> "top"
+	| VInteger -> "integer"
+	| VFloat -> "float"
+	| VLong -> "long"
+	| VDouble -> "double"
+	| VNull -> "null"
+	| VUninitializedThis -> "uninitializedThis"
+	| VObject i -> "object " ^ (string_of_int i)
+	| VUninitialized i -> "uninitializedVariable " ^ (string_of_int i)

+ 312 - 0
src/generators/jvm/jvmWriter.ml

@@ -0,0 +1,312 @@
+open JvmGlobals
+open JvmData
+
+(* Low-level writing corresponding to jvmData. *)
+
+let write_jvm_attribute ch jvma =
+	write_ui16 ch jvma.attr_index;
+	write_ui32 ch (Bytes.length jvma.attr_data);
+	write_bytes ch jvma.attr_data
+
+let write_jvm_attributes ch jvmal =
+	write_array16 ch write_jvm_attribute jvmal
+
+let write_jvm_field ch jvmf =
+	write_ui16 ch jvmf.field_access_flags;
+	write_ui16 ch jvmf.field_name_index;
+	write_ui16 ch jvmf.field_descriptor_index;
+	write_jvm_attributes ch jvmf.field_attributes
+
+let write_jvm_class ch jvmc =
+	write_ui32 ch 0xCAFEBABE;
+	write_ui16 ch jvmc.class_minor_version;
+	write_ui16 ch jvmc.class_major_version;
+	write_bytes ch jvmc.class_constant_pool;
+	write_ui16 ch jvmc.class_access_flags;
+	write_ui16 ch jvmc.class_this_class;
+	write_ui16 ch jvmc.class_super_class;
+	write_ui16 ch (Array.length jvmc.class_interfaces);
+	Array.iter (write_ui16 ch) jvmc.class_interfaces;
+	write_ui16 ch (Array.length jvmc.class_fields);
+	Array.iter (write_jvm_field ch) jvmc.class_fields;
+	write_ui16 ch (Array.length jvmc.class_methods);
+	Array.iter (write_jvm_field ch) jvmc.class_methods;
+	write_jvm_attributes ch jvmc.class_attributes
+
+(* Level 2: Targeting JVM structures *)
+
+let write_exception ch jvme =
+	write_ui16 ch jvme.exc_start_pc;
+	write_ui16 ch jvme.exc_end_pc;
+	write_ui16 ch jvme.exc_handler_pc;
+	match jvme.exc_catch_type with
+	| None -> write_ui16 ch 0
+	| Some t -> write_ui16 ch t
+
+let write_opcode ch code =
+  let w = write_byte ch in
+  (* TODO: probably don't need these *)
+  let bp i =
+    w ((i lsr 8) land 0xFF);
+    w (i land 0xFF);
+  in
+  let b4 i =
+    w ((i lsr 24) land 0xFF);
+    w ((i lsr 16) land 0xFF);
+    w ((i lsr 8) land 0xFF);
+    w (i land 0xFF);
+  in
+  let rec loop code = match code with
+    (* double *)
+    | OpD2f -> w 0x90
+    | OpD2i -> w 0x8e
+    | OpD2l -> w 0x8f
+    | OpDadd -> w 0x63
+    | OpDaload -> w 0x31
+    | OpDastore -> w 0x52
+    | OpDcmpg -> w 0x98
+    | OpDcmpl -> w 0x97
+    | OpDdiv -> w 0x6f
+    | OpDconst_0 -> w 0xe
+    | OpDconst_1 -> w 0xf
+    | OpDload_0 -> w 0x26
+    | OpDload_1 -> w 0x27
+    | OpDload_2 -> w 0x28
+    | OpDload_3 -> w 0x29
+    | OpDload i -> w 0x18; w i
+    | OpDmul -> w 0x6b
+    | OpDneg -> w 0x77
+    | OpDrem -> w 0x73
+    | OpDreturn -> w 0xaf
+    | OpDstore_0 -> w 0x47
+    | OpDstore_1 -> w 0x48
+    | OpDstore_2 -> w 0x49
+    | OpDstore_3 -> w 0x4a
+    | OpDstore i -> w 0x39; w i
+    | OpDsub -> w 0x67
+    (* float *)
+    | OpF2d -> w 0x8d
+    | OpF2i -> w 0x8b
+    | OpF2l -> w 0x8c
+    | OpFadd -> w 0x62
+    | OpFaload -> w 0x30
+    | OpFastore -> w 0x51
+    | OpFcmpg -> w 0x96
+    | OpFcmpl -> w 0x95
+    | OpFdiv -> w 0x6e
+    | OpFconst_0 -> w 0xb
+    | OpFconst_1 -> w 0xc
+    | OpFconst_2 -> w 0xd
+    | OpFload_0 -> w 0x22
+    | OpFload_1 -> w 0x23
+    | OpFload_2 -> w 0x24
+    | OpFload_3 -> w 0x25
+    | OpFload i -> w 0x17; w i
+    | OpFmul -> w 0x6a
+    | OpFneg -> w 0x76
+    | OpFrem -> w 0x72
+    | OpFreturn -> w 0xae
+    | OpFstore_0 -> w 0x43
+    | OpFstore_1 -> w 0x44
+    | OpFstore_2 -> w 0x45
+    | OpFstore_3 -> w 0x46
+    | OpFstore i -> w 0x38; w i
+    | OpFsub -> w 0x66
+    (* int *)
+    | OpI2b -> w 0x91
+    | OpI2c -> w 0x92
+    | OpI2d -> w 0x87
+    | OpI2f -> w 0x86
+    | OpI2l -> w 0x85
+    | OpI2s -> w 0x93
+    | OpIadd -> w 0x60
+    | OpIaload -> w 0x2e
+    | OpIand -> w 0x7e
+    | OpIastore -> w 0x4f
+    | OpIconst_m1 -> w 0x2
+    | OpIconst_0 -> w 0x3
+    | OpIconst_1 -> w 0x4
+    | OpIconst_2 -> w 0x5
+    | OpIconst_3 -> w 0x6
+    | OpIconst_4 -> w 0x7
+    | OpIconst_5 -> w 0x8
+    | OpIdiv -> w 0x6c
+    | OpIload_0 -> w 0x1a
+    | OpIload_1 -> w 0x1b
+    | OpIload_2 -> w 0x1c
+    | OpIload_3 -> w 0x1d
+    | OpIload i -> w 0x15; w i
+    | OpImul -> w 0x68
+    | OpIneg -> w 0x74
+    | OpIor -> w 0x80
+    | OpIrem -> w 0x70
+    | OpIreturn -> w 0xac
+    | OpIshl -> w 0x78
+    | OpIshr -> w 0x7a
+    | OpIstore_0 -> w 0x3b
+    | OpIstore_1 -> w 0x3c
+    | OpIstore_2 -> w 0x3d
+    | OpIstore_3 -> w 0x3e
+    | OpIstore i -> w 0x36; w i
+    | OpIsub -> w 0x64
+    | OpIushr -> w 0x7c
+    | OpIxor -> w 0x82
+    (* long *)
+    | OpL2d -> w 0x8a
+    | OpL2f -> w 0x89
+    | OpL2i -> w 0x88
+    | OpLadd -> w 0x61
+    | OpLaload -> w 0x2f
+    | OpLand -> w 0x7f
+    | OpLastore -> w 0x50
+	| OpLconst_0 -> w 0x9
+	| OpLconst_1 -> w 0xa
+    | OpLcmp -> w 0x94
+    | OpLdiv -> w 0x6d
+    | OpLload_0 -> w 0x1e
+    | OpLload_1 -> w 0x1f
+    | OpLload_2 -> w 0x20
+    | OpLload_3 -> w 0x21
+    | OpLload i ->  w 0x16; w i
+    | OpLmul -> w 0x69
+    | OpLneg -> w 0x75
+    | OpLor -> w 0x81
+    | OpLrem -> w 0x71
+    | OpLreturn -> w 0xad
+    | OpLshl -> w 0x79
+    | OpLshr -> w 0x7b
+    | OpLstore_0 -> w 0x3f
+    | OpLstore_1 -> w 0x40
+    | OpLstore_2 -> w 0x41
+    | OpLstore_3 -> w 0x42
+    | OpLstore i -> w 0x37; w i
+    | OpLsub -> w 0x65
+    | OpLushr -> w 0x7d
+    | OpLxor -> w 0x83
+    (* short *)
+    | OpSaload -> w 0x35
+    | OpSastore -> w 0x56
+    | OpSipush i -> w 0x11; bp i
+    (* array *)
+    | OpAaload -> w 0x32
+    | OpAastore -> w 0x53
+    | OpAnewarray offset -> w 0xbd; bp offset
+    | OpArraylength -> w 0xbe
+    | OpBaload -> w 0x33
+    | OpBastore -> w 0x54
+    | OpBipush i -> w 0x10; w i
+    | OpCaload -> w 0x34
+    | OpCastore -> w 0x55
+    | OpMultianewarray(offset,d) -> w 0xc5; bp offset; w d
+    | OpNewarray t -> w 0xbc; w t
+    (* reference *)
+    | OpAload_0 -> w 0x2a
+    | OpAload_1 -> w 0x2b
+    | OpAload_2 -> w 0x2c
+    | OpAload_3 -> w 0x2d
+    | OpAload i -> w 0x19; w i
+    | OpAreturn -> w 0xb0
+    | OpAstore_0 -> w 0x4b
+    | OpAstore_1 -> w 0x4c
+    | OpAstore_2 -> w 0x4d
+    | OpAstore_3 -> w 0x4e
+    | OpAstore i -> w 0x3a; w i
+    (* object *)
+    | OpNew offset -> w 0xbb; bp offset
+    | OpInstanceof offset -> w 0xc1; bp offset
+    | OpCheckcast offset -> w 0xc0; bp offset
+    | OpInvokedynamic offset -> w 0xba; bp offset; w 0; w 0 (* ??? *)
+    | OpInvokeinterface(offset,c) -> w 0xb9; bp offset; w c; w 0
+    | OpInvokespecial offset -> w 0xb7; bp offset
+    | OpInvokestatic offset -> w 0xb8; bp offset
+    | OpInvokevirtual offset -> w 0xb6; bp offset
+    | OpGetfield offset -> w 0xb4; bp offset
+    | OpGetstatic offset -> w 0xb2; bp offset
+    | OpPutfield offset -> w 0xb5; bp offset
+    | OpPutstatic offset -> w 0xb3; bp offset
+    (* branching *)
+    | OpIf_acmpeq i -> w 0xa5; bp !i
+    | OpIf_acmpne i -> w 0xa6; bp !i
+    | OpIf_icmp(cmp,i) ->
+      begin match cmp with
+        | CmpEq -> w 0x9f
+        | CmpNe -> w 0xa0
+        | CmpLt -> w 0xa1
+        | CmpGe -> w 0xa2
+        | CmpGt -> w 0xa3
+        | CmpLe -> w 0xa4
+      end;
+      bp !i
+    | OpIf(cmp,i) ->
+      begin match cmp with
+        | CmpEq -> w 0x99
+        | CmpNe -> w 0x9a
+        | CmpLt -> w 0x9b
+        | CmpGe -> w 0x9c
+        | CmpGt -> w 0x9d
+        | CmpLe -> w 0x9e
+      end;
+      bp !i
+    | OpIfnonnull i -> w 0xc7; bp !i
+    | OpIfnull i -> w 0xc6; bp !i
+    | OpGoto i -> w 0xa7; bp !i
+    | OpGoto_w i -> w 0xc8; b4 !i
+    | OpJsr i -> w 0xa8; bp !i
+    | OpJsr_w i -> w 0xc9; b4 !i
+    (* stack *)
+    | OpAconst_null -> w 0x1
+    | OpDup -> w 0x59
+    | OpDup_x1 -> w 0x5a
+    | OpDup_x2 -> w 0x5b
+    | OpDup2 -> w 0x5c
+    | OpDup2_x1 -> w 0x5d
+    | OpDup2_x2 -> w 0x5e
+    | OpLdc i -> w 0x12; w i
+    | OpLdc_w i -> w 0x13; bp i
+    | OpLdc2_w i -> w 0x14; bp i
+    | OpNop -> w 0x0
+    | OpPop -> w 0x57
+    | OpPop2 -> w 0x58
+    | OpSwap -> w 0x5f
+    (* other *)
+    | OpAthrow -> w 0xbf
+    | OpIinc(i,c) -> w 0x84; w i; w c (* TODO: signed? *)
+    | OpLookupswitch(pad,def,pairs) ->
+		w 0xab;
+		if pad > 0 then for i = 0 to pad -1 do w 0 done;
+		b4 !def;
+		b4 (Array.length pairs);
+		Array.iter (fun (i,offset) ->
+			b4 i;
+			b4 !offset
+		) pairs;
+    | OpTableswitch(pad,def,low,high,offsets) ->
+		w 0xaa;
+		if pad > 0 then for i = 0 to pad -1 do w 0 done;
+		b4 !def;
+		b4 low;
+		b4 high;
+		Array.iter (fun offset ->
+			b4 !offset
+		) offsets;
+    | OpMonitorenter -> w 0xc2
+    | OpMonitorexit -> w 0xc3
+    | OpRet i -> w 0xa9; w i
+    | OpReturn -> w 0xb1
+	| OpWide op ->
+		w 0xc4;
+		begin match op with
+			| OpWIinc(i1,i2) -> w 0x84; bp i1; bp i2;
+			| OpWIload i -> w 0x15; bp i
+			| OpWFload i -> w 0x17; bp i
+			| OpWAload i -> w 0x19; bp i
+			| OpWLload i -> w 0x16; bp i
+			| OpWDload i -> w 0x18; bp i
+			| OpWIstore i -> w 0x36; bp i
+			| OpWFstore i -> w 0x38; bp i
+			| OpWAstore i -> w 0x3a; bp i
+			| OpWLstore i -> w 0x37; bp i
+			| OpWDstore i -> w 0x39; bp i
+		end
+  in
+  loop code

+ 1 - 0
std/StdTypes.hx

@@ -26,6 +26,7 @@
 
 
 	@see https://haxe.org/manual/types-void.html
 	@see https://haxe.org/manual/types-void.html
 **/
 **/
+#if jvm @:runtimeValue #end
 @:coreType abstract Void { }
 @:coreType abstract Void { }
 
 
 /**
 /**

+ 1 - 1
std/haxe/CallStack.hx

@@ -195,7 +195,7 @@ class CallStack {
 			return makeStack(s);
 			return makeStack(s);
 		#elseif java
 		#elseif java
 			var stack = [];
 			var stack = [];
-			for ( el in java.internal.Exceptions.currentException().getStackTrace() ) {
+			for ( el in #if jvm jvm.Exception #else java.internal.Exceptions#end.currentException().getStackTrace() ) {
 				var className = el.getClassName();
 				var className = el.getClassName();
 				var methodName = el.getMethodName();
 				var methodName = el.getMethodName();
 				var fileName = el.getFileName();
 				var fileName = el.getFileName();

+ 3 - 3
std/haxe/format/JsonPrinter.hx

@@ -84,9 +84,9 @@ class JsonPrinter {
 		case TObject:
 		case TObject:
 			objString(v);
 			objString(v);
 		case TInt:
 		case TInt:
-			add(#if as3 Std.string(v) #else v #end);
+			add(#if (as3 || jvm) Std.string(v) #else v #end);
 		case TFloat:
 		case TFloat:
-			add(Math.isFinite(v) ? v : 'null');
+			add(Math.isFinite(v) ? Std.string(v) : 'null');
 		case TFunction:
 		case TFunction:
 			add('"<fun>"');
 			add('"<fun>"');
 		case TClass(c):
 		case TClass(c):
@@ -127,7 +127,7 @@ class JsonPrinter {
 			var i : Dynamic = Type.enumIndex(v);
 			var i : Dynamic = Type.enumIndex(v);
 			add(i);
 			add(i);
 		case TBool:
 		case TBool:
-			add(#if (php || as3) (v ? 'true' : 'false') #else v #end);
+			add(#if (php || as3 || jvm) (v ? 'true' : 'false') #else v #end);
 		case TNull:
 		case TNull:
 			add('null');
 			add('null');
 		}
 		}

+ 19 - 5
std/java/_std/Array.hx

@@ -40,7 +40,10 @@ import java.NativeArray;
 	')
 	')
 	private static function ofNative<X>(native:NativeArray<X>):Array<X>
 	private static function ofNative<X>(native:NativeArray<X>):Array<X>
 	{
 	{
-		return null;
+		var a = new Array();
+		a.length = native.length;
+		a.__a = native;
+		return a;
 	}
 	}
 
 
 	@:functionCode('
 	@:functionCode('
@@ -48,9 +51,20 @@ import java.NativeArray;
 	')
 	')
 	private static function alloc<Y>(size:Int):Array<Y>
 	private static function alloc<Y>(size:Int):Array<Y>
 	{
 	{
-		return null;
+		var a = new Array();
+		a.length = size;
+		a.__a = new java.NativeArray(size);
+		return a;
 	}
 	}
 
 
+	#if jvm
+	function getNative():NativeArray<T> {
+		var a = new NativeArray(length);
+		System.arraycopy(__a, 0, a, 0, length);
+		return a;
+	}
+	#end
+
 	public function new() : Void
 	public function new() : Void
 	{
 	{
 		this.length = 0;
 		this.length = 0;
@@ -450,7 +464,7 @@ import java.NativeArray;
 		return __a[idx];
 		return __a[idx];
 	}
 	}
 
 
-	private function __set(idx:Int, v:T):T
+	private function __set(idx:Int, v:T):#if jvm Void #else T #end
 	{
 	{
 		var __a = __a;
 		var __a = __a;
 		if (idx >= __a.length)
 		if (idx >= __a.length)
@@ -467,7 +481,7 @@ import java.NativeArray;
 		if (idx >= length)
 		if (idx >= length)
 			this.length = idx + 1;
 			this.length = idx + 1;
 
 
-		return __a[idx] = v;
+		#if !jvm return #end __a[idx] = v;
 	}
 	}
 
 
 	private inline function __unsafe_get(idx:Int):T
 	private inline function __unsafe_get(idx:Int):T
@@ -496,4 +510,4 @@ private final class ArrayIterator<T>
 
 
 	public inline function hasNext():Bool return i < len;
 	public inline function hasNext():Bool return i < len;
 	public inline function next():T return arr[i++];
 	public inline function next():T return arr[i++];
-}
+}

+ 1 - 1
std/java/_std/String.hx

@@ -69,4 +69,4 @@ import haxe.iterators.StringKeyValueIterator;
 
 
 	static function fromCharCode( code : Int ) : String;
 	static function fromCharCode( code : Int ) : String;
 
 
-}
+}

+ 10 - 0
std/java/_std/StringBuf.hx

@@ -35,6 +35,15 @@ class StringBuf {
 		return b.length();
 		return b.length();
 	}
 	}
 
 
+	#if jvm
+	public function add<T>( x : T ) : Void {
+		if (jvm.Jvm.instanceof(x, java.lang.Double.DoubleClass)) {
+			b.append(jvm.Jvm.toString(cast x));
+		} else {
+			b.append(x);
+		}
+	}
+	#else
 	public function add<T>( x : T ) : Void {
 	public function add<T>( x : T ) : Void {
 		if (Std.is(x, Int))
 		if (Std.is(x, Int))
 		{
 		{
@@ -45,6 +54,7 @@ class StringBuf {
 			b.append(x);
 			b.append(x);
 		}
 		}
 	}
 	}
+	#end
 
 
 	public function addSub( s : String, pos : Int, ?len : Int ) : Void {
 	public function addSub( s : String, pos : Int, ?len : Int ) : Void {
 		var l:Int = (len == null) ? s.length - pos : len;
 		var l:Int = (len == null) ? s.length - pos : len;

+ 5 - 2
std/java/_std/haxe/Int64.hx

@@ -29,9 +29,12 @@ private typedef __Int64 = java.StdTypes.Int64;
 @:coreApi
 @:coreApi
 abstract Int64(__Int64) from __Int64 to __Int64
 abstract Int64(__Int64) from __Int64 to __Int64
 {
 {
-
+	#if jvm
+	extern public static function make(high:Int32, low:Int32):Int64;
+	#else
 	public static inline function make( high : Int32, low : Int32 ) : Int64
 	public static inline function make( high : Int32, low : Int32 ) : Int64
-		return new Int64( (cast(high, __Int64) << 32) | (cast(low, __Int64)& (untyped __java__('0xffffffffL') : Int64)) );
+		return new Int64( ((cast high : __Int64) << 32) | ((cast low : __Int64) & (untyped __java__('0xffffffffL') : Int64)) );
+	#end
 
 
 	private inline function new(x : __Int64)
 	private inline function new(x : __Int64)
 		this = x;
 		this = x;

+ 93 - 0
std/jvm/DynamicObject.hx

@@ -0,0 +1,93 @@
+package jvm;
+
+import haxe.ds.StringMap;
+
+@:keep
+@:native('haxe.jvm.DynamicObject')
+@:nativeGen
+class DynamicObject implements java.lang.Cloneable extends Object {
+	static var __hx_toString_depth  = 0;
+
+	var _hx_fields:Null<StringMap<Dynamic>>;
+
+	public var _hx_deletedAField:Null<Bool>;
+
+	public function toString() {
+		if (__hx_toString_depth >= 5) {
+			return "...";
+		}
+		++__hx_toString_depth;
+		_hx_initReflection();
+		if (_hx_hasField("toString")) {
+			--__hx_toString_depth;
+			return _hx_getField("toString")();
+		}
+		var buf = new StringBuf();
+		buf.addChar("{".code);
+		var first = true;
+		try {
+			for (key in _hx_fields.keys()) {
+				buf.add(key);
+				buf.add(": ");
+				buf.add(_hx_fields.get(key));
+				if (first) {
+					first = false;
+					buf.add(", ");
+				}
+			}
+		} catch(e:Dynamic) {
+			--__hx_toString_depth;
+			throw(e);
+		}
+		--__hx_toString_depth;
+		buf.addChar("}".code);
+		return buf.toString();
+	}
+
+	final public function _hx_deleteField(name:String) {
+		_hx_initReflection();
+		_hx_deletedAField = true;
+		try {
+			Jvm.writeFieldNoObject(this, name, null);
+		} catch (_:Dynamic) {}
+		return _hx_fields.remove(name);
+	}
+
+	final public function _hx_getFields() {
+		_hx_initReflection();
+		return [for (key in _hx_fields.keys()) key];
+	}
+
+	override public function _hx_getField<T>(name:String) {
+		_hx_initReflection();
+		return _hx_fields.get(name);
+	}
+
+	final public function _hx_hasField(name:String) {
+		_hx_initReflection();
+		return _hx_fields.exists(name);
+	}
+
+	override public function _hx_setField(name:String, value:Dynamic) {
+		_hx_initReflection();
+		_hx_fields.set(name, value);
+	}
+
+	final public function _hx_clone() {
+		var clone:DynamicObject = (cast this : java.lang.Object).clone();
+		if (_hx_fields != null) {
+			clone._hx_fields = this._hx_fields.copy();
+		}
+		return clone;
+	}
+
+	final function _hx_initReflection() {
+		if (_hx_fields == null) {
+			_hx_fields = _hx_getKnownFields();
+		}
+	}
+
+	function _hx_getKnownFields():StringMap<Dynamic> {
+		return new StringMap();
+	}
+}

+ 5 - 0
std/jvm/EmptyConstructor.hx

@@ -0,0 +1,5 @@
+package jvm;
+
+@:keep
+@:native('haxe.jvm.EmptyConstructor')
+class EmptyConstructor {}

+ 25 - 0
std/jvm/Enum.hx

@@ -0,0 +1,25 @@
+package jvm;
+
+import java.NativeArray;
+
+@:keep
+@:native('haxe.jvm.Enum')
+class Enum<T> extends java.lang.Enum<T> {
+	@:nativeGen public function new(index:Int, name:String) {
+		super(name, index);
+	}
+
+	public function _hx_getParameters() {
+		return new java.NativeArray(0);
+	}
+
+	@:overload
+	override public function toString() {
+		var baseName = Type.getEnumConstructs(Type.getEnum(cast this))[ordinal()];
+		var parameters = Type.enumParameters(cast this);
+		if (parameters.length == 0) {
+			return baseName;
+		}
+		return '$baseName(${@:privateAccess parameters.join(",")})';
+	}
+}

+ 38 - 0
std/jvm/Exception.hx

@@ -0,0 +1,38 @@
+package jvm;
+
+@:keep
+@:native('haxe.jvm.Exception')
+class Exception<T> extends java.lang.Exception {
+	static public var exception = new java.lang.ThreadLocal<java.lang.Throwable>();
+
+	static public function setException(exc:java.lang.Throwable) {
+		exception.set(exc);
+	}
+
+	static public function currentException() {
+		return exception.get();
+	}
+
+	public var value:T;
+
+	public function new(value:T) {
+		super();
+		this.value = value;
+	}
+
+	@:overload override public function toString() {
+		return Std.string(value);
+	}
+
+	public function unwrap() {
+		return value;
+	}
+
+	static public function wrap<T>(t:Null<T>) {
+		if (Jvm.instanceof(t, java.lang.Exception)) {
+			return (cast t : java.lang.Exception);
+		} else {
+			return new Exception(t);
+		}
+	}
+}

+ 495 - 0
std/jvm/Jvm.hx

@@ -0,0 +1,495 @@
+package jvm;
+
+import haxe.extern.Rest;
+import haxe.Constraints;
+import Enum;
+import jvm.DynamicObject;
+import jvm.Exception;
+import jvm.EmptyConstructor;
+import jvm.Object;
+import jvm.annotation.ClassReflectionInformation;
+import jvm.annotation.EnumReflectionInformation;
+import jvm.annotation.EnumValueReflectionInformation;
+import java.lang.invoke.*;
+import java.NativeArray;
+import haxe.ds.Vector;
+import haxe.ds.Option;
+
+@:keep
+@:native('haxe.jvm.Jvm')
+class Jvm {
+	extern static public function instanceof<S, T>(obj:S, type:T):Bool;
+
+	extern static public function referenceEquals<T>(v1:T, v2:T):Bool;
+
+	extern static public function invokedynamic<T>(bootstrapMethod:Function, fieldName:String, staticArguments:Array<Dynamic>, rest:Rest<Dynamic>):T;
+
+	static public function stringCompare(v1:String, v2:String):Int {
+		if (v1 == null) {
+			return v2 == null ? 0 : 1;
+		}
+		if (v2 == null) {
+			return -1;
+		}
+		return (cast v1 : java.NativeString).compareTo(v2);
+	}
+
+	static public function compare<T>(v1:T, v2:T):Int {
+		return Reflect.compare(v1, v2);
+	}
+
+	// calls
+
+	static public function getArgumentTypes(args:NativeArray<Dynamic>):NativeArray<java.lang.Class<Dynamic>> {
+		var argTypes:NativeArray<java.lang.Class<Dynamic>> = new NativeArray(args.length);
+		for (i in 0...args.length) {
+			var arg = (cast args[i] : java.lang.Object);
+			argTypes[i] = arg == null ? (cast java.lang.Object) : arg.getClass();
+		}
+		return argTypes;
+	}
+
+	static public function unifyCallArguments(args:NativeArray<Dynamic>, params:NativeArray<java.lang.Class<Dynamic>>, allowPadding:Bool = false):Option<NativeArray<Dynamic>> {
+		var callArgs:NativeArray<Dynamic> = {
+			if (args.length < params.length) {
+				var callArgs = new NativeArray(params.length);
+				Vector.blit(cast args, 0, cast callArgs, 0, args.length);
+				callArgs;
+			} else {
+				Vector.fromData(args).copy().toData();
+			}
+		}
+		if (params.length < args.length) {
+			return None;
+		}
+		for (i in 0...params.length) {
+			var paramType = params[i];
+			if (i >= args.length) {
+				if (paramType == cast Bool) {
+					callArgs[i] = false;
+				} else if (paramType == cast Float) {
+					callArgs[i] = 0.0;
+				} else if (paramType == cast Int) {
+					callArgs[i] = 0;
+				} else {
+					if (!allowPadding) {
+						return None;
+					}
+					callArgs[i] = null;
+				}
+				continue;
+			}
+			var argValue = args[i];
+			if (argValue == null) {
+				if (paramType.isPrimitive()) {
+					if (paramType == cast Bool) {
+						callArgs[i] = false;
+					} else if (paramType == cast Float) {
+						callArgs[i] = 0.0;
+					} else if (paramType == cast Int) {
+						callArgs[i] = 0;
+					} else {
+						throw 'Unexpected basic type: $paramType';
+					}
+				} else {
+					callArgs[i] = null;
+				}
+				continue;
+			};
+			var argType = (argValue : java.lang.Object).getClass();
+			var arg = getWrapperClass(paramType);
+			if (arg.isAssignableFrom(argType)) {
+				callArgs[i] = args[i];
+				continue;
+			}
+			if (arg == (cast java.lang.Double.DoubleClass) && argType == cast java.lang.Integer.IntegerClass) {
+		 		callArgs[i] = nullIntToNullFloat(args[i]);
+			} else {
+				return None;
+			}
+		}
+		return Some(callArgs);
+	}
+
+	static public function call(mh:java.lang.invoke.MethodHandle, args:NativeArray<Dynamic>) {
+		var params = mh.type().parameterArray();
+		return switch (unifyCallArguments(args, params, true)) {
+			case Some(args): mh.invokeWithArguments(args);
+			case None: mh.invokeWithArguments(args);
+		}
+	}
+
+	// casts
+
+	static public function dynamicToNullFloat<T>(d:T):Null<Float> {
+		if (instanceof(d, java.lang.Integer.IntegerClass)) {
+			return nullIntToNullFloat(cast d);
+		}
+		// TODO: need a better strategy to avoid infinite recursion here
+		return cast d;
+	}
+
+	static public function nullIntToNullFloat(i:Null<Int>):Null<Float> {
+		if (i == null) {
+			return null;
+		}
+		return (cast i : java.lang.Number).intValue();
+	}
+
+	static public function toByte(d:Dynamic) {
+		return d == null ? 0 : (d : java.lang.Byte).byteValue();
+	}
+
+	static public function toChar(d:Dynamic) {
+		return d == null ? 0 : (d : java.lang.Character).charValue();
+	}
+
+	static public function toDouble(d:Dynamic) {
+		return d == null ? 0. : (d : java.lang.Number).doubleValue();
+	}
+
+	static public function toFloat(d:Dynamic):Single {
+		return d == null ? 0. : (d : java.lang.Number).floatValue();
+	}
+
+	static public function toInt(d:Dynamic) {
+		return d == null ? 0 : (d : java.lang.Number).intValue();
+	}
+
+	static public function toLong(d:Dynamic) {
+		return d == null ? 0 : (d : java.lang.Long).longValue();
+	}
+
+	static public function toShort(d:Dynamic) {
+		return d == null ? 0 : (d : java.lang.Short).shortValue();
+	}
+
+	static public function toBoolean(d:Dynamic) {
+		return d == null ? false : (d : java.lang.Boolean).booleanValue();
+	}
+
+	static public function getWrapperClass<S, T>(c:java.lang.Class<S>):java.lang.Class<S> {
+		if (!c.isPrimitive()) {
+			return c;
+		}
+		// TODO: other basic types
+		return if (c == cast Int) {
+			cast java.lang.Integer.IntegerClass;
+		} else if (c == cast Float) {
+			cast java.lang.Double.DoubleClass;
+		} else if (c == cast Bool) {
+			cast java.lang.Boolean.BooleanClass;
+		} else {
+			c;
+		}
+	}
+
+	// access
+
+	static public function arrayRead(obj:Dynamic, index:Int) {
+		if (instanceof(obj, Array)) {
+			return (obj : Array<Dynamic>)[index];
+		}
+		throw 'Cannot array-read on $obj';
+	}
+
+	static public function arrayWrite(obj:Dynamic, index:Int, value:Dynamic):Void {
+		if (instanceof(obj, Array)) {
+			(obj : Array<Dynamic>)[index] = value;
+			return;
+		}
+		throw 'Cannot array-write on $obj';
+	}
+
+	static public function bootstrap(caller:MethodHandles.MethodHandles_Lookup, name:String, type:MethodType):CallSite {
+		var handle = caller.findStatic(caller.lookupClass(), name, type);
+		return new ConstantCallSite(handle);
+	}
+
+	static public function readFieldNoObject(obj:Dynamic, name:String):Dynamic {
+		var isStatic = instanceof(obj, java.lang.Class);
+		var cl = isStatic ? obj : (obj : java.lang.Object).getClass();
+		try {
+			var field = cl.getField(name);
+			field.setAccessible(true);
+			return field.get(obj);
+		} catch (_:java.lang.NoSuchFieldException) {
+			while (cl != null) {
+				var methods = cl.getMethods();
+				for (m in methods) {
+					if (m.getName() == name) {
+						var method = java.lang.invoke.MethodHandles.lookup().unreflect(m);
+						if (!isStatic || cl == cast java.lang.Class) {
+							method = method.bindTo(obj);
+						}
+						return method;
+					}
+				}
+				if (isStatic) {
+					if (cl == cast java.lang.Class) {
+						break;
+					}
+					cl = cast java.lang.Class;
+				} else {
+					cl = cl.getSuperclass();
+				}
+			}
+			return null;
+		}
+	}
+
+	static public function readField(obj:Dynamic, name:String):Dynamic {
+		if (obj == null || name == null) {
+			return null;
+		}
+		if (instanceof(obj, jvm.Object)) {
+			return (cast obj : jvm.Object)._hx_getField(name);
+		}
+		if (instanceof(obj, java.NativeString)) {
+			switch (name) {
+				case "length": return (obj : String).length;
+				case "charAt": return (cast jvm.StringExt.charAt : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "charCodeAt": return (cast jvm.StringExt.charCodeAt : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "indexOf": return (cast jvm.StringExt.indexOf : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "iterator": return function() return new haxe.iterators.StringIterator(obj);
+				case "keyValueIterator": return function() return new haxe.iterators.StringKeyValueIterator(obj);
+				case "lastIndexOf": return (cast jvm.StringExt.lastIndexOf : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "split": return (cast jvm.StringExt.split : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "substr": return (cast jvm.StringExt.substr : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "substring": return (cast jvm.StringExt.substring : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "toLowerCase": return (cast jvm.StringExt.toLowerCase : java.lang.invoke.MethodHandle).bindTo(obj);
+				case "toUpperCase": return (cast jvm.StringExt.toUpperCase : java.lang.invoke.MethodHandle).bindTo(obj);
+			}
+		}
+		return readFieldNoObject(obj, name);
+	}
+
+	static public function writeFieldNoObject<T>(obj:Dynamic, name:String, value:T) {
+		try {
+			var cl = (obj : java.lang.Object).getClass();
+			var field = cl.getField(name);
+			field.setAccessible(true);
+			try {
+				field.set(obj, value);
+			} catch (_:java.lang.IllegalArgumentException) {
+				if (value == null) {
+					field.setByte(obj, 0); // rely on widening
+				} else if (field.getType() == (cast Int) && instanceof(value, java.lang.Number)) {
+					// Can happen with ++ on Dynamic because that defaults to Float
+					field.setInt(obj, (cast value : java.lang.Number).intValue());
+				}
+			}
+		} catch (_:java.lang.NoSuchFieldException) {
+			return;
+		}
+	}
+
+	static public function writeField<T>(obj:Dynamic, name:String, value:T) {
+		if (obj == null || name == null) {
+			return;
+		}
+		if (instanceof(obj, Object)) {
+			return (obj : Object)._hx_setField(name, value);
+		}
+		writeFieldNoObject(obj, name, value);
+	}
+
+	// string
+
+	static public function toString<T:java.lang.Object>(obj:T):String {
+		if (obj == null) {
+			return "null";
+		} else if (instanceof(obj, java.lang.Double.DoubleClass)) {
+			var n:java.lang.Number = cast obj;
+			if (n.doubleValue() == n.intValue()) {
+				return java.lang.Integer.IntegerClass.valueOf(n.intValue()).toString();
+			}
+			return obj.toString();
+		} else {
+			return obj.toString();
+		}
+	}
+
+	static public function stringConcat<A:java.lang.Object, B:java.lang.Object>(a:A, b:B):String {
+		return (cast toString(a) : java.NativeString).concat(toString(b));
+	}
+
+	// ops
+
+	static public function opAdd<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.NativeString) || instanceof(b, java.NativeString)) {
+			return stringConcat(a, b);
+		}
+		if (instanceof(a, java.lang.Double.DoubleClass) || instanceof(b, java.lang.Double.DoubleClass)) {
+			return toDouble(a) + toDouble(b);
+		}
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) + toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) + toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opSub<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass) || instanceof(b, java.lang.Double.DoubleClass)) {
+			return toDouble(a) - toDouble(b);
+		}
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) - toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) - toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opMul<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass) || instanceof(b, java.lang.Double.DoubleClass)) {
+			return toDouble(a) * toDouble(b);
+		}
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) * toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) * toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opDiv<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass) || instanceof(b, java.lang.Double.DoubleClass)) {
+			return toDouble(a) / toDouble(b);
+		}
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) / toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) / toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opMod<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass) || instanceof(b, java.lang.Double.DoubleClass)) {
+			return toDouble(a) % toDouble(b);
+		}
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) % toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) % toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opAnd<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) & toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) & toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opOr<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) | toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) | toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opXor<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) ^ toLong(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) ^ toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opShl<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) << toInt(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) << toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opShr<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) >> toInt(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) >> toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opUshr<T1:java.lang.Object, T2:java.lang.Object>(a:T1, b:T2):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass) || instanceof(b, java.lang.Long.LongClass)) {
+			return toLong(a) >>> toInt(b);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass) || instanceof(b, java.lang.Integer.IntegerClass)) {
+			return toInt(a) >>> toInt(b);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opIncrement<T1:java.lang.Object>(a:T1):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass)) {
+			return toDouble(a) + 1.;
+		}
+		if (instanceof(a, java.lang.Long.LongClass)) {
+			return toLong(a) + 1.;
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass)) {
+			return toInt(a) + 1;
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opDecrement<T1:java.lang.Object>(a:T1):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass)) {
+			return toDouble(a) - 1.;
+		}
+		if (instanceof(a, java.lang.Long.LongClass)) {
+			return toLong(a) - 1.;
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass)) {
+			return toInt(a) - 1;
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opNeg<T1:java.lang.Object>(a:T1):Dynamic {
+		if (instanceof(a, java.lang.Double.DoubleClass)) {
+			return -toDouble(a);
+		}
+		if (instanceof(a, java.lang.Long.LongClass)) {
+			return -toLong(a);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass)) {
+			return -toInt(a);
+		}
+		throw "Invalid operation";
+	}
+
+	static public function opNegBits<T1:java.lang.Object>(a:T1):Dynamic {
+		if (instanceof(a, java.lang.Long.LongClass)) {
+			return ~toLong(a);
+		}
+		if (instanceof(a, java.lang.Integer.IntegerClass)) {
+			return ~toInt(a);
+		}
+		throw "Invalid operation";
+	}
+}

+ 27 - 0
std/jvm/NativeTools.hx

@@ -0,0 +1,27 @@
+package jvm;
+
+extern class ObjectTools {
+	static public inline function object<T>(t:T):java.lang.Object {
+		return cast t;
+	}
+}
+
+extern class NativeClassTools {
+	static public inline function native<T>(c:Class<T>):java.lang.Class<T> {
+		return cast c;
+	}
+
+	static public inline function haxe<T>(c:java.lang.Class<T>):Class<T> {
+		return cast c;
+	}
+
+	static public inline function haxeEnum<T>(c:java.lang.Class<T>):std.Enum<T> {
+		return cast c;
+	}
+}
+
+extern class NativeEnumTools {
+	static public inline function native<T>(e:std.Enum<Dynamic>):java.lang.Class<T> {
+		return cast e;
+	}
+}

+ 16 - 0
std/jvm/Object.hx

@@ -0,0 +1,16 @@
+package jvm;
+
+@:keep
+@:native('haxe.jvm.Object')
+@:nativeGen
+class Object {
+	public function new() { }
+
+	public function _hx_getField(name:String) {
+		return Jvm.readFieldNoObject(this, name);
+	}
+
+	public function _hx_setField(name:String, value:Dynamic) {
+		return Jvm.writeFieldNoObject(this, name, value);
+	}
+}

+ 109 - 0
std/jvm/StringExt.hx

@@ -0,0 +1,109 @@
+package jvm;
+
+import java.NativeString;
+
+@:native("haxe.jvm.StringExt")
+class StringExt {
+	public static function fromCharCode(code:Int):String {
+		var a = new java.NativeArray(1);
+		a[0] = code;
+		return new String(a, 0, 1);
+	}
+
+	public static function charAt(me:String, index:Int):String {
+		if (index >= me.length || index < 0)
+			return "";
+		else
+			return java.lang.Character._toString((cast me : NativeString).charAt(index));
+	}
+
+	public static function charCodeAt(me:String, index:Int):Null<Int> {
+		if (index >= me.length || index < 0)
+			return null;
+		else
+			return cast((cast me : NativeString).charAt(index), Int);
+	}
+
+	public static function indexOf(me:String, str:String, startIndex:Null<Int>) {
+		return
+			if (startIndex == null) (cast me : NativeString).indexOf(str)
+			else (cast me : NativeString).indexOf(str, startIndex);
+	}
+
+	public static function lastIndexOf(me:String, str:String, ?startIndex:Int):Int {
+		if (startIndex == null || startIndex > me.length || startIndex < 0) {
+			startIndex = me.length - 1;
+		}
+		return (cast me : NativeString).lastIndexOf(str, startIndex);
+	}
+
+	public static function split(me:String, delimiter:String):Array<String> {
+		var ret = [];
+		if (delimiter.length == 0) {
+			for (i in 0...me.length) {
+				ret.push(me.charAt(i));
+			}
+		} else {
+			var start = 0;
+			var pos = me.indexOf(delimiter, start);
+			while (pos >= 0) {
+				ret.push((cast me : NativeString).substring(start, pos));
+				start = pos + delimiter.length;
+				pos = me.indexOf(delimiter, start);
+			}
+			ret.push((cast me : NativeString).substring(start));
+		}
+		return ret;
+	}
+
+	public static function substr(me:String, pos:Int, ?len:Int):String {
+		var len:Int = len == null ? me.length : len;
+		if (pos != 0 && len < 0) {
+			return "";
+		}
+		if (pos < 0) {
+			pos = me.length + pos;
+			if (pos < 0) {
+				pos = 0;
+			}
+		} else if (len < 0) {
+			len = me.length + len - pos;
+		}
+		if (pos + len > me.length) {
+			len = me.length - pos;
+		}
+		if (pos < 0 || len <= 0) {
+			return "";
+		}
+		return (cast me : NativeString).substring(pos, pos + len);
+	}
+
+	public static function substring(me:String, startIndex:Int, ?endIndex:Int):String {
+		var endIndex:Int = endIndex == null ? me.length : endIndex;
+		if (endIndex < 0) {
+			endIndex = 0;
+		} else if (endIndex > me.length) {
+			endIndex = me.length;
+		}
+		if (startIndex < 0) {
+			startIndex = 0;
+		} else if (startIndex > me.length) {
+			startIndex = me.length;
+		}
+
+		if (startIndex > endIndex) {
+			var tmp = startIndex;
+			startIndex = endIndex;
+			endIndex = tmp;
+		}
+		return (cast me : NativeString).substring(startIndex, endIndex);
+	}
+
+	public static function toLowerCase(me:String):String {
+		return me.toLowerCase();
+	}
+
+	public static function toUpperCase(me:String):String {
+		return me.toUpperCase();
+	}
+}

+ 182 - 0
std/jvm/_std/Reflect.hx

@@ -0,0 +1,182 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import jvm.Jvm;
+
+@:coreApi
+class Reflect {
+	public static function hasField(o:Dynamic, field:String):Bool {
+		if (!Jvm.instanceof(o, jvm.DynamicObject)) {
+			var c:java.lang.Class <Dynamic>= Jvm.instanceof(o, java.lang.Class) ? cast o : (cast o : java.lang.Object).getClass();
+			try {
+				c.getField(field);
+				return true;
+			} catch(e:Dynamic) {
+				return false;
+			}
+		}
+		return (cast o : jvm.DynamicObject)._hx_hasField(field);
+	}
+
+	public static function field(o:Dynamic, field:String):Dynamic {
+		return Jvm.readField(o, field);
+	}
+
+	public static function setField(o:Dynamic, field:String, value:Dynamic):Void {
+		Jvm.writeField(o, field, value);
+	}
+
+	public static function getProperty(o:Dynamic, field:String):Dynamic {
+		var f = Reflect.field(o, "get_" + field);
+		if (f != null) {
+			return f();
+		}
+		return Reflect.field(o, field);
+	}
+
+	public static function setProperty(o:Dynamic, field:String, value:Dynamic):Void {
+		var f = Reflect.field(o, "set_" + field);
+		if (f != null) {
+			f(value);
+			return;
+		}
+		Reflect.setField(o, field, value);
+	}
+
+	public static function callMethod(o:Dynamic, func:haxe.Constraints.Function, args:Array<Dynamic>):Dynamic {
+		return Jvm.call(cast func, @:privateAccess args.getNative());
+	}
+
+	public static function fields(o:Dynamic):Array<String> {
+		if (!Jvm.instanceof(o, jvm.DynamicObject)) {
+			if (Jvm.instanceof(o, java.lang.Class)) {
+				return Type.getClassFields(o);
+			}
+			var c = (o : java.lang.Object).getClass();
+			var ret = [];
+			for (f in c.getDeclaredFields()) {
+				if (java.lang.reflect.Modifier.isStatic(f.getModifiers()) == false && !f.isSynthetic()) {
+					ret.push(f.getName());
+				}
+			}
+			return ret;
+		}
+		return (cast o : jvm.DynamicObject)._hx_getFields();
+	}
+
+	public static function isFunction(f:Dynamic):Bool {
+		return Jvm.instanceof(f, java.lang.invoke.MethodHandle);
+	}
+
+	public static function compare<T>(a:T, b:T):Int {
+		if (Jvm.referenceEquals(a, b)) {
+			return 0;
+		}
+		if (a == null) {
+			return -1;
+		}
+		if (b == null) {
+			return 1;
+		}
+		if (Jvm.instanceof(a, java.lang.Number) && Jvm.instanceof(b, java.lang.Number)) {
+			return java.lang.Long.compare((cast a : java.lang.Number).longValue(), (cast b : java.lang.Number).longValue());
+		}
+		if (Jvm.instanceof(a, java.NativeString)) {
+			if (!Jvm.instanceof(b, java.NativeString)) {
+				return -1;
+			}
+			return (cast a : java.NativeString).compareTo(cast b);
+		}
+		return -1;
+	}
+
+	public static function compareMethods(f1:Dynamic, f2:Dynamic):Bool {
+		if (f1 == f2) {
+			return true;
+		}
+		if (f1 == null || f2 == null) {
+			return false;
+		}
+		var c1 = (f1 : java.lang.Object).getClass();
+		if (c1 != (f2 : java.lang.Object).getClass()) {
+			return false;
+		}
+		try {
+			var arg0 = c1.getDeclaredField("argL0");
+			arg0.setAccessible(true);
+			var arg1 = c1.getDeclaredField("argL1");
+			arg1.setAccessible(true);
+			return arg0.get(f1) == arg0.get(f2) && arg1.get(f1) == arg1.get(f2);
+		} catch(_:Dynamic) {
+			return false;
+		}
+	}
+
+	public static function isObject(v:Dynamic):Bool {
+		if (v == null) {
+			return false;
+		}
+		if (Jvm.instanceof(v, jvm.Enum)) {
+			return false;
+		}
+		if (Jvm.instanceof(v, java.lang.Number)) {
+			return false;
+		}
+		if (Jvm.instanceof(v, java.lang.Boolean.BooleanClass)) {
+			return false;
+		}
+		if (Jvm.instanceof(v, java.lang.invoke.MethodHandle)) {
+			return false;
+		}
+		return true;
+	}
+
+	public static function isEnumValue(v:Dynamic):Bool {
+		if (v == null) {
+			return false;
+		}
+		return @:privateAccess Type.isEnumValueClass((cast v : java.lang.Object).getClass());
+	}
+
+	public static function deleteField(o:Dynamic, field:String):Bool {
+		if (!Jvm.instanceof(o, jvm.DynamicObject)) {
+			return false;
+		}
+		return (cast o : jvm.DynamicObject)._hx_deleteField(field);
+	}
+
+	public static function copy<T>(o:T):T {
+		if (!Jvm.instanceof(o, jvm.DynamicObject)) {
+			return null;
+		}
+		var o = (cast o : jvm.DynamicObject);
+		return cast o._hx_clone();
+	}
+
+	@:overload(function(f:Array<Dynamic>->Void):Dynamic {})
+	public static function makeVarArgs(f:Array<Dynamic>->Dynamic):Dynamic {
+		var fAdapt = function(args:java.NativeArray<Dynamic>) {
+			return f(@:privateAccess Array.ofNative(args));
+		}
+		return (cast fAdapt : java.lang.invoke.MethodHandle).asVarargsCollector(cast java.NativeArray);
+	}
+}

+ 106 - 0
std/jvm/_std/Std.hx

@@ -0,0 +1,106 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import jvm.Jvm;
+
+@:coreApi
+class Std {
+	public static function is(v:Dynamic, t:Dynamic):Bool {
+		if (v == null || t == null) {
+			return false;
+		}
+		var clt:java.lang.Class<Dynamic> = cast t;
+		if (clt == null) {
+			return false;
+		}
+		if (clt == cast java.lang.Object) {
+			return true;
+		}
+		clt = Jvm.getWrapperClass(clt);
+		if (clt == cast java.lang.Double.DoubleClass) {
+			// Haxe semantics: any number is assignable to Float
+			clt = cast java.lang.Number;
+		} else if (clt == cast java.lang.Integer.IntegerClass) {
+			if (!Jvm.instanceof(v, java.lang.Number)) {
+				return false;
+			}
+			var n = (cast v : java.lang.Number);
+			// Haxe semantics: 2.0 is an integer...
+			return n.doubleValue() == n.intValue();
+		}
+		return clt.isAssignableFrom((v : java.lang.Object).getClass());
+	}
+
+	public static function string(s:Dynamic):String {
+		return jvm.Jvm.toString(s);
+	}
+
+	public static function int(x:Float):Int {
+		return cast x;
+	}
+
+	static var integerFormatter = java.text.NumberFormat.getIntegerInstance(java.util.Locale.US);
+	static var doubleFormatter = {
+		var fmt = new java.text.DecimalFormat();
+		fmt.setParseBigDecimal(true);
+		fmt.setDecimalFormatSymbols(new java.text.DecimalFormatSymbols(java.util.Locale.US));
+		fmt;
+	};
+
+	public static function parseInt(x:String):Null<Int> {
+		try {
+			x = StringTools.trim(x);
+			if (x.length < 2) {
+				return integerFormatter.parse(x).intValue();
+			}
+			switch ((cast x : java.NativeString).codePointAt(1)) {
+				case 'x'.code | 'X'.code:
+					return java.lang.Integer.decode(x).intValue();
+				case _:
+					return integerFormatter.parse(x).intValue();
+			}
+		} catch(_:Dynamic) {
+			return null;
+		}
+	}
+
+	public static function parseFloat(x:String):Float {
+		try {
+			x = StringTools.trim(x);
+			x = x.split("+").join(""); // TODO: stupid
+			return doubleFormatter.parse(x.toUpperCase()).doubleValue();
+		} catch(_:Dynamic) {
+			return Math.NaN;
+		}
+	}
+
+	inline public static function instance<T:{}, S:T>(value:T, c:Class<S>):S {
+		return Std.is(value, c) ? cast value : null;
+	}
+
+	public static function random(x:Int):Int {
+		if (x <= 0) {
+			return 0;
+		}
+		return Std.int(Math.random() * x);
+	}
+}

+ 90 - 0
std/jvm/_std/String.hx

@@ -0,0 +1,90 @@
+/*
+ * Copyright (C)2005-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+import haxe.iterators.StringIterator;
+import haxe.iterators.StringKeyValueIterator;
+
+@:coreApi
+@:native("java.lang.String")
+extern class String implements java.lang.CharSequence {
+
+	var length(default,null) : Int;
+
+	@:overload(function(b:haxe.io.BytesData, offset:Int, length:Int, charsetName:String):Void { })
+	@:overload(function(b:haxe.io.BytesData, offset:Int, length:Int):Void { })
+	@:overload(function(b:java.NativeArray<java.StdTypes.Char16>):Void { })
+	@:overload(function(b:java.NativeArray<Int>, offset:Int, count:Int):Void { })
+	function new(string:String) : Void;
+
+	function toUpperCase() : String;
+	function toLowerCase() : String;
+
+	@:runtime inline function charAt( index : Int) : String {
+		return jvm.StringExt.charAt(this, index);
+	}
+
+	inline function charCodeAt( index : Int) : Null<Int> {
+		return jvm.StringExt.charCodeAt(this, index);
+	}
+
+	inline function indexOf( str : String, ?startIndex : Int ) : Int {
+		return jvm.StringExt.indexOf(this, str, startIndex);
+	}
+
+	@:runtime inline function lastIndexOf( str : String, ?startIndex : Int ) : Int {
+		return jvm.StringExt.lastIndexOf(this, str, startIndex);
+	}
+
+	@:runtime inline function split( delimiter : String ) : Array<String> {
+		return jvm.StringExt.split(this, delimiter);
+	}
+
+	@:runtime inline function substr( pos : Int, ?len : Int ) : String {
+		return jvm.StringExt.substr(this, pos, len);
+	}
+
+	@:runtime inline function substring( startIndex : Int, ?endIndex : Int ) : String {
+		return jvm.StringExt.substring(this, startIndex, endIndex);
+	}
+
+	function toString() : String;
+
+	@:pure @:runtime inline function iterator() : StringIterator {
+		return new StringIterator(this);
+	}
+
+	@:pure @:runtime inline function keyValueIterator() : StringKeyValueIterator {
+		return new StringKeyValueIterator(this);
+	}
+
+	private function compareTo( anotherString : String ) : Int;
+
+	private function codePointAt( idx : Int ) : Int;
+
+	@:overload(function() : haxe.io.BytesData { })
+	private function getBytes(encoding:String) : haxe.io.BytesData;
+
+	@:runtime static inline function fromCharCode( code : Int ) : String {
+		return jvm.StringExt.fromCharCode(code);
+	}
+
+}

+ 329 - 0
std/jvm/_std/Type.hx

@@ -0,0 +1,329 @@
+import java.lang.invoke.*;
+import java.lang.NoSuchMethodException;
+import jvm.annotation.*;
+import jvm.Jvm;
+
+using jvm.NativeTools.NativeClassTools;
+using jvm.NativeTools.NativeEnumTools;
+using jvm.NativeTools.ObjectTools;
+
+enum ValueType {
+	TNull;
+	TInt;
+	TFloat;
+	TBool;
+	TObject;
+	TFunction;
+	TClass(c:Class<Dynamic>);
+	TEnum(e:Enum<Dynamic>);
+	TUnknown;
+}
+
+@:coreApi
+class Type {
+	static function isEnumClass<T>(c:java.lang.Class<T>):Bool {
+		// TODO: have to be careful if we ever decide to omit EnumReflectionInformation
+		// Maybe a separate HaxeEnum annotation would be better here
+		return c.isAnnotationPresent(cast EnumReflectionInformation);
+	}
+
+	static function isEnumValueClass<T>(c:java.lang.Class<T>):Bool {
+		// TODO: have to be careful if we ever decide to omit EnumValueReflectionInformation
+		// Maybe a separate HaxeEnum annotation would be better here
+		return c.isAnnotationPresent(cast EnumValueReflectionInformation);
+	}
+
+	public static function getClass<T>(o:T):Class<T> {
+		if (o == null) {
+			return null;
+		}
+		if (Jvm.instanceof(o, Class)) {
+			return null;
+		}
+		var c = o.object().getClass();
+		if (isEnumValueClass(c)) {
+			return null;
+		}
+		if (c == jvm.DynamicObject.native()) {
+			return null;
+		}
+		return c.haxe();
+	}
+
+	public static function getEnum(o:EnumValue):Enum<Dynamic> {
+		if (o == null) {
+			return null;
+		}
+		var c = o.object().getClass().getSuperclass();
+		if (!c.isAnnotationPresent(cast EnumReflectionInformation)) {
+			return null;
+		}
+		return c.haxeEnum();
+	}
+
+	public static function getSuperClass(c:Class<Dynamic>):Class<Dynamic> {
+		if (c == String) {
+			return null;
+		}
+		var c = c.native();
+		var cSuper = c.getSuperclass();
+		if (cSuper == null) {
+			return null;
+		}
+		var annotation = c.getAnnotation((cast ClassReflectionInformation : java.lang.Class<ClassReflectionInformation>));
+		if (annotation != null && annotation.hasSuperClass() == false) {
+			return null;
+		}
+		return cSuper.haxe();
+	}
+
+	public static function getClassName(c:Class<Dynamic>):String {
+		return switch (c.native().getName()) {
+			case "java.lang.String": "String";
+			case "java.lang.Math": "Math";
+			case s: s;
+		}
+	}
+
+	public static function getEnumName(e:Enum<Dynamic>):String {
+		return e.native().getName();
+	}
+
+	public static function resolveClass(name:String):Class<Dynamic> {
+		if (name == "String") {
+			return java.NativeString;
+		} else if (name == "Math") {
+			return java.lang.Math;
+		}
+		return try {
+			java.lang.Class.forName(name).haxe();
+		} catch (e:java.lang.ClassNotFoundException) {
+			return null;
+		}
+	}
+
+	public static function resolveEnum(name:String):Enum<Dynamic> {
+		return try {
+			var c = java.lang.Class.forName(name);
+			if (!isEnumClass(c)) {
+				null;
+			} else {
+				c.haxeEnum();
+			}
+		} catch (e:java.lang.ClassNotFoundException) {
+			return null;
+		}
+	}
+
+	static final emptyArg = {
+		var a = new java.NativeArray(1);
+		a[0] = (null : jvm.EmptyConstructor);
+		a;
+	}
+
+	static final emptyClass = {
+		var a = new java.NativeArray(1);
+		a[0] = jvm.EmptyConstructor.native();
+		a;
+	}
+
+	public static function createInstance<T>(cl:Class<T>, args:Array<Dynamic>):T {
+		var args = @:privateAccess args.getNative();
+		var cl = cl.native();
+		var argTypes = Jvm.getArgumentTypes(args);
+		var methodType = MethodType.methodType(cast Void, argTypes);
+		// 1. attempt: direct constructor lookup
+		try {
+			var ctor = MethodHandles.lookup().findConstructor(cl, methodType);
+            return ctor.invokeWithArguments(args);
+		} catch(_:NoSuchMethodException) { }
+
+		// 2. attempt direct new lookup
+		try {
+			var ctor = MethodHandles.lookup().findVirtual(cl, "new", methodType);
+			var obj = cl.getConstructor(emptyClass).newInstance(emptyArg);
+			ctor.bindTo(obj).invokeWithArguments(args);
+			return obj;
+		} catch (_:NoSuchMethodException) { }
+
+		// 3. attempt: unify actual constructor
+		for (ctor in cl.getDeclaredConstructors()) {
+			switch (Jvm.unifyCallArguments(args, ctor.getParameterTypes())) {
+				case Some(args):
+					return MethodHandles.lookup().unreflectConstructor(ctor).invokeWithArguments(args);
+				case None:
+			}
+		}
+
+		// 4. attempt: unify new
+		for (ctor in cl.getDeclaredMethods()) {
+			if (ctor.getName() != "new") {
+				continue;
+			}
+			switch (Jvm.unifyCallArguments(args, ctor.getParameterTypes())) {
+				case Some(args):
+					return MethodHandles.lookup().unreflect(ctor).invokeWithArguments(args);
+				case None:
+			}
+		}
+
+		return null;
+	}
+
+	public static function createEmptyInstance<T>(cl:Class<T>):T {
+		var annotation = (cl.native().getAnnotation((cast ClassReflectionInformation : java.lang.Class<ClassReflectionInformation>)));
+		if (annotation != null) {
+			return cl.native().getConstructor(emptyClass).newInstance(emptyArg);
+		} else {
+			return cl.native().newInstance();
+		}
+	}
+
+	public static function createEnum<T>(e:Enum<T>, constr:String, ?params:Array<Dynamic>):T {
+		if (params == null || params.length == 0) {
+			var v:Dynamic = Jvm.readField(e, constr);
+			if (!Std.is(v, e)) {
+				throw 'Could not create enum value ${getEnumName(e)}.$constr: Unexpected value $v';
+			}
+			return v;
+		} else {
+			return Reflect.callMethod(null, Jvm.readField(e, constr), params);
+		}
+	}
+
+	public static function createEnumIndex<T>(e:Enum<T>, index:Int, ?params:Array<Dynamic>):T {
+		var clInfo:java.lang.Class<EnumReflectionInformation> = cast EnumReflectionInformation;
+		var annotation = e.native().getAnnotation(clInfo);
+		if (params == null || params.length == 0) {
+			return Jvm.readField(e, annotation.constructorNames()[index]);
+		} else {
+			return Reflect.callMethod(null, Jvm.readField(e, annotation.constructorNames()[index]), params);
+		}
+	}
+
+	static function getFields<T>(c:java.lang.Class<T>, statics:Bool):Array<String> {
+		var ret = [];
+		for (f in c.getDeclaredFields()) {
+			if (java.lang.reflect.Modifier.isStatic(f.getModifiers()) == statics && !f.isSynthetic()) {
+				ret.push(f.getName());
+			}
+		}
+		for (m in c.getDeclaredMethods()) {
+			if (java.lang.reflect.Modifier.isStatic(m.getModifiers()) == statics && !m.isSynthetic()) {
+				ret.push(m.getName());
+			}
+		}
+		return ret;
+	}
+
+	public static function getInstanceFields(c:Class<Dynamic>):Array<String> {
+		var fields = [];
+		while (c != null) {
+			for (field in fields.concat(getFields(c.native(), false))) {
+				if (fields.indexOf(field) == -1) {
+					fields.push(field);
+				}
+			}
+			c = getSuperClass(c);
+		};
+		return fields;
+	}
+
+	public static function getClassFields(c:Class<Dynamic>):Array<String> {
+		return getFields(c.native(), true);
+	}
+
+	public static function getEnumConstructs(e:Enum<Dynamic>):Array<String> {
+		var clInfo:java.lang.Class<EnumReflectionInformation> = cast EnumReflectionInformation;
+		var annotation = e.native().getAnnotation(clInfo);
+		return @:privateAccess Array.ofNative(annotation.constructorNames());
+	}
+
+	public static function typeof(v:Dynamic):ValueType {
+		// could optimize this with an annotation on Haxe classes
+		if (v == null) {
+			return TNull;
+		}
+		if (Jvm.instanceof(v, java.lang.Number)) {
+			var v:java.lang.Number = cast v;
+			if (v.intValue() == v.doubleValue()) {
+				return TInt;
+			}
+			return TFloat;
+		}
+		if (Jvm.instanceof(v, java.lang.Boolean.BooleanClass)) {
+			return TBool;
+		}
+		if (Jvm.instanceof(v, jvm.DynamicObject)) {
+			return TObject;
+		}
+		if (Jvm.instanceof(v, java.lang.invoke.MethodHandle)) {
+			return TFunction;
+		}
+		var c = (cast v : java.lang.Object).getClass();
+		// TODO: native enums?
+		if (isEnumValueClass(c)) {
+			return TEnum(c.getSuperclass().haxeEnum());
+		}
+		if (Jvm.instanceof(v, java.lang.Class)) {
+			return TObject;
+		}
+		return TClass(c.haxe());
+	}
+
+	public static function enumEq<T>(a:T, b:T):Bool {
+		if (a == null) {
+			return b == null;
+		}
+		if (b == null) {
+			return false;
+		}
+		var a:jvm.Enum<Dynamic> = cast a;
+		var b:jvm.Enum<Dynamic> = cast b;
+		if (a.ordinal() != b.ordinal()) {
+			return false;
+		}
+		var params1 = a._hx_getParameters();
+		var params2 = b._hx_getParameters();
+		if (params1.length != params2.length) {
+			return false;
+		}
+		for (i in 0...params1.length) {
+			if (params1[i] != params2[i]) {
+				if (Jvm.instanceof(params1[i], jvm.Enum)) {
+					if (!enumEq(params1[i], params2[i])) {
+						return false;
+					}
+				} else {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	public static function enumConstructor(e:EnumValue):String {
+		return (cast e : java.lang.Enum<Dynamic>).name();
+	}
+
+	public static function enumParameters(e:EnumValue):Array<Dynamic> {
+		var a = (cast e : jvm.Enum<Dynamic>)._hx_getParameters();
+		return @:privateAccess Array.ofNative(a);
+	}
+
+	public static function enumIndex(e:EnumValue):Int {
+		return (cast e : java.lang.Enum<Dynamic>).ordinal();
+	}
+
+	public static function allEnums<T>(e:Enum<T>):Array<T> {
+		var all = getEnumConstructs(e);
+		var ret = [];
+		for (name in all) {
+			var v = Jvm.readField(e, name);
+			if (Jvm.instanceof(v, jvm.Enum)) {
+				ret.push(v);
+			}
+		}
+		return ret;
+	}
+}

+ 8 - 0
std/jvm/annotation/ClassReflectionInformation.hx

@@ -0,0 +1,8 @@
+package jvm.annotation;
+
+@:annotation
+@:native("haxe.jvm.annotation.ClassReflectionInformation")
+@:keep
+interface ClassReflectionInformation extends java.lang.annotation.Annotation {
+	function hasSuperClass():Bool;
+}

+ 8 - 0
std/jvm/annotation/EnumReflectionInformation.hx

@@ -0,0 +1,8 @@
+package jvm.annotation;
+
+@:annotation
+@:native("haxe.jvm.annotation.EnumReflectionInformation")
+@:keep
+interface EnumReflectionInformation extends java.lang.annotation.Annotation {
+	function constructorNames():java.NativeArray<String>;
+}

+ 8 - 0
std/jvm/annotation/EnumValueReflectionInformation.hx

@@ -0,0 +1,8 @@
+package jvm.annotation;
+
+@:annotation
+@:native("haxe.jvm.annotation.EnumValueReflectionInformation")
+@:keep
+interface EnumValueReflectionInformation extends java.lang.annotation.Annotation {
+	function argumentNames():java.NativeArray<String>;
+}

+ 2 - 0
tests/RunCi.hx

@@ -72,6 +72,8 @@ class RunCi {
 						runci.targets.Js.run(args);
 						runci.targets.Js.run(args);
 					case Java:
 					case Java:
 						runci.targets.Java.run(args);
 						runci.targets.Java.run(args);
+					case Jvm:
+						runci.targets.Jvm.run(args);
 					case Cs:
 					case Cs:
 						runci.targets.Cs.run(args);
 						runci.targets.Cs.run(args);
 					case Flash9:
 					case Flash9:

+ 1 - 0
tests/benchs/.vscode/settings.json

@@ -13,6 +13,7 @@
 		{"label": "PHP", "args": ["build.hxml", "-php", "export/php", "-cmd", "C:\\WINDOWS\\Sysnative\\bash.exe -c 'php export/php/index.php'"]},
 		{"label": "PHP", "args": ["build.hxml", "-php", "export/php", "-cmd", "C:\\WINDOWS\\Sysnative\\bash.exe -c 'php export/php/index.php'"]},
 		{"label": "PHP 7", "args": ["build.hxml", "-php", "export/php", "-D", "php7", "-cmd", "C:\\WINDOWS\\Sysnative\\bash.exe -c 'php7.0 export/php/index.php'"]},
 		{"label": "PHP 7", "args": ["build.hxml", "-php", "export/php", "-D", "php7", "-cmd", "C:\\WINDOWS\\Sysnative\\bash.exe -c 'php7.0 export/php/index.php'"]},
 		{"label": "Java", "args": ["build.hxml", "-java", "export/java", "-cmd", "java -jar export/java/Main.jar"]},
 		{"label": "Java", "args": ["build.hxml", "-java", "export/java", "-cmd", "java -jar export/java/Main.jar"]},
+		{"label": "Jvm", "args": ["build.hxml", "-D", "jvm", "-java", "export/java", "-cmd", "java -jar export/java/Main.jar"]},
 		{"label": "C#", "args": ["build.hxml", "-cs", "export/cs", "-cmd", "cmd /C export\\cs\\bin\\Main.exe"]},
 		{"label": "C#", "args": ["build.hxml", "-cs", "export/cs", "-cmd", "cmd /C export\\cs\\bin\\Main.exe"]},
 		{"label": "Interp", "args": ["build.hxml", "--interp"]},
 		{"label": "Interp", "args": ["build.hxml", "--interp"]},
 	],
 	],

+ 2 - 1
tests/benchs/src/hxbenchmark/ResultPrinter.hx

@@ -8,7 +8,8 @@ class ResultPrinter {
 	}
 	}
 
 
 	public function print(result:SuiteResult) {
 	public function print(result:SuiteResult) {
-		result.cases.sort((cr1, cr2) -> Reflect.compare(cr2.numSamples, cr1.numSamples));
+		// result.cases.sort((cr1, cr2) -> Reflect.compare(cr2.numSamples, cr1.numSamples));
+		result.cases.sort((cr1, cr2) -> Reflect.compare(cr1.name, cr2.name));
 		var maxNameLength = 0;
 		var maxNameLength = 0;
 		var maxSampleLength = 0;
 		var maxSampleLength = 0;
 		for (caseResult in result.cases) {
 		for (caseResult in result.cases) {

+ 1 - 0
tests/runci/TestTarget.hx

@@ -12,6 +12,7 @@ enum abstract TestTarget(String) from String {
 	var Flash9 = "flash9";
 	var Flash9 = "flash9";
 	var As3 = "as3";
 	var As3 = "as3";
 	var Java = "java";
 	var Java = "java";
+	var Jvm = "jvm";
 	var Cs = "cs";
 	var Cs = "cs";
 	var Python = "python";
 	var Python = "python";
 	var Hl = "hl";
 	var Hl = "hl";

+ 24 - 0
tests/runci/targets/Jvm.hx

@@ -0,0 +1,24 @@
+package runci.targets;
+
+import runci.System.*;
+import runci.Config.*;
+
+class Jvm {
+	static public function run(args:Array<String>) {
+		runCommand("haxe", ["compile-jvm.hxml"].concat(args));
+		runCommand("java", ["-jar", "bin/jvm/TestMain-Debug.jar"]);
+
+		runCommand("haxe", ["compile-jvm.hxml","-dce","no"].concat(args));
+		runCommand("java", ["-jar", "bin/jvm/TestMain-Debug.jar"]);
+
+		changeDirectory(sysDir);
+		runCommand("haxe", ["compile-jvm.hxml"]);
+		runCommand("java", ["-jar", "bin/jvm/Main-Debug.jar"]);
+
+		changeDirectory(threadsDir);
+		runCommand("haxe", ["build.hxml", "-java", "export/jvm", "-D", "jvm"]);
+		if (ci != AppVeyor) { // #8154
+			runCommand("java", ["-jar", "export/jvm/Main.jar"]);
+		}
+	}
+}

+ 16 - 0
tests/sys/compile-jvm.hxml

@@ -0,0 +1,16 @@
+compile-each.hxml
+--main Main
+-java bin/jvm
+-D jvm
+
+--next
+compile-each.hxml
+--main TestArguments
+-java bin/jvm
+-D jvm
+
+--next
+compile-each.hxml
+--main ExitCode
+-java bin/jvm
+-D jvm

+ 5 - 0
tests/unit/compile-jvm.hxml

@@ -0,0 +1,5 @@
+compile-each.hxml
+--main unit.TestMain
+--java-lib native_java/native.jar
+-java bin/jvm
+-D jvm

+ 4 - 4
tests/unit/src/unit/TestJava.hx

@@ -115,10 +115,10 @@ class TestJava extends Test
 		f(es1.contains(TA));
 		f(es1.contains(TA));
 		es1.add(TA);
 		es1.add(TA);
 		t(es1.contains(TA));
 		t(es1.contains(TA));
-		var es2 = EnumSet.of(HA,HB);
-		t(es2.contains(HA));
-		t(es2.contains(HB));
-		f(es2.contains(HC));
+		// var es2 = EnumSet.of(HA,HB);
+		// t(es2.contains(HA));
+		// t(es2.contains(HB));
+		// f(es2.contains(HC));
 	}
 	}
 
 
 	function testHaxeKeywords()
 	function testHaxeKeywords()

+ 1 - 1
tests/unit/src/unit/issues/Issue3826.hx

@@ -35,7 +35,7 @@ class Issue3826 extends Test {
 		#end
 		#end
 	}
 	}
 
 
-	#if !(java || cs || flash)
+	#if !((java && !jvm) || cs || flash)
 	public function testReflect() {
 	public function testReflect() {
 		eq( Reflect.callMethod(this, get, []), "2/4.25" );
 		eq( Reflect.callMethod(this, get, []), "2/4.25" );
 		eq( Reflect.callMethod(this, get, [5]), "5/4.25" );
 		eq( Reflect.callMethod(this, get, [5]), "5/4.25" );