Browse Source

Merge pull request #4984 from Simn/idom_calculation

Some internal analyzer improvements
Simon Krajewski 9 years ago
parent
commit
1e2f8d954d

+ 12 - 3
Makefile

@@ -55,8 +55,9 @@ MODULES=syntax/ast typing/type syntax/lexer typing/common generators/genxml synt
 	optimization/optimizer typing/typeload generators/codegen generators/gencommon generators/genas3 \
 	optimization/optimizer typing/typeload generators/codegen generators/gencommon generators/genas3 \
 	generators/gencpp generators/genjs generators/genneko generators/genphp generators/genswf9 \
 	generators/gencpp generators/genjs generators/genneko generators/genphp generators/genswf9 \
 	generators/genswf generators/genjava generators/gencs generators/genpy macro/interp generators/genhl \
 	generators/genswf generators/genjava generators/gencs generators/genpy macro/interp generators/genhl \
-	optimization/dce optimization/analyzer optimization/filters typing/typer \
-	typing/matcher version main
+	optimization/dce optimization/analyzerConfig optimization/analyzerTypes optimization/analyzerTexpr \
+	optimization/analyzerTexprTransformer optimization/analyzer \
+	optimization/filters typing/typer typing/matcher version main
 
 
 ADD_REVISION?=0
 ADD_REVISION?=0
 
 
@@ -128,7 +129,15 @@ uninstall:
 
 
 # Modules
 # Modules
 
 
-src/optimization/analyzer.$(MODULE_EXT): src/syntax/ast.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/generators/codegen.$(MODULE_EXT)
+src/optimization/analyzer.$(MODULE_EXT): src/syntax/ast.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/optimization/analyzerConfig.$(MODULE_EXT) src/optimization/analyzerTypes.$(MODULE_EXT) src/optimization/analyzerTexpr.$(MODULE_EXT) src/optimization/analyzerTexprTransformer.$(MODULE_EXT) src/generators/codegen.$(MODULE_EXT)
+
+src/optimization/analyzerConfig.$(MODULE_EXT): src/syntax/ast.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/typing/typecore.$(MODULE_EXT)
+
+src/optimization/analyzerTexpr.$(MODULE_EXT): src/syntax/ast.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/typing/typecore.$(MODULE_EXT) src/generators/codegen.$(MODULE_EXT) src/optimization/analyzerConfig.$(MODULE_EXT)
+
+src/optimization/analyzerTexprTransformer.$(MODULE_EXT): src/syntax/ast.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/typing/typecore.$(MODULE_EXT) src/generators/codegen.$(MODULE_EXT) src/optimization/analyzerConfig.$(MODULE_EXT) src/optimization/analyzerTypes.$(MODULE_EXT) src/optimization/analyzerTexpr.$(MODULE_EXT)
+
+src/optimization/analyzerTypes.$(MODULE_EXT): src/syntax/ast.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/optimization/analyzerConfig.$(MODULE_EXT)
 
 
 src/generators/codegen.$(MODULE_EXT): src/optimization/optimizer.$(MODULE_EXT) src/typing/typeload.$(MODULE_EXT) src/typing/typecore.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/generators/genxml.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/syntax/ast.$(MODULE_EXT)
 src/generators/codegen.$(MODULE_EXT): src/optimization/optimizer.$(MODULE_EXT) src/typing/typeload.$(MODULE_EXT) src/typing/typecore.$(MODULE_EXT) src/typing/type.$(MODULE_EXT) src/generators/genxml.$(MODULE_EXT) src/typing/common.$(MODULE_EXT) src/syntax/ast.$(MODULE_EXT)
 
 

File diff suppressed because it is too large
+ 9 - 1649
src/optimization/analyzer.ml


+ 119 - 0
src/optimization/analyzerConfig.ml

@@ -0,0 +1,119 @@
+(*
+	The Haxe Compiler
+	Copyright (C) 2005-2016  Haxe Foundation
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *)
+
+open Ast
+open Type
+open Common
+
+type t = {
+	optimize : bool;
+	const_propagation : bool;
+	copy_propagation : bool;
+	code_motion : bool;
+	local_dce : bool;
+	fusion : bool;
+	purity_inference : bool;
+	dot_debug : bool;
+}
+
+let flag_const_propagation = "const_propagation"
+let flag_copy_propagation = "copy_propagation"
+let flag_code_motion = "code_motion"
+let flag_local_dce = "local_dce"
+let flag_fusion = "fusion"
+let flag_purity_inference = "purity_inference"
+let flag_ignore = "ignore"
+let flag_dot_debug = "dot_debug"
+
+let all_flags =
+	List.fold_left (fun acc flag ->
+		flag :: ("no_" ^ flag) :: acc
+	) [] [flag_const_propagation;flag_copy_propagation;flag_code_motion;flag_local_dce;flag_fusion;flag_purity_inference;flag_ignore;flag_dot_debug]
+
+let has_analyzer_option meta s =
+	try
+		let rec loop ml = match ml with
+			| (Meta.Analyzer,el,_) :: ml ->
+				if List.exists (fun (e,p) ->
+					match e with
+						| EConst(Ident s2) when s = s2 -> true
+						| _ -> false
+				) el then
+					true
+				else
+					loop ml
+			| _ :: ml ->
+				loop ml
+			| [] ->
+				false
+		in
+		loop meta
+	with Not_found ->
+		false
+
+let is_ignored meta =
+	has_analyzer_option meta flag_ignore
+
+let get_base_config com =
+	{
+		optimize = not (Common.defined com Define.NoAnalyzer);
+		const_propagation = not (Common.raw_defined com "analyzer-no-const-propagation");
+		copy_propagation = not (Common.raw_defined com "analyzer-no-copy-propagation");
+		code_motion = Common.raw_defined com "analyzer-code-motion";
+		local_dce = not (Common.raw_defined com "analyzer-no-local-dce");
+		fusion = not (Common.raw_defined com "analyzer-no-fusion") && (match com.platform with Flash | Java -> false | _ -> true);
+		purity_inference = not (Common.raw_defined com "analyzer-no-purity-inference");
+		dot_debug = false;
+	}
+
+let update_config_from_meta com config meta =
+	List.fold_left (fun config meta -> match meta with
+		| (Meta.Analyzer,el,_) ->
+			List.fold_left (fun config e -> match fst e with
+				| EConst (Ident s) when s = "no_" ^ flag_const_propagation -> { config with const_propagation = false}
+				| EConst (Ident s) when s = flag_const_propagation -> { config with const_propagation = true}
+				| EConst (Ident s) when s = "no_" ^ flag_copy_propagation -> { config with copy_propagation = false}
+				| EConst (Ident s) when s = flag_copy_propagation -> { config with copy_propagation = true}
+				| EConst (Ident s) when s = "no_" ^ flag_code_motion -> { config with code_motion = false}
+				| EConst (Ident s) when s = flag_code_motion -> { config with code_motion = true}
+				| EConst (Ident s) when s = "no_" ^ flag_local_dce -> { config with local_dce = false}
+				| EConst (Ident s) when s = flag_local_dce -> { config with local_dce = true}
+				| EConst (Ident s) when s = "no_" ^ flag_fusion -> { config with fusion = false}
+				| EConst (Ident s) when s = flag_fusion -> { config with fusion = true}
+				| EConst (Ident s) when s = "no_" ^ flag_purity_inference -> { config with purity_inference = false}
+				| EConst (Ident s) when s = flag_purity_inference -> { config with purity_inference = true}
+				| EConst (Ident s) when s = flag_dot_debug -> {config with dot_debug = true}
+				| _ ->
+					let s = Ast.s_expr e in
+					com.warning (Typecore.string_error s all_flags ("Unrecognized analyzer option: " ^ s)) (pos e);
+					config
+			) config el
+		| (Meta.HasUntyped,_,_) ->
+			{config with optimize = false}
+		| _ ->
+			config
+	) config meta
+
+let get_class_config com c =
+	let config = get_base_config com in
+	update_config_from_meta com config c.cl_meta
+
+let get_field_config com c cf =
+	let config = get_class_config com c in
+	update_config_from_meta com config cf.cf_meta

+ 709 - 0
src/optimization/analyzerTexpr.ml

@@ -0,0 +1,709 @@
+(*
+	The Haxe Compiler
+	Copyright (C) 2005-2016  Haxe Foundation
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *)
+
+open Ast
+open Type
+open Common
+
+let s_expr_pretty e = s_expr_pretty "" (s_type (print_context())) e
+
+let rec is_true_expr e1 = match e1.eexpr with
+	| TConst(TBool true) -> true
+	| TParenthesis e1 -> is_true_expr e1
+	| _ -> false
+
+let map_values ?(allow_control_flow=true) f e =
+	let branching = ref false in
+	let efinal = ref None in
+	let f e =
+		if !branching then
+			f e
+		else begin
+			efinal := Some e;
+			mk (TConst TNull) e.etype e.epos
+		end
+	in
+	let rec loop complex e = match e.eexpr with
+		| TIf(e1,e2,Some e3) ->
+			branching := true;
+			let e2 = loop true e2 in
+			let e3 = loop true e3 in
+			{e with eexpr = TIf(e1,e2,Some e3)}
+		| TSwitch(e1,cases,edef) ->
+			branching := true;
+			let cases = List.map (fun (el,e) -> el,loop true e) cases in
+			let edef = Option.map (loop true) edef in
+			{e with eexpr = TSwitch(e1,cases,edef)}
+		| TBlock [e1] ->
+			loop complex e1
+		| TBlock el ->
+			begin match List.rev el with
+			| e1 :: el ->
+				let e1 = loop true e1 in
+				let e = {e with eexpr = TBlock (List.rev (e1 :: el))} in
+				{e with eexpr = TMeta((Meta.MergeBlock,[],e.epos),e)}
+			| [] ->
+				f e
+			end
+		| TTry(e1,catches) ->
+			branching := true;
+			let e1 = loop true e1 in
+			let catches = List.map (fun (v,e) -> v,loop true e) catches in
+			{e with eexpr = TTry(e1,catches)}
+		| TMeta(m,e1) ->
+			{e with eexpr = TMeta(m,loop complex e1)}
+		| TParenthesis e1 ->
+			{e with eexpr = TParenthesis (loop complex e1)}
+		| TBreak | TContinue | TThrow _ | TReturn _ ->
+			if not allow_control_flow then raise Exit;
+			e
+		| _ ->
+			if not complex then raise Exit;
+			f e
+	in
+	let e = loop false e in
+	e,!efinal
+
+let can_throw e =
+	let rec loop e = match e.eexpr with
+		| TConst _ | TLocal _ | TTypeExpr _ | TFunction _ | TBlock _ -> ()
+		| TCall _ | TNew _ | TThrow _ | TCast(_,Some _) -> raise Exit
+		| TField _ | TArray _ -> raise Exit (* sigh *)
+		| _ -> Type.iter loop e
+	in
+	try
+		loop e; false
+	with Exit ->
+		true
+
+let rec can_be_inlined e = match e.eexpr with
+	| TConst _ -> true
+	| TParenthesis e1 | TMeta(_,e1) -> can_be_inlined e1
+	| _ -> false
+
+let rec can_be_used_as_value com e =
+	let rec loop e = match e.eexpr with
+		| TBlock [e] -> loop e
+		| TBlock _ | TSwitch _ | TTry _ -> raise Exit
+		| TCall({eexpr = TConst (TString "phi")},_) -> raise Exit
+		(* | TCall _ | TNew _ when (match com.platform with Cpp | Php -> true | _ -> false) -> raise Exit *)
+		| TReturn _ | TThrow _ | TBreak | TContinue -> raise Exit
+		| TUnop((Increment | Decrement),_,_) when com.platform = Python -> raise Exit
+		| TNew _ when com.platform = Php -> raise Exit
+		| TFunction _ -> ()
+		| _ -> Type.iter loop e
+	in
+	try
+		loop e;
+		true
+	with Exit ->
+		false
+
+let has_pure_meta meta = Meta.has Meta.Pure meta
+
+let is_pure c cf = has_pure_meta c.cl_meta || has_pure_meta cf.cf_meta
+
+let wrap_meta s e =
+	mk (TMeta((Meta.Custom s,[],e.epos),e)) e.etype e.epos
+
+let is_unbound v =
+	Meta.has Meta.Unbound v.v_meta
+
+let is_really_unbound v =
+	v.v_name <> "`trace" && is_unbound v
+
+let r = Str.regexp "^\\([A-Za-z0-9_]\\)+$"
+let is_unbound_call_that_might_have_side_effects v el = match v.v_name,el with
+	| "__js__",[{eexpr = TConst (TString s)}] when Str.string_match r s 0 -> false
+	| _ -> true
+
+let is_ref_type = function
+	| TType({t_path = ["cs"],("Ref" | "Out")},_) -> true
+	| TAbstract({a_path=["hl";"types"],"Ref"},_) -> true
+	| _ -> false
+
+let type_change_ok com t1 t2 =
+	if t1 == t2 then
+		true
+	else begin
+		let rec map t = match t with
+			| TMono r -> (match !r with None -> t_dynamic | Some t -> map t)
+			| _ -> Type.map map t
+		in
+		let t1 = map t1 in
+		let t2 = map t2 in
+		let rec is_nullable_or_whatever = function
+			| TMono r ->
+				(match !r with None -> false | Some t -> is_nullable_or_whatever t)
+			| TType ({ t_path = ([],"Null") },[_]) ->
+				true
+			| TLazy f ->
+				is_nullable_or_whatever (!f())
+			| TType (t,tl) ->
+				is_nullable_or_whatever (apply_params t.t_params tl t.t_type)
+			| TFun _ ->
+				false
+			| TInst ({ cl_kind = KTypeParameter _ },_) ->
+				false
+			| TAbstract (a,_) when Meta.has Meta.CoreType a.a_meta ->
+				not (Meta.has Meta.NotNull a.a_meta)
+			| TAbstract (a,tl) ->
+				not (Meta.has Meta.NotNull a.a_meta) && is_nullable_or_whatever (apply_params a.a_params tl a.a_this)
+			| _ ->
+				true
+		in
+		(* Check equality again to cover cases where TMono became t_dynamic *)
+		t1 == t2 || match follow t1,follow t2 with
+			| TDynamic _,_ | _,TDynamic _ -> false
+			| _ ->
+				if com.config.pf_static && is_nullable_or_whatever t1 <> is_nullable_or_whatever t2 then false
+				else type_iseq t1 t2
+	end
+
+let dynarray_map f d =
+	DynArray.iteri (fun i e -> DynArray.unsafe_set d i (f e)) d
+
+let dynarray_mapi f d =
+	DynArray.iteri (fun i e -> DynArray.unsafe_set d i (f i e)) d
+
+(*
+	This module rewrites some expressions to reduce the amount of special cases for subsequent analysis. After analysis
+	it restores some of these expressions back to their original form.
+
+	The following expressions are removed from the AST after `apply` has run:
+	- OpBoolAnd and OpBoolOr binary operations are rewritten to TIf
+	- OpAssignOp on a variable is rewritten to OpAssign
+	- Prefix increment/decrement operations are rewritten to OpAssign
+	- Postfix increment/decrement operations are rewritten to a TBlock with OpAssign and OpAdd/OpSub
+	- `do {} while(true)` is rewritten to `while(true) {}`
+	- TWhile expressions are rewritten to `while (true)` with appropriate conditional TBreak
+	- TFor is rewritten to TWhile
+*)
+module TexprFilter = struct
+	let apply com e =
+		let rec loop e = match e.eexpr with
+		| TBinop(OpBoolAnd | OpBoolOr as op,e1,e2) ->
+			let e_then = e2 in
+			let e_if,e_else = if op = OpBoolOr then
+				mk (TUnop(Not,Prefix,e1)) com.basic.tbool e.epos,mk (TConst (TBool(true))) com.basic.tbool e.epos
+			else
+				e1,mk (TConst (TBool(false))) com.basic.tbool e.epos
+			in
+			loop (mk (TIf(e_if,e_then,Some e_else)) e.etype e.epos)
+		| TBinop(OpAssignOp op,({eexpr = TLocal _} as e1),e2) ->
+			let e = {e with eexpr = TBinop(op,e1,e2)} in
+			loop {e with eexpr = TBinop(OpAssign,e1,e)}
+		| TUnop((Increment | Decrement as op),flag,({eexpr = TLocal _} as e1)) ->
+			let e_one = mk (TConst (TInt (Int32.of_int 1))) com.basic.tint e1.epos in
+			let e = {e with eexpr = TBinop(OpAssignOp (if op = Increment then OpAdd else OpSub),e1,e_one)} in
+			let e = if flag = Prefix then
+				e
+			else
+				mk (TBlock [
+					{e with eexpr = TBinop(OpAssignOp (if op = Increment then OpAdd else OpSub),e1,e_one)};
+					{e with eexpr = TBinop((if op = Increment then OpSub else OpAdd),e1,e_one)};
+				]) e.etype e.epos
+			in
+			loop e
+		| TWhile(e1,e2,DoWhile) when is_true_expr e1 ->
+			loop {e with eexpr = TWhile(e1,e2,NormalWhile)}
+		| TWhile(e1,e2,flag) when not (is_true_expr e1) ->
+			let p = e.epos in
+			let e_break = mk TBreak t_dynamic p in
+			let e_not = mk (TUnop(Not,Prefix,Codegen.mk_parent e1)) e1.etype e1.epos in
+			let e_if eo = mk (TIf(e_not,e_break,eo)) com.basic.tvoid p in
+			let rec map_continue e = match e.eexpr with
+				| TContinue ->
+					Texpr.duplicate_tvars (e_if (Some e))
+				| TWhile _ | TFor _ ->
+					e
+				| _ ->
+					Type.map_expr map_continue e
+			in
+			let e2 = if flag = NormalWhile then e2 else map_continue e2 in
+			let e_if = e_if None in
+			let e_block = if flag = NormalWhile then Type.concat e_if e2 else Type.concat e2 e_if in
+			let e_true = mk (TConst (TBool true)) com.basic.tbool p in
+			let e = mk (TWhile(Codegen.mk_parent e_true,e_block,NormalWhile)) e.etype p in
+			loop e
+		| TFor(v,e1,e2) ->
+			let v' = alloc_var "tmp" e1.etype in
+			let ev' = mk (TLocal v') e1.etype e1.epos in
+			let t1 = (Abstract.follow_with_abstracts e1.etype) in
+			let ehasnext = mk (TField(ev',quick_field t1 "hasNext")) (tfun [] com.basic.tbool) e1.epos in
+			let ehasnext = mk (TCall(ehasnext,[])) com.basic.tbool ehasnext.epos in
+			let enext = mk (TField(ev',quick_field t1 "next")) (tfun [] v.v_type) e1.epos in
+			let enext = mk (TCall(enext,[])) v.v_type e1.epos in
+			let eassign = mk (TVar(v,Some enext)) com.basic.tvoid e.epos in
+			let ebody = Type.concat eassign e2 in
+			let e = mk (TBlock [
+				mk (TVar (v',Some e1)) com.basic.tvoid e1.epos;
+				mk (TWhile((mk (TParenthesis ehasnext) ehasnext.etype ehasnext.epos),ebody,NormalWhile)) com.basic.tvoid e1.epos;
+			]) com.basic.tvoid e.epos in
+			loop e
+		| _ ->
+			Type.map_expr loop e
+		in
+		loop e
+end
+
+module Fusion = struct
+
+	type interference_kind =
+		| IKVarMod of tvar list
+		| IKSideEffect
+		| IKNone
+
+	let get_interference_kind e =
+		let vars = ref [] in
+		let rec loop e = match e.eexpr with
+			| TMeta((Meta.Pure,_,_),_) ->
+				()
+			| TUnop((Increment | Decrement),_,{eexpr = TLocal v}) ->
+				vars := v :: !vars
+			| TBinop((OpAssign | OpAssignOp _),{eexpr = TLocal v},e2) ->
+				vars := v :: !vars;
+				loop e2
+			| TBinop((OpAssign | OpAssignOp _),_,_) | TUnop((Increment | Decrement),_,_) ->
+				raise Exit
+			| TCall({eexpr = TLocal v},el) when not (is_unbound_call_that_might_have_side_effects v el) ->
+				List.iter loop el
+			| TCall({eexpr = TField(_,FStatic(c,cf))},el) when is_pure c cf ->
+				List.iter loop el
+			| TNew(c,_,el) when (match c.cl_constructor with Some cf when is_pure c cf -> true | _ -> false) ->
+				List.iter loop el;
+			| TCall _ | TNew _ ->
+				raise Exit
+			| _ ->
+				Type.iter loop e
+		in
+		try
+			loop e;
+			begin match !vars with
+				| [] -> IKNone
+				| vars -> IKVarMod vars
+			end
+		with Exit ->
+			IKSideEffect
+
+	let apply com config e =
+		let rec block_element acc el = match el with
+			| {eexpr = TBinop((OpAssign | OpAssignOp _),_,_) | TUnop((Increment | Decrement),_,_)} as e1 :: el ->
+				block_element (e1 :: acc) el
+			| {eexpr = TLocal _} as e1 :: el when not config.AnalyzerConfig.local_dce ->
+				block_element (e1 :: acc) el
+			(* no-side-effect *)
+			| {eexpr = TEnumParameter _ | TFunction _ | TConst _ | TTypeExpr _ | TLocal _} :: el ->
+				block_element acc el
+			(* no-side-effect composites *)
+			| {eexpr = TParenthesis e1 | TMeta(_,e1) | TCast(e1,None) | TField(e1,_) | TUnop(_,_,e1)} :: el ->
+				block_element acc (e1 :: el)
+			| {eexpr = TArray(e1,e2) | TBinop(_,e1,e2)} :: el ->
+				block_element acc (e1 :: e2 :: el)
+			| {eexpr = TArrayDecl el1 | TCall({eexpr = TField(_,FEnum _)},el1)} :: el2 -> (* TODO: check e1 of FEnum *)
+				block_element acc (el1 @ el2)
+			| {eexpr = TObjectDecl fl} :: el ->
+				block_element acc ((List.map snd fl) @ el)
+			| {eexpr = TIf(e1,{eexpr = TBlock []},(Some {eexpr = TBlock []} | None))} :: el ->
+				block_element acc (e1 :: el)
+			| {eexpr = TBlock [e1]} :: el ->
+				block_element acc (e1 :: el)
+			| {eexpr = TBlock []} :: el ->
+				block_element acc el
+			| e1 :: el ->
+				block_element (e1 :: acc) el
+			| [] ->
+				acc
+		in
+		let changed = ref false in
+		let var_uses = Hashtbl.create 0 in
+		let var_writes = Hashtbl.create 0 in
+		let get_num_uses v =
+			try Hashtbl.find var_uses v.v_id with Not_found -> 0
+		in
+		let get_num_writes v =
+			try Hashtbl.find var_writes v.v_id with Not_found -> 0
+		in
+		let change map v delta =
+			Hashtbl.replace map v.v_id ((try Hashtbl.find map v.v_id with Not_found -> 0) + delta);
+		in
+		let change_num_uses v delta =
+			change var_uses v delta
+		in
+		let change_num_writes v delta =
+			change var_writes v delta
+		in
+		let rec loop e = match e.eexpr with
+			| TLocal v ->
+				change_num_uses v 1;
+			| TBinop(OpAssign,{eexpr = TLocal v},e2) ->
+				change_num_writes v 1;
+				loop e2
+			| _ ->
+				Type.iter loop e
+		in
+		loop e;
+		let can_be_fused v e =
+			let b = get_num_uses v <= 1 && get_num_writes v = 0 && can_be_used_as_value com e && (Meta.has Meta.CompilerGenerated v.v_meta || config.AnalyzerConfig.optimize && config.AnalyzerConfig.fusion && type_change_ok com v.v_type e.etype && v.v_extra = None) in
+			(* let st = s_type (print_context()) in *)
+			(* if e.epos.pfile = "src/Main.hx" then print_endline (Printf.sprintf "%s: %i %i %b %s %s (%b %b %b %b %b) -> %b" v.v_name (get_num_uses v) (get_num_writes v) (can_be_used_as_value com e) (st v.v_type) (st e.etype) (Meta.has Meta.CompilerGenerated v.v_meta) config.Config.optimize config.Config.fusion (type_change_ok com v.v_type e.etype) (v.v_extra = None) b); *)
+			b
+		in
+		let rec fuse acc el = match el with
+			| ({eexpr = TVar(v1,None)} as e1) :: {eexpr = TBinop(OpAssign,{eexpr = TLocal v2},e2)} :: el when v1 == v2 ->
+				changed := true;
+				let e1 = {e1 with eexpr = TVar(v1,Some e2)} in
+				change_num_writes v1 (-1);
+				fuse (e1 :: acc) el
+			| ({eexpr = TVar(v1,None)} as e1) :: ({eexpr = TIf(eif,_,Some _)} as e2) :: el when can_be_used_as_value com e2 && (match com.platform with Php -> false | Cpp when not (Common.defined com Define.Cppia) -> false | _ -> true) ->
+				begin try
+					let i = ref 0 in
+					let check_assign e = match e.eexpr with
+						| TBinop(OpAssign,{eexpr = TLocal v2},e2) when v1 == v2 -> incr i; e2
+						| _ -> raise Exit
+					in
+					let e,_ = map_values ~allow_control_flow:false check_assign e2 in
+					let e = match follow e.etype with
+						| TAbstract({a_path=[],"Void"},_) -> {e with etype = v1.v_type}
+						| _ -> e
+					in
+					let e1 = {e1 with eexpr = TVar(v1,Some e)} in
+					changed := true;
+					change_num_writes v1 (- !i);
+					fuse (e1 :: acc) el
+				with Exit ->
+					fuse (e1 :: acc) (e2 :: el)
+				end
+			| ({eexpr = TVar(v1,Some e1)} as ev) :: e2 :: el when can_be_fused v1 e1 ->
+				let found = ref false in
+				let affected = ref false in
+				let ik1 = get_interference_kind e1 in
+				let check_interference e2 =
+					let check ik e2 = match ik with
+						| IKNone -> ()
+						| IKSideEffect -> (* TODO: Could this miss a IKVarMod case? *)
+							let rec loop e = match e.eexpr with
+								| TMeta((Meta.Pure,_,_),_) ->
+									()
+								| TField _ when Optimizer.is_affected_type e.etype ->
+									raise Exit
+								| TCall({eexpr = TField(_,FStatic(c,cf))},el) when is_pure c cf ->
+									List.iter loop el
+								| TNew(c,_,el) when (match c.cl_constructor with Some cf when is_pure c cf -> true | _ -> false) ->
+									List.iter loop el
+								| TCall _ | TNew _ | TBinop((OpAssign | OpAssignOp _),_,_) | TUnop((Increment | Decrement),_,_) ->
+									raise Exit
+								| _ ->
+									Type.iter loop e
+							in
+							loop e2
+						| IKVarMod vl ->
+							let rec loop e = match e.eexpr with
+								| TLocal v when List.exists (fun v' -> v == v') vl -> raise Exit
+								| _ -> Type.iter loop e
+							in
+							loop e2
+					in
+					try
+						check ik1 e2;
+						check (get_interference_kind e2) e1
+					with Exit -> match com.platform with
+						| Cpp when not (Common.defined com Define.Cppia) -> raise Exit
+						| Php -> raise Exit (* They don't define evaluation order, so let's exit *)
+						| _ -> affected := true;
+				in
+				let rec replace e =
+					let e = match e.eexpr with
+						| TWhile _ | TFunction _ ->
+							e
+						| TIf(e1,e2,eo) ->
+							let e1 = replace e1 in
+							{e with eexpr = TIf(e1,e2,eo)}
+						| TSwitch(e1,cases,edef) ->
+							let e1 = replace e1 in
+							{e with eexpr = TSwitch(e1,cases,edef)}
+						| TLocal v2 when v1 == v2 && not !affected ->
+							found := true;
+							e1
+						| TBinop((OpAssign | OpAssignOp _ as op),({eexpr = TArray(e1,e2)} as ea),e3) ->
+							let e1 = replace e1 in
+							let e2 = replace e2 in
+							let ea = {ea with eexpr = TArray(e1,e2)} in
+							let e3 = replace e3 in
+							{e with eexpr = TBinop(op,ea,e3)}
+						| TBinop((OpAssign | OpAssignOp _ as op),e1,e2) ->
+							let e2 = replace e2 in
+							let e1 = match e1.eexpr with TLocal _ -> e1 | _ -> replace e1 in
+							{e with eexpr = TBinop(op,e1,e2)}
+						| TUnop((Increment | Decrement),_,{eexpr = TLocal _}) ->
+							e
+						| TCall({eexpr = TLocal v},_) when is_really_unbound v ->
+							e
+						| TCall(e1,el) when com.platform = Neko ->
+							(* Neko has this reversed at the moment (issue #4787) *)
+							let el = List.map replace el in
+							let e1 = replace e1 in
+							{e with eexpr = TCall(e1,el)}
+						| _ ->
+							Type.map_expr replace e
+					in
+					check_interference e;
+					e
+				in
+				begin try
+					let e = replace e2 in
+					if not !found then raise Exit;
+					changed := true;
+					change_num_uses v1 (-1);
+					fuse (e :: acc) el
+				with Exit ->
+					fuse (ev :: acc) (e2 :: el)
+				end
+			| {eexpr = TUnop((Increment | Decrement as op,Prefix,({eexpr = TLocal v} as ev)))} as e1 :: e2 :: el ->
+				begin try
+					let e2,f = match e2.eexpr with
+						| TReturn (Some e2) -> e2,(fun e -> {e2 with eexpr = TReturn (Some e)})
+						| TBinop(OpAssign,e21,e22) -> e22,(fun e -> {e2 with eexpr = TBinop(OpAssign,e21,e)})
+						| TVar(v,Some e2) -> e2,(fun e -> {e2 with eexpr = TVar(v,Some e)})
+						| _ -> raise Exit
+					in
+					let ops_match op1 op2 = match op1,op2 with
+						| Increment,OpSub
+						| Decrement,OpAdd ->
+							true
+						| _ ->
+							false
+					in
+					begin match e2.eexpr with
+						| TBinop(op2,{eexpr = TLocal v2},{eexpr = TConst (TInt i32)}) when v == v2 && Int32.to_int i32 = 1 && ops_match op op2 ->
+							changed := true;
+							change_num_uses v2 (-1);
+							let e = (f {e1 with eexpr = TUnop(op,Postfix,ev)}) in
+							fuse (e :: acc) el
+						| TLocal v2 when v == v2 ->
+							changed := true;
+							change_num_uses v2 (-1);
+							let e = (f {e1 with eexpr = TUnop(op,Prefix,ev)}) in
+							fuse (e :: acc) el
+						| _ ->
+							raise Exit
+					end
+				with Exit ->
+					fuse (e1 :: acc) (e2 :: el)
+				end
+			| e1 :: el ->
+				fuse (e1 :: acc) el
+			| [] ->
+				acc
+		in
+		let rec loop e = match e.eexpr with
+			| TBlock el ->
+				let el = List.map loop el in
+				(* fuse flips element order, but block_element doesn't care and flips it back *)
+				let el = fuse [] el in
+				let el = block_element [] el in
+				let rec fuse_loop el =
+					changed := false;
+					let el = fuse [] el in
+					let el = block_element [] el in
+					if !changed then fuse_loop el else el
+				in
+				let el = fuse_loop el in
+				{e with eexpr = TBlock el}
+			| TCall({eexpr = TLocal v},_) when is_really_unbound v ->
+				e
+			| _ ->
+				Type.map_expr loop e
+		in
+		let e = loop e in
+		e
+end
+
+module Cleanup = struct
+	open Typecore
+	let apply com e =
+		let if_or_op e e1 e2 e3 = match (Texpr.skip e1).eexpr,(Texpr.skip e3).eexpr with
+			| TUnop(Not,Prefix,e1),TConst (TBool true) -> Optimizer.optimize_binop {e with eexpr = TBinop(OpBoolOr,e1,e2)} OpBoolOr e1 e2
+			| _,TConst (TBool false) -> Optimizer.optimize_binop {e with eexpr = TBinop(OpBoolAnd,e1,e2)} OpBoolAnd e1 e2
+			| _,TBlock [] -> {e with eexpr = TIf(e1,e2,None)}
+			| _ -> match (Texpr.skip e2).eexpr with
+				| TBlock [] when com.platform <> Cs ->
+					let e1' = mk (TUnop(Not,Prefix,e1)) e1.etype e1.epos in
+					let e1' = Optimizer.optimize_unop e1' Not Prefix e1 in
+					{e with eexpr = TIf(e1',e3,None)}
+				| _ ->
+					{e with eexpr = TIf(e1,e2,Some e3)}
+		in
+		let rec loop e = match e.eexpr with
+			| TIf(e1,e2,Some e3) ->
+				let e1 = loop e1 in
+				let e2 = loop e2 in
+				let e3 = loop e3 in
+				if_or_op e e1 e2 e3;
+			| TBlock el ->
+				let el = List.map (fun e ->
+					let e = loop e in
+					match e.eexpr with
+					| TIf _ -> {e with etype = com.basic.tvoid}
+					| _ -> e
+				) el in
+				{e with eexpr = TBlock el}
+			| TWhile(e1,e2,NormalWhile) ->
+				let e1 = loop e1 in
+				let e2 = loop e2 in
+				begin match e2.eexpr with
+					| TBlock ({eexpr = TIf(e1,({eexpr = TBlock[{eexpr = TBreak}]} as eb),None)} :: el2) ->
+						let e1 = Texpr.skip e1 in
+						let e1 = match e1.eexpr with TUnop(_,_,e1) -> e1 | _ -> {e1 with eexpr = TUnop(Not,Prefix,e1)} in
+						{e with eexpr = TWhile(e1,{eb with eexpr = TBlock el2},NormalWhile)}
+					| TBlock el ->
+						let rec loop2 el = match el with
+							| {eexpr = TBreak | TContinue | TReturn _ | TThrow _} as e :: el ->
+								[e]
+							| e :: el ->
+								e :: (loop2 el)
+							| [] ->
+								[]
+						in
+						let el = loop2 el in
+						{e with eexpr = TWhile(e1,{e2 with eexpr = TBlock el},NormalWhile)}
+					| _ ->
+						{e with eexpr = TWhile(e1,e2,NormalWhile)}
+				end
+			| _ ->
+				Type.map_expr loop e
+		in
+		loop e
+
+	let rec reduce_control_flow ctx e =
+		Type.map_expr (reduce_control_flow ctx) (Optimizer.reduce_control_flow ctx e)
+end
+
+module Purity = struct
+	type purity =
+		| Pure
+		| NotPure
+		| MaybePure
+
+	type purity_node = {
+		pn_field : tclass_field;
+		mutable pn_purity : purity;
+		mutable pn_dependents : purity_node list;
+	}
+
+	let node_lut = Hashtbl.create 0
+
+	let get_field_id c cf = Printf.sprintf "%s.%s" (s_type_path c.cl_path) cf.cf_name
+
+	let get_node c cf =
+		try
+			Hashtbl.find node_lut (get_field_id c cf)
+		with Not_found ->
+			let node = {
+				pn_field = cf;
+				pn_purity = MaybePure;
+				pn_dependents = []
+			} in
+			Hashtbl.replace node_lut (get_field_id c cf) node;
+			node
+
+	let apply_to_field com is_ctor c cf =
+		let node = get_node c cf in
+		let rec taint node =
+			if node.pn_purity <> NotPure then begin
+				node.pn_purity <- NotPure;
+				List.iter taint node.pn_dependents
+			end
+		in
+		let taint_raise node =
+			taint node;
+			raise Exit;
+		in
+		let check_field c cf =
+			let node' = get_node c cf in
+			match node'.pn_purity with
+				| Pure -> ()
+				| NotPure -> taint_raise node;
+				| MaybePure -> node'.pn_dependents <- node :: node'.pn_dependents
+		in
+		let rec check_write e1 =
+			begin match e1.eexpr with
+				| TLocal v ->
+					() (* Writing to locals does not violate purity. *)
+				| TField({eexpr = TConst TThis},_) when is_ctor ->
+					() (* A constructor can write to its own fields without violating purity. *)
+				| _ ->
+					taint_raise node
+			end
+		and loop e = match e.eexpr with
+			| TMeta((Meta.Pure,_,_),_) ->
+				()
+			| TThrow _ ->
+				taint_raise node;
+			| TBinop((OpAssign | OpAssignOp _),e1,e2) ->
+				check_write e1;
+				loop e2;
+			| TUnop((Increment | Decrement),_,e1) ->
+				check_write e1;
+			| TCall({eexpr = TField(_,FStatic(c,cf))},el) ->
+				List.iter loop el;
+				check_field c cf;
+			| TNew(c,_,el) ->
+				List.iter loop el;
+				begin match c.cl_constructor with
+					| Some cf -> check_field c cf
+					| None -> taint_raise node
+				end
+			| TCall({eexpr = TLocal v},el) when not (is_unbound_call_that_might_have_side_effects v el) ->
+				List.iter loop el;
+			| TCall _ ->
+				taint_raise node
+			| _ ->
+				Type.iter loop e
+		in
+		match cf.cf_expr with
+		| None ->
+			taint node
+		| Some e ->
+			try
+				if (Meta.has (Meta.Custom ":impure")) cf.cf_meta then taint_raise node;
+				if is_pure c cf then raise Exit;
+				loop e;
+				node.pn_purity <- Pure;
+			with Exit ->
+				()
+
+	let apply_to_class com c =
+		List.iter (apply_to_field com false c) c.cl_ordered_fields;
+		List.iter (apply_to_field com false c) c.cl_ordered_statics;
+		(match c.cl_constructor with Some cf -> apply_to_field com true c cf | None -> ())
+
+	let infer com =
+		Hashtbl.clear node_lut;
+		List.iter (fun mt -> match mt with
+			| TClassDecl c -> apply_to_class com c
+			| _ -> ()
+		) com.types;
+		Hashtbl.fold (fun _ node acc ->
+			if node.pn_purity = Pure then begin
+				node.pn_field.cf_meta <- (Meta.Pure,[],node.pn_field.cf_pos) :: node.pn_field.cf_meta;
+				node.pn_field :: acc
+			end else acc
+		) node_lut [];
+end

+ 698 - 0
src/optimization/analyzerTexprTransformer.ml

@@ -0,0 +1,698 @@
+(*
+	The Haxe Compiler
+	Copyright (C) 2005-2016  Haxe Foundation
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *)
+
+open Ast
+open Type
+open Common
+open AnalyzerConfig
+open AnalyzerTypes
+open AnalyzerTypes.BasicBlock
+open AnalyzerTypes.Graph
+open AnalyzerTexpr
+
+(*
+	Transforms an expression to a graph, and a graph back to an expression. This module relies on TexprFilter being
+	run first.
+
+	The created graph is intact and can immediately be transformed back to an expression, or used for analysis first.
+*)
+
+let rec func ctx bb tf t p =
+	let g = ctx.graph in
+	let create_node kind t p =
+		let bb = Graph.create_node g kind t p in
+		bb.bb_loop_groups <- ctx.loop_stack;
+		bb
+	in
+	let bb_root = create_node (BKFunctionBegin tf) tf.tf_expr.etype tf.tf_expr.epos in
+	let bb_exit = create_node BKFunctionEnd tf.tf_expr.etype tf.tf_expr.epos in
+	add_function g tf t p bb_root;
+	add_cfg_edge bb bb_root CFGFunction;
+	let make_block_meta b =
+		let e = mk (TConst (TInt (Int32.of_int b.bb_id))) ctx.com.basic.tint b.bb_pos in
+		wrap_meta ":block" e
+	in
+	let bb_breaks = ref [] in
+	let bb_continue = ref None in
+	let b_try_stack = ref [] in
+	let begin_loop bb_loop_pre bb_continue' =
+		let old = !bb_breaks,!bb_continue in
+		bb_breaks := [];
+		bb_continue := Some bb_continue';
+		let id = ctx.loop_counter in
+		g.g_loops <- IntMap.add id bb_loop_pre g.g_loops;
+		ctx.loop_stack <- id :: ctx.loop_stack;
+		bb_continue'.bb_loop_groups <- id :: bb_continue'.bb_loop_groups;
+		ctx.loop_counter <- id + 1;
+		(fun () ->
+			let breaks = !bb_breaks in
+			bb_breaks := fst old;
+			bb_continue := snd old;
+			ctx.loop_stack <- List.tl ctx.loop_stack;
+			breaks;
+		)
+	in
+	let begin_try b =
+		b_try_stack := b :: !b_try_stack;
+		(fun () ->
+			b_try_stack := List.tl !b_try_stack
+		)
+	in
+	let add_terminator bb e =
+		add_texpr bb e;
+		close_node g bb;
+		g.g_unreachable
+	in
+	let check_unbound_call v el =
+		if is_unbound_call_that_might_have_side_effects v el then ctx.has_unbound <- true
+	in
+	let rec value bb e = match e.eexpr with
+		| TLocal v ->
+			bb,e
+		| TBinop(OpAssign,({eexpr = TLocal v} as e1),e2) ->
+			block_element bb e,e1
+		| TBlock [e1] ->
+			value bb e1
+		| TBlock _ | TIf _ | TSwitch _ | TTry _ ->
+			bind_to_temp bb false e
+		| TCall({eexpr = TLocal v},el) when is_really_unbound v ->
+			check_unbound_call v el;
+			bb,e
+		| TCall(e1,el) ->
+			call bb e e1 el
+		| TBinop((OpAssign | OpAssignOp _) as op,e1,e2) ->
+			let bb,e2 = value bb e2 in
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TBinop(op,e1,e2)}
+		| TBinop(op,e1,e2) ->
+			let bb,e1,e2 = match ordered_value_list bb [e1;e2] with
+				| bb,[e1;e2] -> bb,e1,e2
+				| _ -> assert false
+			in
+			bb,{e with eexpr = TBinop(op,e1,e2)}
+		| TUnop(op,flag,e1) ->
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TUnop(op,flag,e1)}
+		| TArrayDecl el ->
+			let bb,el = ordered_value_list bb el in
+			bb,{e with eexpr = TArrayDecl el}
+		| TObjectDecl fl ->
+			let el = List.map snd fl in
+			let bb,el = ordered_value_list bb el in
+			bb,{e with eexpr = TObjectDecl (List.map2 (fun (s,_) e -> s,e) fl el)}
+		| TField({eexpr = TTypeExpr _},fa) ->
+			bb,e
+		| TField(e1,fa) ->
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TField(e1,fa)}
+		| TArray(e1,e2) ->
+			let bb,e1,e2 = match ordered_value_list bb [e1;e2] with
+				| bb,[e1;e2] -> bb,e1,e2
+				| _ -> assert false
+			in
+			bb,{e with eexpr = TArray(e1,e2)}
+		| TMeta(m,e1) ->
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TMeta(m,e1)}
+		| TParenthesis e1 ->
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TParenthesis e1}
+		| TCast(e1,mto) ->
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TCast(e1,mto)}
+		| TNew(c,tl,el) ->
+			let bb,el = ordered_value_list bb el in
+			bb,{e with eexpr = TNew(c,tl,el)}
+		| TEnumParameter(e1,ef,ei) ->
+			let bb,e1 = value bb e1 in
+			bb,{e with eexpr = TEnumParameter(e1,ef,ei)}
+		| TFunction tf ->
+			let bb_func,bb_func_end = func ctx bb tf e.etype e.epos in
+			let e_fun = mk (TConst (TString "fun")) t_dynamic p in
+			let econst = mk (TConst (TInt (Int32.of_int bb_func.bb_id))) ctx.com.basic.tint e.epos in
+			let ec = mk (TCall(e_fun,[econst])) t_dynamic p in
+			let bb_next = create_node BKNormal bb.bb_type bb.bb_pos in
+			add_cfg_edge bb bb_next CFGGoto;
+			set_syntax_edge bb (SEMerge bb_next);
+			close_node g bb;
+			add_cfg_edge bb_func_end bb_next CFGGoto;
+			bb_next,ec
+		| TTypeExpr(TClassDecl {cl_kind = KAbstractImpl a}) when not (Meta.has Meta.RuntimeValue a.a_meta) ->
+			error "Cannot use abstract as value" e.epos
+		| TTypeExpr(TClassDecl c) ->
+			List.iter (fun cf -> if not (Meta.has Meta.MaybeUsed cf.cf_meta) then cf.cf_meta <- (Meta.MaybeUsed,[],cf.cf_pos) :: cf.cf_meta;) c.cl_ordered_statics;
+			bb,e
+		| TConst _ | TTypeExpr _ ->
+			bb,e
+		| TThrow _ | TReturn _ | TBreak | TContinue ->
+			let bb = block_element bb e in
+			bb,mk (TConst TNull) t_dynamic e.epos
+		| TVar _ | TFor _ | TWhile _ ->
+			error "Cannot use this expression as value" e.epos
+	and ordered_value_list bb el =
+		let might_be_affected,collect_modified_locals = Optimizer.create_affection_checker() in
+		let rec can_be_optimized e = match e.eexpr with
+			| TBinop _ | TArray _ | TCall _ -> true
+			| TParenthesis e1 -> can_be_optimized e1
+			| _ -> false
+		in
+		let _,el = List.fold_left (fun (had_side_effect,acc) e ->
+			if had_side_effect then
+				(true,(might_be_affected e || Optimizer.has_side_effect e,can_be_optimized e,e) :: acc)
+			else begin
+				let had_side_effect = Optimizer.has_side_effect e in
+				if had_side_effect then collect_modified_locals e;
+				let opt = can_be_optimized e in
+				(had_side_effect || opt,(false,opt,e) :: acc)
+			end
+		) (false,[]) (List.rev el) in
+		let bb,values = List.fold_left (fun (bb,acc) (aff,opt,e) ->
+			let bb,value = if aff || opt then bind_to_temp bb aff e else value bb e in
+			bb,(value :: acc)
+		) (bb,[]) el in
+		bb,List.rev values
+	and bind_to_temp bb sequential e =
+		let rec loop fl e = match e.eexpr with
+			| TField(e1,fa) when (match extract_field fa with Some {cf_kind = Method MethNormal} -> true | _ -> false) ->
+				loop ((fun e' -> {e with eexpr = TField(e',fa)}) :: fl) e1
+			| _ ->
+				fl,e
+		in
+		let fl,e = loop [] e in
+		let v = alloc_var ctx.temp_var_name e.etype in
+		begin match ctx.com.platform with
+			| Cpp when sequential && not (Common.defined ctx.com Define.Cppia) -> ()
+			| _ -> v.v_meta <- [Meta.CompilerGenerated,[],e.epos];
+		end;
+		let bb = declare_var_and_assign bb v e in
+		let e = {e with eexpr = TLocal v} in
+		let e = List.fold_left (fun e f -> f e) e (List.rev fl) in
+		bb,e
+	and declare_var_and_assign bb v e =
+		let rec loop bb e = match e.eexpr with
+			| TParenthesis e1 ->
+				loop bb e1
+			| TBlock el ->
+				let rec loop2 bb el = match el with
+					| [e] ->
+						bb,e
+					| e1 :: el ->
+						let bb = block_element bb e1 in
+						loop2 bb el
+					| [] ->
+						assert false
+				in
+				let bb,e = loop2 bb el in
+				loop bb e
+			| _ ->
+				bb,e
+		in
+		let bb,e = loop bb e in
+		begin match follow v.v_type with
+			| TAbstract({a_path=[],"Void"},_) -> error "Cannot use Void as value" e.epos
+			| _ -> ()
+		end;
+		let ev = mk (TLocal v) v.v_type e.epos in
+		let was_assigned = ref false in
+		let assign e =
+			if not !was_assigned then begin
+				was_assigned := true;
+				add_texpr bb (mk (TVar(v,None)) ctx.com.basic.tvoid ev.epos);
+			end;
+			mk (TBinop(OpAssign,ev,e)) ev.etype e.epos
+		in
+		begin try
+			block_element_plus bb (map_values assign e) (fun e -> mk (TVar(v,Some e)) ctx.com.basic.tvoid e.epos)
+		with Exit ->
+			let bb,e = value bb e in
+			add_texpr bb (mk (TVar(v,Some e)) ctx.com.basic.tvoid ev.epos);
+			bb
+		end
+	and block_element_plus bb (e,efinal) f =
+		let bb = block_element bb e in
+		let bb = match efinal with
+			| None -> bb
+			| Some e -> block_element bb (f e)
+		in
+		bb
+	and block_element_value bb e f =
+		let e,efinal = map_values f e in
+		block_element_plus bb (e,efinal) f
+	and call bb e e1 el =
+		begin match e1.eexpr with
+			| TConst TSuper when ctx.com.platform = Java || ctx.com.platform = Cs ->
+				bb,e
+			| _ ->
+				let check e t = match e.eexpr with
+					| TLocal v when is_ref_type t ->
+						v.v_capture <- true;
+						e
+					| _ ->
+						e
+				in
+				let el = Codegen.UnificationCallback.check_call check el e1.etype in
+				let bb,el = ordered_value_list bb (e1 :: el) in
+				match el with
+					| e1 :: el -> bb,{e with eexpr = TCall(e1,el)}
+					| _ -> assert false
+		end
+	and block_element bb e = match e.eexpr with
+		(* variables *)
+		| TVar(v,None) ->
+			add_texpr bb e;
+			bb
+		| TVar(v,Some e1) ->
+			declare_var_and_assign bb v e1
+		| TBinop(OpAssign,({eexpr = TLocal v} as e1),e2) ->
+			let assign e =
+				mk (TBinop(OpAssign,e1,e)) e.etype e.epos
+			in
+			begin try
+				block_element_value bb e2 assign
+			with Exit ->
+				let bb,e2 = value bb e2 in
+				add_texpr bb {e with eexpr = TBinop(OpAssign,e1,e2)};
+				bb
+			end
+		(* branching *)
+		| TMeta((Meta.MergeBlock,_,_),{eexpr = TBlock el}) ->
+			block_el bb el
+		| TBlock el ->
+			let bb_sub = create_node BKSub e.etype e.epos in
+			add_cfg_edge bb bb_sub CFGGoto;
+			close_node g bb;
+			let bb_sub_next = block_el bb_sub el in
+			if bb_sub_next != g.g_unreachable then begin
+				let bb_next = create_node BKNormal bb.bb_type bb.bb_pos in
+				set_syntax_edge bb (SESubBlock(bb_sub,bb_next));
+				add_cfg_edge bb_sub_next bb_next CFGGoto;
+				close_node g bb_sub_next;
+				bb_next;
+			end else begin
+				set_syntax_edge bb (SEMerge bb_sub);
+				close_node g bb_sub_next;
+				bb_sub_next
+			end
+		| TIf(e1,e2,None) ->
+			let bb,e1 = bind_to_temp bb false e1 in
+			let bb_then = create_node BKConditional e2.etype e2.epos in
+			add_texpr bb (wrap_meta ":cond-branch" e1);
+			add_cfg_edge bb bb_then (CFGCondBranch (mk (TConst (TBool true)) ctx.com.basic.tbool e2.epos));
+			let bb_then_next = block bb_then e2 in
+			let bb_next = create_node BKNormal bb.bb_type bb.bb_pos in
+			set_syntax_edge bb (SEIfThen(bb_then,bb_next));
+			add_cfg_edge bb bb_next CFGCondElse;
+			close_node g bb;
+			add_cfg_edge bb_then_next bb_next CFGGoto;
+			close_node g bb_then_next;
+			bb_next
+		| TIf(e1,e2,Some e3) ->
+			let bb,e1 = bind_to_temp bb false e1 in
+			let bb_then = create_node BKConditional e2.etype e2.epos in
+			let bb_else = create_node BKConditional e3.etype e3.epos in
+			add_texpr bb (wrap_meta ":cond-branch" e1);
+			add_cfg_edge bb bb_then (CFGCondBranch (mk (TConst (TBool true)) ctx.com.basic.tbool e2.epos));
+			add_cfg_edge bb bb_else CFGCondElse;
+			close_node g bb;
+			let bb_then_next = block bb_then e2 in
+			let bb_else_next = block bb_else e3 in
+			if bb_then_next == g.g_unreachable && bb_else_next == g.g_unreachable then begin
+				set_syntax_edge bb (SEIfThenElse(bb_then,bb_else,g.g_unreachable,e.etype));
+				g.g_unreachable
+			end else begin
+				let bb_next = create_node BKNormal bb.bb_type bb.bb_pos in
+				set_syntax_edge bb (SEIfThenElse(bb_then,bb_else,bb_next,e.etype));
+				add_cfg_edge bb_then_next bb_next CFGGoto;
+				add_cfg_edge bb_else_next bb_next CFGGoto;
+				close_node g bb_then_next;
+				close_node g bb_else_next;
+				bb_next
+			end
+		| TSwitch(e1,cases,edef) ->
+			let is_exhaustive = edef <> None || Optimizer.is_exhaustive e1 in
+			let bb,e1 = bind_to_temp bb false e1 in
+			add_texpr bb (wrap_meta ":cond-branch" e1);
+			let reachable = ref [] in
+			let make_case e =
+				let bb_case = create_node BKConditional e.etype e.epos in
+				let bb_case_next = block bb_case e in
+				if bb_case_next != g.g_unreachable then
+					reachable := bb_case_next :: !reachable;
+				close_node g bb_case_next;
+				bb_case
+			in
+			let cases = List.map (fun (el,e) ->
+				let bb_case = make_case e in
+				List.iter (fun e -> add_cfg_edge bb bb_case (CFGCondBranch e)) el;
+				el,bb_case
+			) cases in
+			let def = match edef with
+				| None ->
+					None
+				| Some e ->
+					let bb_case = make_case e in
+					add_cfg_edge bb bb_case (CFGCondElse);
+					Some (bb_case)
+			in
+			if is_exhaustive && !reachable = [] then begin
+				set_syntax_edge bb (SESwitch(cases,def,g.g_unreachable));
+				close_node g bb;
+				g.g_unreachable;
+			end else begin
+				let bb_next = create_node BKNormal bb.bb_type bb.bb_pos in
+				if not is_exhaustive then add_cfg_edge bb bb_next CFGGoto;
+				List.iter (fun bb -> add_cfg_edge bb bb_next CFGGoto) !reachable;
+				set_syntax_edge bb (SESwitch(cases,def,bb_next));
+				close_node g bb;
+				bb_next
+			end
+		| TWhile(e1,e2,NormalWhile) ->
+			let bb_loop_pre = create_node BKNormal e1.etype e1.epos in
+			add_cfg_edge bb bb_loop_pre CFGGoto;
+			set_syntax_edge bb (SEMerge bb_loop_pre);
+			close_node g bb;
+			let bb_loop_head = create_node BKLoopHead e1.etype e1.epos in
+			add_cfg_edge bb_loop_pre bb_loop_head CFGGoto;
+			let close = begin_loop bb bb_loop_head in
+			let bb_loop_body = create_node BKNormal e2.etype e2.epos in
+			let bb_loop_body_next = block bb_loop_body e2 in
+			let bb_breaks = close() in
+			let bb_next = if bb_breaks = [] then begin
+				(* The loop appears to be infinite, let's assume that something within it throws.
+				   Otherwise DCE's mark-pass won't see its body and removes everything. *)
+				add_cfg_edge bb_loop_body_next bb_exit CFGMaybeThrow;
+				g.g_unreachable
+			end else
+				create_node BKNormal bb.bb_type bb.bb_pos
+			in
+			List.iter (fun bb -> add_cfg_edge bb bb_next CFGGoto) bb_breaks;
+			set_syntax_edge bb_loop_pre (SEWhile(bb_loop_head,bb_loop_body,bb_next));
+			close_node g bb_loop_pre;
+			add_texpr bb_loop_pre {e with eexpr = TWhile(e1,make_block_meta bb_loop_body,NormalWhile)};
+			add_cfg_edge bb_loop_body_next bb_loop_head CFGGoto;
+			add_cfg_edge bb_loop_head bb_loop_body CFGGoto;
+			close_node g bb_loop_body_next;
+			close_node g bb_loop_head;
+			bb_next;
+		| TTry(e1,catches) ->
+			let bb_try = create_node BKNormal e1.etype e1.epos in
+			let bb_exc = create_node BKException t_dynamic e.epos in
+			add_cfg_edge bb bb_try CFGGoto;
+			let close = begin_try bb_exc in
+			let bb_try_next = block bb_try e1 in
+			close();
+			let bb_next = if bb_exc.bb_incoming = [] then
+				let bb_next = if bb_try_next == g.g_unreachable then
+					g.g_unreachable
+				else begin
+					let bb_next = create_node BKNormal bb.bb_type bb.bb_pos in
+					add_cfg_edge bb_try_next bb_next CFGGoto;
+					close_node g bb_try_next;
+					bb_next
+				end in
+				set_syntax_edge bb (SESubBlock(bb_try,bb_next));
+				bb_next
+			else begin
+				let is_reachable = ref (not (bb_try_next == g.g_unreachable)) in
+				let catches = List.map (fun (v,e) ->
+					let bb_catch = create_node (BKCatch v) e.etype e.epos in
+					add_cfg_edge bb_exc bb_catch CFGGoto;
+					let bb_catch_next = block bb_catch e in
+					is_reachable := !is_reachable || (not (bb_catch_next == g.g_unreachable));
+					v,bb_catch,bb_catch_next
+				) catches in
+				let bb_next = if !is_reachable then create_node BKNormal bb.bb_type bb.bb_pos else g.g_unreachable in
+				let catches = List.map (fun (v,bb_catch,bb_catch_next) ->
+					if bb_catch_next != g.g_unreachable then add_cfg_edge bb_catch_next bb_next CFGGoto;
+					close_node g bb_catch_next;
+					v,bb_catch
+				) catches in
+				set_syntax_edge bb (SETry(bb_try,bb_exc,catches,bb_next));
+				if bb_try_next != g.g_unreachable then add_cfg_edge bb_try_next bb_next CFGGoto;
+				close_node g bb_try_next;
+				bb_next
+			end in
+            close_node g bb_exc;
+            close_node g bb;
+			bb_next
+		(* control flow *)
+		| TReturn None ->
+			add_cfg_edge bb bb_exit CFGGoto;
+			add_terminator bb e
+		| TReturn (Some e1) ->
+			begin try
+				let mk_return e1 = mk (TReturn (Some e1)) t_dynamic e.epos in
+				block_element_value bb e1 mk_return
+			with Exit ->
+				let bb,e1 = value bb e1 in
+				add_cfg_edge bb bb_exit CFGGoto;
+				add_terminator bb {e with eexpr = TReturn(Some e1)};
+			end
+		| TBreak ->
+			bb_breaks := bb :: !bb_breaks;
+			add_terminator bb e
+		| TContinue ->
+			begin match !bb_continue with
+				| Some bb_continue -> add_cfg_edge bb bb_continue CFGGoto
+				| _ -> assert false
+			end;
+			add_terminator bb e
+		| TThrow e1 ->
+			begin try
+				let mk_throw e1 = mk (TThrow e1) t_dynamic e.epos in
+				block_element_value bb e1 mk_throw
+			with Exit ->
+				let bb,e1 = value bb e1 in
+				begin match !b_try_stack with
+					| [] -> add_cfg_edge bb bb_exit CFGGoto
+					| _ -> List.iter (fun bb_exc -> add_cfg_edge bb bb_exc CFGGoto) !b_try_stack;
+				end;
+				add_terminator bb {e with eexpr = TThrow e1};
+			end
+		(* side_effects *)
+		| TCall({eexpr = TLocal v},el) when is_really_unbound v ->
+			check_unbound_call v el;
+			add_texpr bb e;
+			bb
+		| TCall(e1,el) ->
+			let bb,e = call bb e e1 el in
+			add_texpr bb e;
+			bb
+		| TNew(c,tl,el) ->
+			let bb,el = ordered_value_list bb el in
+			add_texpr bb {e with eexpr = TNew(c,tl,el)};
+			bb
+		| TCast(e1,Some mt) ->
+			let b,e1 = value bb e1 in
+			add_texpr bb {e with eexpr = TCast(e1,Some mt)};
+			bb
+		| TBinop((OpAssign | OpAssignOp _) as op,({eexpr = TArray(e1,e2)} as ea),e3) ->
+			let bb,e1,e2,e3 = match ordered_value_list bb [e1;e2;e3] with
+				| bb,[e1;e2;e3] -> bb,e1,e2,e3
+				| _ -> assert false
+			in
+			add_texpr bb {e with eexpr = TBinop(op,{ea with eexpr = TArray(e1,e2)},e3)};
+			bb
+		| TBinop((OpAssign | OpAssignOp _ as op),e1,e2) ->
+			let bb,e1 = value bb e1 in
+			let bb,e2 = value bb e2 in
+			add_texpr bb {e with eexpr = TBinop(op,e1,e2)};
+			bb
+		| TUnop((Increment | Decrement as op),flag,e1) ->
+			let bb,e1 = value bb e1 in
+			add_texpr bb {e with eexpr = TUnop(op,flag,e1)};
+			bb
+		| TLocal _ when not ctx.config.AnalyzerConfig.local_dce ->
+			add_texpr bb e;
+			bb
+		(* no-side-effect *)
+		| TEnumParameter _ | TFunction _ | TConst _ | TTypeExpr _ | TLocal _ ->
+			bb
+		(* no-side-effect composites *)
+		| TParenthesis e1 | TMeta(_,e1) | TCast(e1,None) | TField(e1,_) | TUnop(_,_,e1) ->
+			block_element bb e1
+		| TArray(e1,e2) | TBinop(_,e1,e2) ->
+			let bb = block_element bb e1 in
+			block_element bb e2
+		| TArrayDecl el ->
+			block_el bb el
+		| TObjectDecl fl ->
+			block_el bb (List.map snd fl)
+		| TFor _ | TWhile(_,_,DoWhile) ->
+			assert false
+	and block_el bb el =
+		match !b_try_stack with
+		| [] ->
+			let rec loop bb el = match el with
+				| [] -> bb
+				| e :: el ->
+					let bb = block_element bb e in
+					if bb == g.g_unreachable then bb else loop bb el
+			in
+			loop bb el
+		| bbl ->
+			let rec loop bb el = match el with
+				| [] -> bb
+				| e :: el ->
+					let bb = if not (can_throw e) then
+						block_element bb e
+					else begin
+						let bb' = create_node BKNormal e.etype e.epos in
+						add_cfg_edge bb bb' CFGGoto;
+						List.iter (fun bb_exc -> add_cfg_edge bb bb_exc CFGMaybeThrow) bbl;
+						set_syntax_edge bb (SEMerge bb');
+						close_node g bb;
+						block_element bb' e
+					end in
+					if bb == g.g_unreachable then bb else loop bb el
+			in
+			loop bb el
+	and block bb e =
+		let el = match e.eexpr with
+			| TBlock el -> el
+			| _ -> [e]
+		in
+		block_el bb el
+	in
+	let bb_last = block bb_root tf.tf_expr in
+	close_node g bb_last;
+	add_cfg_edge bb_last bb_exit CFGGoto; (* implied return *)
+	close_node g bb_exit;
+	bb_root,bb_exit
+
+let from_texpr com config e =
+	let g = Graph.create e.etype e.epos in
+	let tf,is_real_function = match e.eexpr with
+		| TFunction tf ->
+			tf,true
+		| _ ->
+			(* Wrap expression in a function so we don't have to treat it as a special case throughout. *)
+			let e = mk (TReturn (Some e)) t_dynamic e.epos in
+			let tf = { tf_args = []; tf_type = e.etype; tf_expr = e; } in
+			tf,false
+	in
+	let ctx = {
+		com = com;
+		config = config;
+		graph = g;
+		(* For CPP we want to use variable names which are "probably" not used by users in order to
+		   avoid problems with the debugger, see https://github.com/HaxeFoundation/hxcpp/issues/365 *)
+		temp_var_name = (match com.platform with Cpp -> "_hx_tmp" | _ -> "tmp");
+		is_real_function = is_real_function;
+		entry = g.g_unreachable;
+		has_unbound = false;
+		loop_counter = 0;
+		loop_stack = [];
+	} in
+	let bb_func,bb_exit = func ctx g.g_root tf e.etype e.epos in
+	ctx.entry <- bb_func;
+	close_node g g.g_root;
+	g.g_exit <- bb_exit;
+	set_syntax_edge bb_exit SEEnd;
+	ctx
+
+let rec block_to_texpr_el ctx bb =
+	if bb.bb_dominator == ctx.graph.g_unreachable then
+		[]
+	else begin
+		let block bb = block_to_texpr ctx bb in
+		let rec loop bb se =
+			let el = List.rev (DynArray.to_list bb.bb_el) in
+			match el,se with
+			| el,SESubBlock(bb_sub,bb_next) ->
+				Some bb_next,(block bb_sub) :: el
+			| el,SEMerge bb_next ->
+				Some bb_next,el
+			| el,(SEEnd | SENone) ->
+				None,el
+			| {eexpr = TWhile(e1,_,flag)} as e :: el,(SEWhile(_,bb_body,bb_next)) ->
+				let e2 = block bb_body in
+				Some bb_next,{e with eexpr = TWhile(e1,e2,flag)} :: el
+			| el,SETry(bb_try,_,bbl,bb_next) ->
+				Some bb_next,(mk (TTry(block bb_try,List.map (fun (v,bb) -> v,block bb) bbl)) ctx.com.basic.tvoid bb_try.bb_pos) :: el
+			| e1 :: el,se ->
+				let e1 = Texpr.skip e1 in
+				let bb_next,e1_def,t = match se with
+					| SEIfThen(bb_then,bb_next) -> Some bb_next,TIf(e1,block bb_then,None),ctx.com.basic.tvoid
+					| SEIfThenElse(bb_then,bb_else,bb_next,t) -> Some bb_next,TIf(e1,block bb_then,Some (block bb_else)),t
+					| SESwitch(bbl,bo,bb_next) -> Some bb_next,TSwitch(e1,List.map (fun (el,bb) -> el,block bb) bbl,Option.map block bo),ctx.com.basic.tvoid
+					| _ -> error (Printf.sprintf "Invalid node exit: %s" (s_expr_pretty e1)) bb.bb_pos
+				in
+				bb_next,(mk e1_def t e1.epos) :: el
+			| [],_ ->
+				None,[]
+		in
+		let bb_next,el = loop bb bb.bb_syntax_edge in
+		let el = match bb_next with
+			| None -> el
+			| Some bb -> (block_to_texpr_el ctx bb) @ el
+		in
+		el
+	end
+
+and block_to_texpr ctx bb =
+	assert(bb.bb_closed);
+	let el = block_to_texpr_el ctx bb in
+	let e = mk (TBlock (List.rev el)) bb.bb_type bb.bb_pos in
+	e
+
+and func ctx i =
+	let bb,t,p,tf = Hashtbl.find ctx.graph.g_functions i in
+	let e = block_to_texpr ctx bb in
+	let rec loop e = match e.eexpr with
+		| TLocal v when not (is_unbound v) ->
+			{e with eexpr = TLocal (get_var_origin ctx.graph v)}
+		| TVar(v,eo) when not (is_unbound v) ->
+			let eo = Option.map loop eo in
+			let v' = get_var_origin ctx.graph v in
+			{e with eexpr = TVar(v',eo)}
+		| TBinop(OpAssign,e1,({eexpr = TBinop(op,e2,e3)} as e4)) ->
+			let e1 = loop e1 in
+			let e2 = loop e2 in
+			let e3 = loop e3 in
+			let is_valid_assign_op = function
+				| OpAdd | OpMult | OpDiv | OpSub | OpAnd
+				| OpOr | OpXor | OpShl | OpShr | OpUShr | OpMod ->
+					true
+				| OpAssignOp _ | OpInterval | OpArrow | OpAssign | OpEq
+				| OpNotEq | OpGt | OpGte | OpLt | OpLte | OpBoolAnd | OpBoolOr ->
+					false
+			in
+			begin match e1.eexpr,e2.eexpr with
+				| TLocal v1,TLocal v2 when v1 == v2 && is_valid_assign_op op ->
+					begin match op,e3.eexpr with
+						| OpAdd,TConst (TInt i32) when Int32.to_int i32 = 1 -> {e with eexpr = TUnop(Increment,Prefix,e1)}
+						| OpSub,TConst (TInt i32) when Int32.to_int i32 = 1 -> {e with eexpr = TUnop(Decrement,Prefix,e1)}
+						| _ -> {e with eexpr = TBinop(OpAssignOp op,e1,e3)}
+					end
+				| _ ->
+					{e with eexpr = TBinop(OpAssign,e1,{e4 with eexpr = TBinop(op,e2,e3)})}
+			end
+		| TCall({eexpr = TConst (TString "fun")},[{eexpr = TConst (TInt i32)}]) ->
+			func ctx (Int32.to_int i32)
+		| TCall({eexpr = TLocal v},_) when is_really_unbound v ->
+			e
+		| _ ->
+			Type.map_expr loop e
+	in
+	let e = loop e in
+	mk (TFunction {tf with tf_expr = e}) t p
+
+let to_texpr ctx =
+	func ctx ctx.entry.bb_id

+ 522 - 0
src/optimization/analyzerTypes.ml

@@ -0,0 +1,522 @@
+(*
+	The Haxe Compiler
+	Copyright (C) 2005-2016  Haxe Foundation
+
+	This program is free software; you can redistribute it and/or
+	modify it under the terms of the GNU General Public License
+	as published by the Free Software Foundation; either version 2
+	of the License, or (at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the Free Software
+	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *)
+
+open Ast
+open Type
+open Common
+
+(*
+	A BasicBlock represents a node in the control flow. It has expression elements similar to TBlock in the AST,
+	but also holds additional information related to control flow and variables.
+
+	Basic blocks are created whenever it is relevant for control flow. They differ from TBlock in that only their
+	final element can be a control flow expression (the terminator). As a consequence, a given TBlock is split up
+	into several basic blocks when control flow expressions are encountered.
+*)
+module BasicBlock = struct
+	type block_kind =
+		| BKRoot                    (* The unique root block of the graph *)
+		| BKNormal                  (* A normal block *)
+		| BKFunctionBegin of tfunc  (* Entry block of a function *)
+		| BKFunctionEnd             (* Exit block of a function *)
+		| BKSub                     (* A sub block *)
+		| BKConditional             (* A "then", "else" or "case" block *)
+		| BKLoopHead                (* Header block of a loop *)
+		| BKException               (* Relay block for exceptions *)
+		| BKUnreachable             (* The unique unreachable block *)
+		| BKCatch of tvar           (* A catch block *)
+
+	type cfg_edge_Flag =
+		| FlagExecutable      (* Used by constant propagation to handle live edges *)
+		| FlagDce             (* Used by DCE to keep track of handled edges *)
+		| FlagCodeMotion      (* Used by code motion to track handled edges *)
+		| FlagCopyPropagation (* Used by copy propagation to track handled eges *)
+
+	type cfg_edge_kind =
+		| CFGGoto                (* An unconditional branch *)
+		| CFGFunction            (* Link to a function *)
+		| CFGMaybeThrow          (* The block may or may not throw an exception *)
+		| CFGCondBranch of texpr (* A conditional branch *)
+		| CFGCondElse            (* A conditional alternative (else,default) *)
+
+	and cfg_edge = {
+		cfg_from : t;                           (* The source block *)
+		cfg_to : t;                             (* The target block *)
+		cfg_kind : cfg_edge_kind;               (* The edge kind *)
+		mutable cfg_flags : cfg_edge_Flag list; (* Edge flags *)
+	}
+
+	and syntax_edge =
+		| SEIfThen of t * t                                (* `if` with "then" and "next" *)
+		| SEIfThenElse of t * t * t * Type.t               (* `if` with "then", "else" and "next" *)
+		| SESwitch of (texpr list * t) list * t option * t (* `switch` with cases, "default" and "next" *)
+		| SETry of t * t * (tvar * t) list * t             (* `try` with "exc", catches and "next" *)
+		| SEWhile of t * t * t                             (* `while` with "head", "body" and "next" *)
+		| SESubBlock of t * t                              (* "sub" with "next" *)
+		| SEMerge of t                                     (* Merge to same block *)
+		| SEEnd                                            (* End of syntax *)
+		| SENone                                           (* No syntax exit *)
+
+	and t = {
+		bb_id : int;                          (* The unique ID of the block *)
+		bb_type : Type.t;                     (* The block type *)
+		bb_pos : pos;                         (* The block position *)
+		bb_kind : block_kind;                 (* The block kind *)
+		mutable bb_closed : bool;             (* Whether or not the block has been closed *)
+		(* elements *)
+		bb_el : texpr DynArray.t;             (* The block expressions *)
+		bb_phi : texpr DynArray.t;            (* SSA-phi expressions *)
+		(* relations *)
+		mutable bb_outgoing : cfg_edge list;  (* Outgoing edges *)
+		mutable bb_incoming : cfg_edge list;  (* Incoming edges *)
+		mutable bb_dominator : t;             (* The block's dominator *)
+		mutable bb_dominated : t list;        (* The dominated blocks *)
+		mutable bb_df : t list;               (* The dominance frontier *)
+		mutable bb_syntax_edge : syntax_edge; (* The syntactic edge *)
+		mutable bb_loop_groups : int list;    (* The loop groups this block belongs to *)
+		mutable bb_scopes : int list;         (* The scopes this block belongs to *)
+		(* variables *)
+		mutable bb_var_writes : tvar list;    (* List of assigned variables *)
+	}
+
+	let s_block_kind = function
+		| BKRoot -> "BKRoot"
+		| BKNormal -> "BKNormal"
+		| BKFunctionBegin _ -> "BKFunctionBegin"
+		| BKFunctionEnd -> "BKFunctionEnd"
+		| BKSub -> "BKSub"
+		| BKConditional -> "BKConditional"
+		| BKLoopHead -> "BKLoopHead"
+		| BKException -> "BKException"
+		| BKUnreachable -> "BKUnreachable"
+		| BKCatch _ -> "BKCatch"
+
+	let s_cfg_edge_kind = function
+		| CFGGoto -> "CFGGoto"
+		| CFGFunction -> "CFGFunction"
+		| CFGMaybeThrow -> "CFGMaybeThrow"
+		| CFGCondBranch e -> "CFGCondBranch " ^ (s_expr_pretty "" (s_type (print_context())) e)
+		| CFGCondElse -> "CFGCondElse"
+
+	let has_flag edge flag =
+		List.mem flag edge.cfg_flags
+
+	(* expressions *)
+
+	let add_texpr bb e =
+		DynArray.add bb.bb_el e
+
+	let get_texpr bb is_phi i =
+		DynArray.get (if is_phi then bb.bb_phi else bb.bb_el) i
+
+	(* edges *)
+
+	let set_syntax_edge bb se =
+		bb.bb_syntax_edge <- se
+
+	let add_cfg_edge bb_from bb_to kind =
+		if bb_from.bb_kind <> BKUnreachable then begin
+			let edge = { cfg_from = bb_from; cfg_to = bb_to; cfg_kind = kind; cfg_flags = [] } in
+			bb_from.bb_outgoing <- edge :: bb_from.bb_outgoing;
+			bb_to.bb_incoming <- edge :: bb_to.bb_incoming;
+		end
+
+	let _create id kind t p =
+		let rec bb = {
+			bb_kind = kind;
+			bb_id = id;
+			bb_type = t;
+			bb_pos = p;
+			bb_closed = false;
+			bb_el = DynArray.create();
+			bb_phi = DynArray.create();
+			bb_outgoing = [];
+			bb_incoming = [];
+			bb_dominator = bb;
+			bb_dominated = [];
+			bb_df = [];
+			bb_syntax_edge = SENone;
+			bb_loop_groups = [];
+			bb_var_writes = [];
+			bb_scopes = [];
+		} in
+		bb
+
+	let in_scope bb bb' = match bb'.bb_scopes with
+		| [] -> error (Printf.sprintf "Scope-less block (kind: %s)" (s_block_kind bb'.bb_kind)) bb'.bb_pos
+		| scope :: _ -> List.mem scope bb.bb_scopes
+end
+
+(*
+	A Graph contains all relevant information for a given method. It is built from the field expression
+	and then refined in subsequent modules such as Ssa.
+*)
+module Graph = struct
+	open BasicBlock
+
+	type texpr_lookup = BasicBlock.t * bool * int
+	type tfunc_info = BasicBlock.t * Type.t * pos * tfunc
+	type var_write = BasicBlock.t list
+	type 'a itbl = (int,'a) Hashtbl.t
+
+	type var_info = {
+		vi_var : tvar;                            (* The variable itself *)
+		vi_extra : tvar_extra;                    (* The original v_extra *)
+		vi_bb_declare : BasicBlock.t;             (* The block where this variable was declared *)
+		mutable vi_origin : tvar;                 (* The origin variable of this variable *)
+		mutable vi_writes : var_write;            (* A list of blocks that assign to this variable *)
+		mutable vi_value : texpr_lookup option;   (* The value of this variable, if known *)
+		mutable vi_ssa_edges : texpr_lookup list; (* The expressions this variable influences *)
+		mutable vi_reaching_def : tvar option;    (* The current reaching definition variable of this variable *)
+	}
+
+	type t = {
+		mutable g_root : BasicBlock.t;             (* The unique root block *)
+		mutable g_exit : BasicBlock.t;             (* The unique exit block *)
+		mutable g_unreachable : BasicBlock.t;      (* The unique unreachable block *)
+		mutable g_functions : tfunc_info itbl;     (* A map of functions, indexed by their block IDs *)
+		mutable g_nodes : BasicBlock.t list;       (* A list of all blocks *)
+		g_var_infos : var_info DynArray.t;         (* A map of variable information *)
+		mutable g_loops : BasicBlock.t IntMap.t;   (* A map containing loop information *)
+	}
+
+	(* variables *)
+
+	let create_var_info g bb v =
+		let vi = {
+			vi_var = v;
+			vi_extra = v.v_extra;
+			vi_bb_declare = bb;
+			vi_origin = v;
+			vi_writes = [];
+			vi_value = None;
+			vi_ssa_edges = [];
+			vi_reaching_def = None;
+		} in
+		DynArray.add g.g_var_infos vi;
+		let i = DynArray.length g.g_var_infos - 1 in
+		v.v_extra <- Some([],Some (mk (TConst (TInt (Int32.of_int i))) t_dynamic null_pos))
+
+	let get_var_info g v = match v.v_extra with
+		| Some(_,Some {eexpr = TConst (TInt i32)}) -> DynArray.get g.g_var_infos (Int32.to_int i32)
+		| _ -> assert false
+
+	let declare_var g v bb =
+		create_var_info g bb v
+
+	let add_var_def g bb v =
+		if bb.bb_id > 0 then begin
+			bb.bb_var_writes <- v :: bb.bb_var_writes;
+			let vi = get_var_info g v in
+			vi.vi_writes <- bb :: vi.vi_writes;
+		end
+
+	let set_var_value g v bb is_phi i =
+		(get_var_info g v).vi_value <- Some (bb,is_phi,i)
+
+	let get_var_value g v =
+		let value = (get_var_info g v).vi_value in
+		let bb,is_phi,i = match value with
+			| None -> raise Not_found
+			| Some l -> l
+		in
+		match (get_texpr bb is_phi i).eexpr with
+		| TVar(_,Some e) | TBinop(OpAssign,_,e) -> e
+		| _ -> assert false
+
+	let add_var_origin g v v_origin =
+		(get_var_info g v).vi_origin <- v_origin
+
+	let get_var_origin g v =
+		(get_var_info g v).vi_origin
+
+	let add_ssa_edge g v bb is_phi i =
+		let vi = get_var_info g v in
+		vi.vi_ssa_edges <- (bb,is_phi,i) :: vi.vi_ssa_edges
+
+	(* nodes *)
+
+	let add_function g tf t p bb =
+		Hashtbl.add g.g_functions bb.bb_id (bb,t,p,tf)
+
+	let alloc_id =
+		let r = ref 1 in
+		(fun () ->
+			incr r;
+			!r
+		)
+
+	let create_node g kind t p =
+		let bb = BasicBlock._create (alloc_id()) kind t p in
+		g.g_nodes <- bb :: g.g_nodes;
+		bb
+
+	let close_node g bb =
+		if bb.bb_id > 0 then begin
+			assert(not bb.bb_closed);
+			bb.bb_closed <- true
+		end
+
+	let iter_dom_tree_from g bb f =
+		let rec loop bb =
+			f bb;
+			List.iter loop bb.bb_dominated
+		in
+		loop bb
+
+	let iter_dom_tree g f =
+		iter_dom_tree_from g g.g_root f
+
+	let iter_edges_from g bb f =
+		iter_dom_tree_from g bb (fun bb -> List.iter f bb.bb_outgoing)
+
+	let iter_edges g f =
+		iter_dom_tree g (fun bb -> List.iter f bb.bb_outgoing)
+
+	(* graph *)
+
+	let create t p =
+		let bb_root = BasicBlock._create 1 BKRoot t p; in
+		let bb_unreachable = BasicBlock._create 0 BKUnreachable t_dynamic null_pos in
+		{
+			g_root = bb_root;
+			g_exit = bb_unreachable;
+			g_unreachable = bb_unreachable;
+			g_functions = Hashtbl.create 0;
+			g_nodes = [bb_root];
+			g_var_infos = DynArray.create();
+			g_loops = IntMap.empty;
+		}
+
+	let check_integrity g =
+		List.iter (fun bb ->
+			List.iter (fun edge ->
+				if edge.cfg_to = g.g_unreachable then
+					prerr_endline (Printf.sprintf "Outgoing edge from %i to the unreachable block" bb.bb_id)
+				else if not (List.memq edge edge.cfg_to.bb_incoming) then
+					prerr_endline (Printf.sprintf "Outgoing edge %i -> %i has no matching incoming edge" edge.cfg_from.bb_id edge.cfg_to.bb_id)
+			) bb.bb_outgoing;
+			List.iter (fun edge ->
+				if edge.cfg_from == g.g_unreachable then
+					prerr_endline (Printf.sprintf "Incoming edge to %i from the unreachable block" bb.bb_id)
+				else if not (List.memq edge edge.cfg_from.bb_outgoing) then
+					prerr_endline (Printf.sprintf "Incoming edge %i <- %i has no matching outgoing edge" edge.cfg_to.bb_id edge.cfg_from.bb_id)
+			) bb.bb_incoming
+		) g.g_nodes
+
+	(* inference *)
+
+	type dom_bb_info = {
+		bb : BasicBlock.t;
+		parent : dom_bb_info;
+		mutable idom : dom_bb_info;
+		mutable semi : int;
+		mutable label : dom_bb_info;
+		mutable ancestor : dom_bb_info;
+		mutable bucket : dom_bb_info list;
+	}
+
+	(* Infers the immediate dominators for all reachable blocks. This function can be run multiple times
+	   in case an update is necessary. *)
+	let infer_immediate_dominators g =
+		let info = Hashtbl.create 0 in
+		let nodes = DynArray.create () in
+		let get_info i = Hashtbl.find info i in
+		let add_info bb bb_parent =
+			let rec bbi = {
+				bb = bb;
+				parent = bbi;
+				idom = bbi;
+				semi = DynArray.length nodes;
+				label = bbi;
+				ancestor = bbi;
+				bucket = [];
+			} in
+			let bbi = if bb == bb_parent then bbi else {bbi with parent = get_info bb_parent.bb_id} in
+			Hashtbl.add info bb.bb_id bbi;
+			DynArray.add nodes bbi;
+		in
+		let rec loop bb_parent bb =
+			bb.bb_dominated <- [];
+			add_info bb bb_parent;
+			List.iter (fun edge ->
+				let bb_to = edge.cfg_to in
+				if not (Hashtbl.mem info bb_to.bb_id) then
+					loop bb bb_to
+			) bb.bb_outgoing
+		in
+		loop g.g_root g.g_root;
+		let compress bbi =
+			let rec loop l bbi =
+				if bbi.ancestor == bbi then l else loop (bbi :: l) bbi.ancestor
+			in
+			let worklist = loop [bbi] bbi.ancestor in
+			match worklist with
+				| a :: worklist ->
+					ignore(List.fold_left (fun (a,min_semi) bbi_desc ->
+						let bbi = bbi_desc.label in
+						if bbi.semi > min_semi then begin
+							bbi_desc.label <- a.label;
+							(bbi_desc,min_semi)
+						end else
+							(bbi_desc,bbi.semi)
+					) (a,a.label.semi) worklist)
+				| [] ->
+					assert false
+		in
+		let eval v =
+			let bbi = get_info v in
+			if bbi.ancestor != bbi then begin
+				compress bbi;
+				bbi.label
+			end else
+				bbi
+		in
+		let rec loop nodes' = match nodes' with
+			| [_] -> ()
+			| [] -> assert false
+			| w :: nodes' ->
+				let semi = List.fold_left (fun acc v ->
+					min acc (eval v.cfg_from.bb_id).semi) w.semi w.bb.bb_incoming
+				in
+				w.semi <- semi;
+				let bbi = DynArray.get nodes semi in
+				bbi.bucket <- w :: bbi.bucket;
+				let bbi_p = w.parent in
+				w.ancestor <- bbi_p;
+				List.iter (fun v ->
+					let u = eval v.bb.bb_id in
+					if u.semi < v.semi then
+						v.idom <- u
+					else
+						v.idom <- bbi_p
+				) bbi_p.bucket;
+				bbi_p.bucket <- [];
+				loop nodes'
+		in
+		let l = DynArray.to_list nodes in
+		loop (List.rev l);
+		List.iter (fun w ->
+			if w.idom != (DynArray.get nodes w.semi) then w.idom <- w.idom.idom
+		) (List.tl l);
+		DynArray.iter (fun bbi ->
+			if bbi.idom != bbi then begin
+				let bb = bbi.bb in
+				let bb' = bbi.idom.bb in
+				if bb != bb' then begin
+					bb.bb_dominator <- bb';
+					bb'.bb_dominated <- bb :: bb'.bb_dominated
+				end
+			end
+		) nodes
+
+	(* Infers the dominance frontier for all reachable blocks. This function should only be run once. *)
+	let infer_dominance_frontier g =
+		iter_edges g (fun edge ->
+			let rec loop bb =
+				if bb != g.g_unreachable && bb != edge.cfg_to && bb != edge.cfg_to.bb_dominator then begin
+					if edge.cfg_to != g.g_exit then bb.bb_df <- edge.cfg_to :: bb.bb_df;
+					if bb.bb_dominator != bb then loop bb.bb_dominator
+				end
+			in
+			loop edge.cfg_from
+		)
+
+	(* Infers variable declarations and definitions. This function should only be run once. *)
+	let infer_var_writes g =
+		iter_dom_tree g (fun bb ->
+			begin match bb.bb_kind with
+				| BKCatch v ->
+					declare_var g v bb;
+					add_var_def g bb v
+				| BKFunctionBegin tf ->
+					List.iter (fun (v,_) ->
+						declare_var g v bb;
+						add_var_def g bb v
+					) tf.tf_args;
+				| _ ->
+					()
+			end;
+			DynArray.iter (fun e -> match e.eexpr with
+				| TVar(v,eo) ->
+					declare_var g v bb;
+					if eo <> None then add_var_def g bb v;
+				| TBinop(OpAssign,{eexpr = TLocal v},_) ->
+					add_var_def g bb v
+				| _ ->
+					()
+			) bb.bb_el
+		)
+
+	(* Infers the scopes of all reachable blocks. This function can be run multiple times
+	   in case an update is necessary *)
+	let infer_scopes g =
+		let next_scope_id = ref 0 in
+		let next_scope scopes =
+			incr next_scope_id;
+			!next_scope_id :: scopes
+		in
+		let rec loop scopes bb =
+			bb.bb_scopes <- scopes;
+			begin match bb.bb_syntax_edge with
+				| SEIfThen(bb_then,bb_next) ->
+					loop (next_scope scopes) bb_then;
+					loop scopes bb_next
+				| SEIfThenElse(bb_then,bb_else,bb_next,_) ->
+					loop (next_scope scopes) bb_then;
+					loop (next_scope scopes) bb_else;
+					loop scopes bb_next
+				| SESwitch(cases,bbo,bb_next) ->
+					List.iter (fun (_,bb_case) -> loop (next_scope scopes) bb_case) cases;
+					(match bbo with None -> () | Some bb -> loop (next_scope scopes) bb);
+					loop scopes bb_next;
+				| SETry(bb_try,bb_exc,catches,bb_next) ->
+					let scopes' = next_scope scopes in
+					loop scopes' bb_try;
+					loop scopes' bb_exc;
+					List.iter (fun (_,bb_catch) -> loop (next_scope scopes) bb_catch) catches;
+					loop scopes bb_next
+				| SEWhile(bb_head,bb_body,bb_next) ->
+					let scopes' = next_scope scopes in
+					loop scopes' bb_head;
+					loop scopes' bb_body;
+					loop scopes bb_next;
+				| SESubBlock(bb_sub,bb_next) ->
+					loop (next_scope scopes) bb_sub;
+					loop scopes bb_next
+				| SEMerge bb ->
+					loop scopes bb
+				| SEEnd | SENone ->
+					()
+			end
+		in
+		Hashtbl.iter (fun _ (bb,_,_,_) -> loop [0] bb) g.g_functions
+end
+
+type analyzer_context = {
+	com : Common.context;
+	config : AnalyzerConfig.t;
+	graph : Graph.t;
+	temp_var_name : string;
+	is_real_function : bool;
+	mutable entry : BasicBlock.t;
+	mutable has_unbound : bool;
+	mutable loop_counter : int;
+	mutable loop_stack : int list;
+}

+ 1 - 1
src/optimization/filters.ml

@@ -868,7 +868,7 @@ let add_field_inits ctx t =
 				| _ ->
 				| _ ->
 					assert false
 					assert false
 			in
 			in
-			let config = Analyzer.Config.get_field_config ctx.com c cf in
+			let config = AnalyzerConfig.get_field_config ctx.com c cf in
 			Analyzer.Run.run_on_field ctx config c cf;
 			Analyzer.Run.run_on_field ctx config c cf;
 			(match cf.cf_expr with
 			(match cf.cf_expr with
 			| Some e ->
 			| Some e ->

+ 0 - 1
std/cpp/ConstPointer.hx

@@ -22,7 +22,6 @@
  package cpp;
  package cpp;
 
 
 @:coreType @:include("cpp/Pointer.h") @:native("cpp.Pointer")
 @:coreType @:include("cpp/Pointer.h") @:native("cpp.Pointer")
-@:analyzer(no_simplification)
 extern class ConstPointer<T>
 extern class ConstPointer<T>
 {
 {
    // ptr actually returns the pointer - not strictly a 'T' - for pointers to smart pointers
    // ptr actually returns the pointer - not strictly a 'T' - for pointers to smart pointers

+ 0 - 1
std/cpp/Function.hx

@@ -22,7 +22,6 @@
  package cpp;
  package cpp;
 
 
 @:coreType @:structAccess @:include("cpp/Pointer.h")
 @:coreType @:structAccess @:include("cpp/Pointer.h")
-@:analyzer(no_simplification)
 extern class Function<T,ABI:cpp.abi.Abi>
 extern class Function<T,ABI:cpp.abi.Abi>
 {
 {
    public function new(d:Dynamic);
    public function new(d:Dynamic);

+ 0 - 1
std/cpp/Lib.hx

@@ -43,7 +43,6 @@ class Lib {
 		return untyped __global__.__hxcpp_unload_all_libraries();
 		return untyped __global__.__hxcpp_unload_all_libraries();
 	}
 	}
 
 
-   @:analyzer(no_simplification)
 	public static function _loadPrime( lib : String, prim : String, signature : String, quietFail = false ) : Dynamic {
 	public static function _loadPrime( lib : String, prim : String, signature : String, quietFail = false ) : Dynamic {
 		var factory:Callable< ConstCharStar -> Object > =
 		var factory:Callable< ConstCharStar -> Object > =
                untyped __global__.__hxcpp_cast_get_proc_address(lib, prim + "__prime", quietFail);
                untyped __global__.__hxcpp_cast_get_proc_address(lib, prim + "__prime", quietFail);

+ 0 - 1
std/cpp/Pointer.hx

@@ -22,7 +22,6 @@
  package cpp;
  package cpp;
 
 
 @:coreType
 @:coreType
-@:analyzer(no_simplification)
 extern class Pointer<T> extends ConstPointer<T> implements ArrayAccess<T>
 extern class Pointer<T> extends ConstPointer<T> implements ArrayAccess<T>
 {
 {
    @:analyzer(no_simplification)
    @:analyzer(no_simplification)

+ 1 - 2
std/cpp/Prime.hx

@@ -32,7 +32,6 @@ class Prime {
 
 
    #if (!macro && cpp)
    #if (!macro && cpp)
 
 
-   @:analyzer(no_simplification)
 	public static function _loadPrime( lib : String, prim : String, signature : String, quietFail = false ) : Dynamic {
 	public static function _loadPrime( lib : String, prim : String, signature : String, quietFail = false ) : Dynamic {
 		var factory:Callable< ConstCharStar -> Object > =
 		var factory:Callable< ConstCharStar -> Object > =
                untyped __global__.__hxcpp_cast_get_proc_address(lib, prim + "__prime", quietFail);
                untyped __global__.__hxcpp_cast_get_proc_address(lib, prim + "__prime", quietFail);
@@ -76,7 +75,7 @@ class Prime {
       #if neko
       #if neko
       var init = neko.Lib.load(inModuleName, "neko_init", 5);
       var init = neko.Lib.load(inModuleName, "neko_init", 5);
 
 
-      if (init != null) 
+      if (init != null)
       {
       {
          init( function(s) return new String(s),
          init( function(s) return new String(s),
                function(len:Int) { var r = []; if (len > 0) r[len - 1] = null; return r; },
                function(len:Int) { var r = []; if (len > 0) r[len - 1] = null; return r; },

+ 0 - 1
std/python/Syntax.hx

@@ -28,7 +28,6 @@ import haxe.macro.ExprTools;
 #end
 #end
 
 
 @:noPackageRestrict
 @:noPackageRestrict
-@:analyzer(no_simplification)
 extern class Syntax {
 extern class Syntax {
 
 
 	#if macro
 	#if macro

+ 1 - 1
tests/optimization/src/TestJs.hx

@@ -73,7 +73,7 @@ class TestJs {
 	}
 	}
 
 
 	@:js("var a = { v : [{ b : 1}]};a;a.v.length == 1 && a.v[0].b == 1;")
 	@:js("var a = { v : [{ b : 1}]};a;a.v.length == 1 && a.v[0].b == 1;")
-	@:analyzer(no_const_propagation, no_local_dce, no_check_has_effect)
+	@:analyzer(no_const_propagation, no_local_dce)
 	static function testDeepMatchingWithoutClosures() {
 	static function testDeepMatchingWithoutClosures() {
 		var a = {v: [{b: 1}]};
 		var a = {v: [{b: 1}]};
 		a;
 		a;

+ 1 - 1
tests/optimization/src/issues/Issue3713.hx

@@ -22,7 +22,7 @@ class Issue3713 {
 		var b = BImpl._new();
 		var b = BImpl._new();
 		var c_x = 1;
 		var c_x = 1;
 	')
 	')
-	@:analyzer(no_const_propagation, no_local_dce, no_check_has_effect)
+	@:analyzer(no_const_propagation, no_local_dce)
 	static function test() {
 	static function test() {
         var b = new B<Int>();
         var b = new B<Int>();
         var c = b.f();
         var c = b.f();

Some files were not shown because too many files changed in this diff