Browse Source

Merge pull request #3425 from Simn/ssa_merge

Add static analyzer with constant propagation
Simon Krajewski 11 years ago
parent
commit
5c10759ad7

+ 1 - 0
.gitignore

@@ -64,3 +64,4 @@ tests/unit/unit.py.res1.txt
 tests/unit/unit.py.res2.bin
 tests/unit/unit.py.res2.bin
 *.cmo
 *.cmo
 tests/sys/bin/
 tests/sys/bin/
+tests/optimization/dump/

+ 4 - 2
Makefile

@@ -49,7 +49,7 @@ RELDIR=../../..
 
 
 MODULES=ast type lexer common genxml parser typecore optimizer typeload \
 MODULES=ast type lexer common genxml parser typecore optimizer typeload \
 	codegen gencommon genas3 gencpp genjs genneko genphp genswf8 \
 	codegen gencommon genas3 gencpp genjs genneko genphp genswf8 \
-	genswf9 genswf genjava gencs genpy interp dce filters typer matcher version main
+	genswf9 genswf genjava gencs genpy interp dce analyzer filters typer matcher version main
 
 
 ADD_REVISION=0
 ADD_REVISION=0
 
 
@@ -116,13 +116,15 @@ uninstall:
 
 
 # Modules
 # Modules
 
 
+analyzer.$(MODULE_EXT): ast.$(MODULE_EXT) type.$(MODULE_EXT) common.$(MODULE_EXT) codegen.$(MODULE_EXT)
+
 codegen.$(MODULE_EXT): optimizer.$(MODULE_EXT) typeload.$(MODULE_EXT) typecore.$(MODULE_EXT) type.$(MODULE_EXT) genxml.$(MODULE_EXT) common.$(MODULE_EXT) ast.$(MODULE_EXT)
 codegen.$(MODULE_EXT): optimizer.$(MODULE_EXT) typeload.$(MODULE_EXT) typecore.$(MODULE_EXT) type.$(MODULE_EXT) genxml.$(MODULE_EXT) common.$(MODULE_EXT) ast.$(MODULE_EXT)
 
 
 common.$(MODULE_EXT): type.$(MODULE_EXT) ast.$(MODULE_EXT)
 common.$(MODULE_EXT): type.$(MODULE_EXT) ast.$(MODULE_EXT)
 
 
 dce.$(MODULE_EXT): ast.$(MODULE_EXT) common.$(MODULE_EXT) codegen.$(MODULE_EXT) type.$(MODULE_EXT)
 dce.$(MODULE_EXT): ast.$(MODULE_EXT) common.$(MODULE_EXT) codegen.$(MODULE_EXT) type.$(MODULE_EXT)
 
 
-filters.$(MODULE_EXT): ast.$(MODULE_EXT) common.$(MODULE_EXT) type.$(MODULE_EXT) dce.$(MODULE_EXT) codegen.$(MODULE_EXT) typecore.$(MODULE_EXT)
+filters.$(MODULE_EXT): ast.$(MODULE_EXT) analyzer.$(MODULE_EXT) common.$(MODULE_EXT) type.$(MODULE_EXT) dce.$(MODULE_EXT) codegen.$(MODULE_EXT) typecore.$(MODULE_EXT)
 
 
 genas3.$(MODULE_EXT): type.$(MODULE_EXT) common.$(MODULE_EXT) codegen.$(MODULE_EXT) ast.$(MODULE_EXT)
 genas3.$(MODULE_EXT): type.$(MODULE_EXT) common.$(MODULE_EXT) codegen.$(MODULE_EXT) ast.$(MODULE_EXT)
 
 

+ 1124 - 0
analyzer.ml

@@ -0,0 +1,1124 @@
+open Ast
+open Type
+open Common
+
+module IntMap = Map.Make(struct type t = int let compare a b = a - b end)
+
+let s_expr = s_expr (s_type (print_context()))
+let s_expr_pretty = s_expr_pretty "" (s_type (print_context()))
+let debug e = print_endline (s_expr e)
+let debug_pretty s e = Printf.printf "%s %s\n" s (s_expr_pretty e)
+
+let flag_no_check = "no_check"
+let flag_check = "check"
+let flag_no_const_propagation = "no_const_propagation"
+let flag_const_propagation = "const_propagation"
+let flag_ignore = "ignore"
+let flag_no_simplification = "no_simplification"
+
+let has_analyzer_option meta s =
+	try
+		let _,el,_ = Meta.get Meta.Analyzer meta in
+		List.exists (fun (e,p) ->
+			match e with
+				| EConst(Ident s2) when s = s2 -> true
+				| _ -> false
+		) el
+	with Not_found ->
+		false
+
+let rec get_type_meta t = match t with
+	| TMono r ->
+		begin match !r with
+			| None -> raise Not_found
+			| Some t -> get_type_meta t
+		end
+	| TLazy f ->
+		get_type_meta (!f())
+	| TInst(c,_) ->
+		c.cl_meta
+	| TEnum(en,_) ->
+		en.e_meta
+	| TAbstract(a,_) ->
+		a.a_meta
+	| TType(t,_) ->
+		t.t_meta
+	| TAnon _ | TFun _ | TDynamic _ ->
+		raise Not_found
+
+let type_has_analyzer_option t s =
+	try
+		has_analyzer_option (get_type_meta t) s
+	with Not_found ->
+		false
+
+(*
+	This module simplifies the AST by introducing temporary variables for complex expressions in many places.
+	In particular, it ensures that no branching can occur in value-places so that we can later insert SSA PHI
+	nodes without worrying about their placement.
+*)
+module Simplifier = struct
+	let mk_block_context com gen_temp =
+		let block_el = ref [] in
+		let push e = block_el := e :: !block_el in
+		let assign ev e =
+			let mk_assign e2 = match e2.eexpr with
+				| TBreak | TContinue | TThrow _ -> e2
+				| _ -> mk (TBinop(OpAssign,ev,e2)) e2.etype e2.epos
+			in
+			let rec loop e = match e.eexpr with
+				| TBlock el ->
+					begin match List.rev el with
+						| e1 :: el ->
+							let el = List.rev ((loop e1) :: el) in
+							{e with eexpr = TBlock el}
+						| _ ->
+							mk_assign e
+					end
+				| TIf(e1,e2,eo) ->
+					let e2 = loop e2 in
+					let eo = match eo with None -> None | Some e3 -> Some (loop e3) in
+					{e with eexpr = TIf(e1,e2,eo)}
+				| TSwitch(e1,cases,edef) ->
+					let cases = List.map (fun (el,e) ->
+						let e = loop e in
+						el,e
+					) cases in
+					let edef = match edef with None -> None | Some edef -> Some (loop edef) in
+					{e with eexpr = TSwitch(e1,cases,edef)}
+				| TTry(e1,catches) ->
+					let e1 = loop e1 in
+					let catches = List.map (fun (v,e) ->
+						let e = loop e in
+						v,e
+					) catches in
+					{e with eexpr = TTry(e1,catches)}
+(* 				| TBinop(OpAssign,({eexpr = TLocal _} as e1),e2) ->
+					push e;
+					mk_assign e1 *)
+(* 				| TBinop(OpAssignOp op,({eexpr = TLocal _} as e1),e2) ->
+					push e;
+					mk_assign e1 *)
+				| TParenthesis e1 | TMeta(_, e1) ->
+					loop e1 (* this is weird *)
+				| _ ->
+					mk_assign e
+			in
+			loop e
+		in
+		let declare_temp t eo p =
+			let v = gen_temp t in
+			v.v_meta <- [Meta.CompilerGenerated,[],p];
+			let e_v = mk (TLocal v) t p in
+			let declare e_init =
+				let e = mk (TVar (v,e_init)) com.basic.tvoid p in
+				push e;
+			in
+			begin match eo with
+				| None ->
+					declare None
+				| Some e1 ->
+					begin match e1.eexpr with
+						| TThrow _ | TReturn _ | TBreak | TContinue ->
+							()
+						| _ ->
+							let e1 = assign e_v e1 in
+							begin match e1.eexpr with
+								| TBinop(OpAssign,{eexpr = TLocal v1},e2) when v == v1 ->
+									declare (Some e2)
+								| _ ->
+									declare None;
+									push e1
+							end
+					end
+			end;
+			e_v
+		in
+		let rec push_block () =
+			let cur = !block_el in
+			block_el := [];
+			fun () ->
+				let added = !block_el in
+				block_el := cur;
+				List.rev added
+		and block f el =
+			let close = push_block() in
+			List.iter (fun e ->
+				push (f e)
+			) el;
+			close()
+		in
+		block,declare_temp,fun () -> !block_el
+
+	let apply com gen_temp e =
+		let block,declare_temp,close_block = mk_block_context com gen_temp in
+		let skip_binding ?(allow_tlocal=false) e =
+			let rec loop e =
+				match e.eexpr with
+				| TConst _ | TTypeExpr _ | TFunction _ -> ()
+				| TLocal _ when allow_tlocal -> ()
+				| TParenthesis e1 | TCast(e1,None) | TEnumParameter(e1,_,_) -> Type.iter loop e
+				| TField(_,(FStatic(c,cf) | FInstance(c,_,cf))) when has_analyzer_option cf.cf_meta flag_no_simplification || has_analyzer_option c.cl_meta flag_no_simplification -> ()
+				| _ -> raise Exit
+			in
+			try
+				loop e;
+				true
+			with Exit ->
+				begin match follow e.etype with
+					| TAbstract({a_path = [],"Void"},_) -> true
+					| _ -> false
+				end
+		in
+		let rec loop e = match e.eexpr with
+			| TLocal v when Meta.has Meta.Unbound v.v_meta && v.v_name <> "`trace" ->
+				raise Exit (* nope *)
+			| TBlock el ->
+				{e with eexpr = TBlock (block loop el)}
+			| TCall({eexpr = TField(_,(FStatic(c,cf) | FInstance(c,_,cf)))},el) when has_analyzer_option cf.cf_meta flag_no_simplification || has_analyzer_option c.cl_meta flag_no_simplification ->
+				e
+			| TField(_,(FStatic(c,cf) | FInstance(c,_,cf))) when has_analyzer_option cf.cf_meta flag_no_simplification || has_analyzer_option c.cl_meta flag_no_simplification ->
+				e
+			| TCall(e1,el) ->
+				let e1 = loop e1 in
+				let check e t =
+					if type_has_analyzer_option t flag_no_simplification then e
+					else bind e
+				in
+				let el = match e1.eexpr,follow e1.etype with
+					| TConst TSuper,_ when com.platform = Java || com.platform = Cs ->
+						(* they hate you if you mess up the super call *)
+						el
+					| _,TFun _ ->
+						Codegen.UnificationCallback.check_call check el e1.etype
+					| _ ->
+						(* too dangerous *)
+						List.map loop el
+				in
+				{e with eexpr = TCall(e1,el)}
+			| TNew(c,tl,el) ->
+				{e with eexpr = TNew(c,tl,ordered_list el)}
+			| TArrayDecl el ->
+				{e with eexpr = TArrayDecl (ordered_list el)}
+			| TObjectDecl fl ->
+				let el = ordered_list (List.map snd fl) in
+				{e with eexpr = TObjectDecl (List.map2 (fun (n,_) e -> n,e) fl el)}
+			| TBinop(OpBoolAnd | OpBoolOr as op,e1,e2) ->
+				let e1 = loop e1 in
+				let e_then = mk (TBlock (block loop [e2])) e2.etype e2.epos 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)) com.basic.tbool e.epos)
+			| TBinop((OpAssign | OpAssignOp _) as op,{eexpr = TArray(e11,e12)},e2) ->
+				let e1 = match ordered_list [e11;e12] with
+					| [e1;e2] ->
+						{e with eexpr = TArray(e1,e2)}
+					| _ ->
+						assert false
+				in
+				let e2 = loop e2 in
+				{e with eexpr = TBinop(op,e1,e2)}
+			| TBinop((OpAssign | OpAssignOp _) as op,e1,e2) ->
+				let e2 = bind e2 in
+				let e1 = loop e1 in
+				{e with eexpr = TBinop(op,e1,e2)}
+			| TBinop(op,e1,e2) ->
+				begin match ordered_list [e1;e2] with
+					| [e1;e2] ->
+						{e with eexpr = TBinop(op,e1,e2)}
+					| _ ->
+						assert false
+				end
+			| TArray(e1,e2) ->
+				begin match ordered_list [e1;e2] with
+					| [e1;e2] ->
+						{e with eexpr = TArray(e1,e2)}
+					| _ ->
+						assert false
+				end
+			| TWhile(e1,e2,flag) when (match e1.eexpr with TConst(TBool true) | TParenthesis {eexpr = TConst(TBool true)} -> false | _ -> true) ->
+				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 ->
+						(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 e1 = bind e1 in
+				let e2 = loop e2 in
+				{e with eexpr = TFor(v,e1,e2)}
+			| TIf(e1,e2,eo) ->
+				let e1 = bind e1 in
+				let e2 = loop e2 in
+				let eo = match eo with None -> None | Some e -> Some (loop e) in
+				{e with eexpr = TIf(e1,e2,eo)}
+			| TVar(v,Some e1) ->
+				let e1 = match e1.eexpr with
+					| TFunction _ -> loop e1
+					| TArrayDecl [{eexpr = TFunction _}] -> loop e1
+					| _ -> bind e1
+				in
+				{e with eexpr = TVar(v,Some e1)}
+			| TUnop((Neg | NegBits | Not) as op,flag,e1) ->
+				let e1 = bind e1 in
+				{e with eexpr = TUnop(op,flag,e1)}
+			| TField(e1,fa) ->
+				let e1 = bind e1 in
+				{e with eexpr = TField(e1,fa)}
+			| TReturn (Some ({eexpr = TThrow _ | TReturn _} as e1)) ->
+				loop e1 (* this is a bit hackish *)
+			| TReturn (Some e1) ->
+				let e1 = bind e1 in
+				{e with eexpr = TReturn (Some e1)}
+			| TCast(e1,mto) ->
+				let e1 = bind ~allow_tlocal:true e1 in
+				{e with eexpr = TCast(e1,mto)}
+			| _ ->
+				Type.map_expr loop e
+		and bind ?(allow_tlocal=false) e =
+			let e = loop e in
+			if skip_binding ~allow_tlocal e then
+				e
+			else
+				declare_temp e.etype (Some e) e.epos
+		and ordered_list el =
+			if List.for_all (skip_binding ~allow_tlocal:true) el then
+				List.map loop el
+			else
+				List.map bind el
+		in
+		let e = loop e in
+		match close_block() with
+			| [] ->
+				e
+			| el ->
+				mk (TBlock (List.rev (e :: el))) e.etype e.epos
+
+	let unapply e =
+		let var_map = ref IntMap.empty in
+		let rec loop e = match e.eexpr with
+			| TBlock el ->
+				let el = ExtList.List.filter_map (fun e -> match e.eexpr with
+					| TVar(v,Some e1) when Meta.has Meta.CompilerGenerated v.v_meta ->
+						var_map := IntMap.add v.v_id (loop e1) !var_map;
+						None
+					| _ ->
+						Some (loop e)
+				) el in
+				{e with eexpr = TBlock el}
+			| TLocal v when Meta.has Meta.CompilerGenerated v.v_meta ->
+				begin try IntMap.find v.v_id !var_map
+				with Not_found -> e end
+			| _ ->
+				Type.map_expr loop e
+		in
+		loop e
+end
+
+module Ssa = struct
+
+	type var_map = tvar IntMap.t
+
+	type condition =
+		| Equal of tvar * texpr
+		| NotEqual of tvar * texpr
+
+	type node_data = {
+		nd_pos: pos;
+		mutable nd_var_map : var_map;
+		mutable nd_terminates : bool;
+	}
+
+	type join_node = {
+		mutable branches : node_data list;
+	}
+
+	type ssa_context = {
+		com : Common.context;
+		mutable cleanup : (unit -> unit) list;
+		mutable cur_data : node_data;
+		mutable var_values : texpr IntMap.t;
+		mutable var_conds : (condition list) IntMap.t;
+		mutable loop_stack : (join_node * join_node) list;
+		mutable exception_stack : join_node list;
+	}
+
+	let s_cond = function
+		| Equal(v,e) -> Printf.sprintf "%s == %s" v.v_name (s_expr_pretty e)
+		| NotEqual(v,e) -> Printf.sprintf "%s != %s" v.v_name (s_expr_pretty e)
+
+	let s_conds conds =
+		String.concat " && " (List.map s_cond conds)
+
+	let mk_loc v p = mk (TLocal v) v.v_type p
+
+	let mk_phi =
+		let v_phi = alloc_var "__ssa_phi__" t_dynamic in
+		(fun vl p ->
+			let e = mk (TCall(mk_loc v_phi p,(List.map (fun (v,p) -> mk_loc v p) vl))) t_dynamic p in
+			e
+		)
+
+	(* TODO: make sure this is conservative *)
+	let can_throw e =
+		let rec loop e = match e.eexpr with
+			| TConst _ | TLocal _ | TTypeExpr _ | TFunction _ | TBlock _ -> ()
+			| TCall _ | TNew _ | TThrow _ | TCast(_,Some _) -> raise Exit
+			| _ -> Type.iter loop e
+		in
+		try
+			loop e; false
+		with Exit ->
+			true
+
+	let mk_join_node() = {
+		branches = []
+	}
+
+	let mk_node_data p = {
+		nd_pos = p;
+		nd_var_map = IntMap.empty;
+		nd_terminates = false;
+	}
+
+	let add_branch join branch p =
+		join.branches <- {branch with nd_pos = p} :: join.branches
+
+	let branch ctx p =
+		let old_map = ctx.cur_data.nd_var_map in
+		let old_term = ctx.cur_data.nd_terminates in
+		ctx.cur_data.nd_terminates <- false;
+		(fun join ->
+			add_branch join ctx.cur_data p;
+			ctx.cur_data.nd_var_map <- old_map;
+			ctx.cur_data.nd_terminates <- old_term;
+		)
+
+	let terminate ctx =
+		ctx.cur_data.nd_terminates <- true
+
+	let set_loop_join ctx join_top join_bottom =
+		ctx.loop_stack <- (join_top,join_bottom) :: ctx.loop_stack;
+		(fun () ->
+			ctx.loop_stack <- List.tl ctx.loop_stack
+		)
+
+	let set_exception_join ctx join =
+		ctx.exception_stack <- join :: ctx.exception_stack;
+		(fun () ->
+			ctx.exception_stack <- List.tl ctx.exception_stack;
+		)
+
+	let declare_var ctx v p =
+		let old = v.v_extra in
+		ctx.cleanup <- (fun () ->
+			v.v_extra <- old
+		) :: ctx.cleanup;
+		ctx.cur_data.nd_var_map <- IntMap.add v.v_id v ctx.cur_data.nd_var_map;
+		v.v_extra <- Some ([],(Some (mk_loc v p)))
+
+	let assign_var ctx v e p =
+		if v.v_capture then
+			v
+		else begin
+			let i = match v.v_extra with
+				| Some (l,eo) ->
+					v.v_extra <- Some (("",t_dynamic) :: l,eo);
+					List.length l + 1
+				| _ ->
+					error "Something went wrong" p
+			in
+			let v' = alloc_var (Printf.sprintf "%s<%i>" v.v_name i) v.v_type in
+			v'.v_meta <- [(Meta.Custom ":ssa"),[],p];
+			v'.v_extra <- Some ([],(Some (mk_loc v p)));
+			ctx.cur_data.nd_var_map <- IntMap.add v.v_id v' ctx.cur_data.nd_var_map;
+			ctx.var_values <- IntMap.add v'.v_id e ctx.var_values;
+			v'
+		end
+
+	let get_var ctx v p =
+		try
+			IntMap.find v.v_id ctx.cur_data.nd_var_map
+		with Not_found ->
+			if not (has_meta Meta.Unbound v.v_meta) then
+				ctx.com.warning (Printf.sprintf "Unbound variable %s" v.v_name) p;
+			v
+
+	let close_join_node ctx node p =
+		let terminates = ref true in
+		let branches = List.filter (fun branch ->
+			if branch.nd_terminates then false
+			else begin
+				terminates := false;
+				true
+			end
+		) node.branches in
+		match branches with
+			| [] ->
+				()
+			| branch :: branches ->
+				let vars = ref (IntMap.map (fun v -> [v,branch.nd_pos]) branch.nd_var_map) in
+				let rec handle_branch branch =
+					IntMap.iter (fun i v ->
+						try
+							let vl = IntMap.find i !vars in
+							if not (List.exists (fun (v',_) -> v == v') vl) then
+								vars := IntMap.add i ((v,p) :: vl) !vars
+						with Not_found ->
+							()
+					) branch.nd_var_map;
+				in
+				List.iter handle_branch branches;
+				ctx.cur_data.nd_terminates <- !terminates;
+				IntMap.iter (fun i vl -> match vl with
+					| [v,p] ->
+						ctx.cur_data.nd_var_map <- IntMap.add i v ctx.cur_data.nd_var_map;
+					| ({v_extra = Some (_,Some {eexpr = TLocal v})},p) :: _ ->
+						ignore(assign_var ctx v (mk_phi vl p) p)
+					| _ ->
+						assert false
+				) !vars
+
+	let invert_cond = function
+		| Equal(v,e) -> NotEqual(v,e)
+		| NotEqual(v,e) -> Equal(v,e)
+
+	let invert_conds =
+		List.map invert_cond
+
+	let rec eval_cond ctx e = match e.eexpr with
+		| TBinop(OpNotEq,{eexpr = TLocal v},e1) ->
+			[NotEqual(v,e1)]
+		| TBinop(OpEq,{eexpr = TLocal v},e1) ->
+			[Equal(v,e1)]
+		| TUnop(Not,_,e1) ->
+			invert_conds (eval_cond ctx e1)
+		| TLocal v ->
+			begin try eval_cond ctx (IntMap.find v.v_id ctx.var_values)
+			with Not_found -> [] end
+		| _ ->
+			[]
+
+	let append_cond ctx v cond p =
+		begin try
+			let conds = IntMap.find v.v_id ctx.var_conds in
+			ctx.var_conds <- IntMap.add v.v_id (cond :: conds) ctx.var_conds
+		with Not_found ->
+			ctx.var_conds <- IntMap.add v.v_id [cond] ctx.var_conds
+		end
+
+	let apply_cond ctx = function
+		| Equal({v_extra = Some(_,Some {eexpr = TLocal v})} as v0,e1) ->
+			let v' = assign_var ctx v (mk_loc v0 e1.epos) e1.epos in
+			append_cond ctx v' (Equal(v',e1)) e1.epos
+		| NotEqual({v_extra = Some(_,Some {eexpr = TLocal v})} as v0,e1) ->
+			let v' = assign_var ctx v (mk_loc v0 e1.epos) e1.epos in
+			append_cond ctx v' (NotEqual(v',e1)) e1.epos
+		| _ -> ()
+
+	let apply_not_null_cond ctx v p =
+		apply_cond ctx (NotEqual(v,(mk (TConst TNull) t_dynamic p)))
+
+	let apply com e =
+		let rec handle_if ctx e econd eif eelse =
+			let econd = loop ctx econd in
+			let cond = eval_cond ctx econd in
+			let join = mk_join_node() in
+			let close = branch ctx eif.epos in
+			List.iter (apply_cond ctx) cond;
+			let eif = loop ctx eif in
+			close join;
+			let eelse = match eelse with
+				| None ->
+					let cond = invert_conds cond in
+					List.iter (apply_cond ctx) cond;
+					add_branch join ctx.cur_data e.epos;
+					None
+				| Some e ->
+					let close = branch ctx e.epos in
+					let cond = invert_conds cond in
+					List.iter (apply_cond ctx) cond;
+					let eelse = loop ctx e in
+					close join;
+					Some eelse
+			in
+			close_join_node ctx join e.epos;
+			let e = {e with eexpr = TIf(econd,eif,eelse)} in
+			e
+		and handle_loop_body ctx e =
+			let join_top = mk_join_node() in
+			let join_bottom = mk_join_node() in
+			let unset = set_loop_join ctx join_top join_bottom in
+			let close = branch ctx e.epos in
+			ignore(loop ctx e); (* TODO: I don't know if this is sane. *)
+			close join_top;
+			add_branch join_top ctx.cur_data e.epos;
+			close_join_node ctx join_top e.epos;
+			let ebody = loop ctx e in
+			ctx.cur_data.nd_terminates <- false;
+			unset();
+			close_join_node ctx join_bottom e.epos;
+			ebody
+		and loop ctx e = match e.eexpr with
+			(* var declarations *)
+			| TVar(v,eo) ->
+				declare_var ctx v e.epos;
+				let eo = match eo with
+					| None -> None
+					| Some e ->
+						let e = loop ctx e in
+						ctx.var_values <- IntMap.add v.v_id e ctx.var_values;
+						Some e
+				in
+				{e with eexpr = TVar(v,eo)}
+			| TFunction tf ->
+				let close = branch ctx e.epos in
+				List.iter (fun (v,co) ->
+					declare_var ctx v e.epos;
+ 					match co with
+						| Some TNull when (match v.v_type with TType({t_path=["haxe"],"PosInfos"},_) -> false | _ -> true) -> ()
+						| _ -> apply_not_null_cond ctx v e.epos
+				) tf.tf_args;
+				let e' = loop ctx tf.tf_expr in
+				close (mk_join_node());
+				{e with eexpr = TFunction {tf with tf_expr = e'}}
+			(* var modifications *)
+			| TBinop(OpAssign,({eexpr = TLocal v} as e1),e2) when v.v_name <> "this" ->
+				let e2 = loop ctx e2 in
+				let _ = assign_var ctx v e2 e1.epos in
+				{e with eexpr = TBinop(OpAssign,e1,e2)}
+			| TBinop(OpAssignOp op,({eexpr = TLocal v} as e1),e2) ->
+				let e1 = loop ctx e1 in
+				let e_op = mk (TBinop(op,e1,e2)) e.etype e.epos in
+				let _ = assign_var ctx v e_op e1.epos in
+				let e2 = loop ctx e2 in
+				{e with eexpr = TBinop(OpAssignOp op,e1,e2)}
+			| TUnop((Increment | Decrement as op),flag,({eexpr = TLocal v} as e1)) ->
+				let op = match op with Increment -> OpAdd | Decrement -> OpSub | _ -> assert false in
+				let e_one = mk (TConst (TInt (Int32.of_int 1))) com.basic.tint e.epos in
+				let e1 = loop ctx e1 in
+				let e_op = mk (TBinop(op,e1,e_one)) e.etype e.epos in
+				let _ = assign_var ctx v e_op e1.epos in
+				e
+			(* var user *)
+			| TLocal v ->
+				let v = get_var ctx v e.epos in
+				{e with eexpr = TLocal v}
+			(* control flow *)
+			| TIf(econd,eif,eelse) ->
+				handle_if ctx e econd eif eelse
+			| TSwitch(e1,cases,edef) ->
+				let e1 = loop ctx e1 in
+				let join = mk_join_node() in
+				let cases = List.map (fun (el,e) ->
+					let close = branch ctx e.epos in
+					let el = List.map (loop ctx) el in
+					let e = loop ctx e in
+					close join;
+					el,e
+				) cases in
+				let edef = match edef with
+					| Some e ->
+						let close = branch ctx e.epos in
+						let e = loop ctx e in
+						close join;
+						Some e
+					| None ->
+						begin match e1.eexpr with
+							| TMeta((Meta.Exhaustive,_,_),_)
+							| TParenthesis({eexpr = TMeta((Meta.Exhaustive,_,_),_)}) ->
+								()
+							| _ ->
+								add_branch join ctx.cur_data e.epos;
+						end;
+						None
+				in
+				close_join_node ctx join e.epos;
+				let e = {e with eexpr = TSwitch(e1,cases,edef)} in
+				e
+			| TWhile(econd,ebody,mode) ->
+				let econd = loop ctx econd in
+				let ebody = handle_loop_body ctx ebody in
+				let e = {e with eexpr = TWhile(econd,ebody,mode)} in
+				e
+			| TFor(v,e1,ebody) ->
+				declare_var ctx v e.epos;
+				apply_not_null_cond ctx v e1.epos;
+				let v' = IntMap.find v.v_id ctx.cur_data.nd_var_map in
+				let e1 = loop ctx e1 in
+				let ebody = handle_loop_body ctx ebody in
+				let e = {e with eexpr = TFor(v',e1,ebody)} in
+				e
+			| TTry(e1,catches) ->
+				let join_ex = mk_join_node() in
+				let join_bottom = mk_join_node() in
+				let unset = set_exception_join ctx join_ex in
+				let e1 = loop ctx e1 in
+				unset();
+				add_branch join_bottom ctx.cur_data e.epos;
+				close_join_node ctx join_ex e.epos;
+				let catches = List.map (fun (v,e) ->
+					declare_var ctx v e.epos;
+					apply_not_null_cond ctx v e.epos;
+					let close = branch ctx e.epos in
+					let e = loop ctx e in
+					close join_bottom;
+					v,e
+				) catches in
+				close_join_node ctx join_bottom e.epos;
+				let e = {e with eexpr = TTry(e1,catches)} in
+				e
+			| TBreak ->
+				begin match ctx.loop_stack with
+					| [] -> error "Break outside loop" e.epos
+					| (_,join) :: _ -> add_branch join ctx.cur_data e.epos
+				end;
+				terminate ctx;
+				e
+			| TContinue ->
+				begin match ctx.loop_stack with
+					| [] -> error "Continue outside loop" e.epos
+					| (join,_) :: _ -> add_branch join ctx.cur_data e.epos
+				end;
+				terminate ctx;
+				e
+			| TThrow e1 ->
+				let e1 = loop ctx e1 in
+				begin match ctx.exception_stack with
+					| join :: _ -> add_branch join ctx.cur_data e.epos
+					| _ -> ()
+				end;
+				terminate ctx;
+				{e with eexpr = TThrow e1}
+			| TReturn eo ->
+				let eo = match eo with None -> None | Some e -> Some (loop ctx e) in
+				terminate ctx;
+				{e with eexpr = TReturn eo}
+			| TBlock el ->
+				let rec loop2 el = match el with
+					| [] ->
+						[]
+					| e :: el ->
+						if ctx.cur_data.nd_terminates then begin
+							ctx.com.warning (Printf.sprintf "Unreachable code: %s" (s_expr_pretty e)) e.epos;
+							[]
+						end else
+							let e = loop ctx e in
+							e :: (loop2 el)
+				in
+				{e with eexpr = TBlock(loop2 el)}
+			| _ ->
+				begin match ctx.exception_stack with
+					| join :: _ when can_throw e -> add_branch join ctx.cur_data e.epos
+					| _ -> ()
+				end;
+				Type.map_expr (loop ctx) e
+		in
+		let ctx = {
+			com = com;
+			cur_data = mk_node_data e.epos;
+			var_values = IntMap.empty;
+			var_conds = IntMap.empty;
+			loop_stack = [];
+			exception_stack = [];
+			cleanup = [];
+		} in
+		let e = loop ctx e in
+		e,ctx
+
+	let unapply com e =
+		let rec loop e = match e.eexpr with
+			| TFor(({v_extra = Some([],Some {eexpr = TLocal v'})} as v),e1,e2) when Meta.has (Meta.Custom ":ssa") v.v_meta ->
+				let e1 = loop e1 in
+				let e2 = loop e2 in
+				{e with eexpr = TFor(v',e1,e2)}
+			| TLocal ({v_extra = Some([],Some {eexpr = TLocal v'})} as v) when Meta.has (Meta.Custom ":ssa") v.v_meta ->
+				{e with eexpr = TLocal v'}
+			| TBlock el ->
+				let rec filter e = match e.eexpr with
+					| TMeta((Meta.Custom ":ssa",_,_),_) ->
+						false
+					| _ ->
+						true
+				in
+				let el = List.filter filter el in
+				let el = List.map loop el in
+				{e with eexpr = TBlock el}
+			| _ ->
+				Type.map_expr loop e
+		in
+		loop e
+end
+
+module ConstPropagation = struct
+	open Ssa
+
+	let expr_eq e1 e2 = match e1.eexpr,e2.eexpr with
+		| TConst ct1, TConst ct2 ->
+			ct1 = ct2
+		| _ ->
+			false
+
+	let rec local ssa v p =
+		if v.v_capture then raise Not_found;
+		if type_has_analyzer_option v.v_type flag_no_const_propagation then raise Not_found;
+		begin match follow v.v_type with
+			| TDynamic _ -> raise Not_found
+			| _ -> ()
+		end;
+		let e = IntMap.find v.v_id ssa.var_values in
+		let reset() =
+			ssa.var_values <- IntMap.add v.v_id e ssa.var_values;
+		in
+		ssa.var_values <- IntMap.remove v.v_id ssa.var_values;
+		let e = try value ssa e with Not_found -> reset(); raise Not_found in
+		reset();
+		e
+
+	and value ssa e = match e.eexpr with
+		| TUnop((Increment | Decrement),_,_)
+		| TBinop(OpAssignOp _,_,_)
+		| TBinop(OpAssign,_,_) ->
+			raise Not_found
+ 		| TBinop(op,e1,e2) ->
+			let e1 = value ssa e1 in
+			let e2 = value ssa e2 in
+			let e = {e with eexpr = TBinop(op,e1,e2)} in
+			let e' = Optimizer.optimize_binop e op e1 e2 in
+			if e == e' then
+				raise Not_found
+			else
+				value ssa e'
+		| TUnop(op,flag,e1) ->
+			let e1 = value ssa e1 in
+			let e = {e with eexpr = TUnop(op,flag,e1)} in
+			let e' = Optimizer.optimize_unop e op flag e1 in
+			if e == e' then
+				raise Not_found
+			else
+				value ssa e'
+		| TCall ({eexpr = TLocal {v_name = "__ssa_phi__"}},el) ->
+			let el = List.map (value ssa) el in
+			begin match el with
+				| [] -> assert false
+				| e1 :: el ->
+					if List.for_all (fun e2 -> expr_eq e1 e2) el then
+						value ssa e1
+					else
+						raise Not_found
+			end
+		| TConst ct ->
+			begin match ct with
+				| TThis | TSuper -> raise Not_found
+				(* Some targets don't like seeing null in certain places and won't even compile. We have to detect `if (x != null)
+				   in order for this to work. *)
+				| TNull when (match ssa.com.platform with Php | Cpp -> true | _ -> false) -> raise Not_found
+				| _ -> e
+			end
+		| TParenthesis e1 | TMeta(_,e1) ->
+			value ssa e1
+		| TLocal v ->
+			local ssa v e.epos
+		| _ ->
+			raise Not_found
+
+	let apply ssa e =
+		let had_function = ref false in
+		let rec loop e = match e.eexpr with
+			| TFunction _ when !had_function ->
+				e
+			| TFunction tf ->
+				had_function := true;
+				{e with eexpr = TFunction {tf with tf_expr = loop tf.tf_expr}}
+			| TLocal v ->
+				begin try local ssa v e.epos
+				with Not_found -> e end
+			| TCall({eexpr = TField(_,(FStatic(_,cf) | FInstance(_,_,cf) | FAnon cf))},el) when has_analyzer_option cf.cf_meta flag_no_const_propagation ->
+				e
+			| TCall(e1,el) ->
+				let e1 = loop e1 in
+				let check e t =
+					if type_has_analyzer_option t flag_no_const_propagation then e
+					else loop e
+				in
+				let el = Codegen.UnificationCallback.check_call check el e1.etype in
+				{e with eexpr = TCall(e1,el)}
+			| TUnop((Increment | Decrement),_,_)
+			| TBinop(OpAssignOp _,_,_) ->
+				e
+			| TBinop(OpAssign,({eexpr = TLocal _} as e1),e2) ->
+				let e2 = loop e2 in
+				{e with eexpr = TBinop(OpAssign,e1,e2)}
+			| TIf(e1,e2,eo) ->
+				let e1 = loop e1 in
+				let e2 = loop e2 in
+				begin match e1.eexpr with
+					| TConst (TBool true) ->
+						e2
+					| TConst (TBool false) ->
+						begin match eo with
+							| None ->
+								mk (TConst TNull) t_dynamic e.epos
+							| Some e ->
+								loop e
+						end
+					| _ ->
+						let eo = match eo with None -> None | Some e -> Some (loop e) in
+						{e with eexpr = TIf(e1,e2,eo)}
+				end;
+			| _ ->
+				Type.map_expr loop e
+		in
+		loop e
+end
+
+module Checker = struct
+	open Ssa
+
+	let apply ssa e =
+		let given_warnings = ref PMap.empty in
+		let add_pos p =
+			given_warnings := PMap.add p true !given_warnings
+		in
+		let resolve_value v =
+			let e' = IntMap.find v.v_id ssa.var_values in
+			begin match e'.eexpr with
+				| TLocal v' when v == v' -> e'
+				| _ -> e'
+			end
+		in
+		let rec is_null_expr e = match e.eexpr with
+			| TConst TNull ->
+				true
+			| TLocal v ->
+				(try is_null_expr (resolve_value v) with Not_found -> false)
+			| _ ->
+				false
+		in
+		let can_be_null v =
+			not (has_meta Meta.NotNull v.v_meta)
+			&& try not (List.exists (fun cond -> match cond with
+				| NotEqual(v',e) when v == v' && is_null_expr e -> true
+				| _ -> false
+			) (IntMap.find v.v_id ssa.var_conds)) with Not_found -> true
+		in
+		let return b p =
+			if b then add_pos p;
+			b
+		in
+		let rec can_be_null_expr vstack e =
+			if PMap.mem e.epos !given_warnings then
+				false
+			else match e.eexpr with
+			| TConst TNull ->
+				add_pos e.epos;
+				true
+			| TBinop((OpAssign | OpAssignOp _),_,e1) ->
+				can_be_null_expr vstack e1
+			| TBinop _ | TUnop _ ->
+				false
+			| TConst _ | TTypeExpr _ | TNew _ | TObjectDecl _ | TArrayDecl _ | TEnumParameter _ | TFunction _ | TVar _ ->
+				false
+			| TFor _ | TWhile _ | TIf _ | TSwitch _ | TTry _ | TReturn _ | TBreak | TContinue | TThrow _ ->
+				assert false
+			| TField _ | TBlock _ | TArray _ ->
+				false (* TODO *)
+			| TCall ({eexpr = TLocal {v_name = "__ssa_phi__"}},el) ->
+				List.exists (can_be_null_expr vstack) el
+			| TLocal v ->
+				if List.mem v.v_id vstack then
+					false (* not really, but let's not be a nuisance *)
+				else
+					return (can_be_null v && (try can_be_null_expr (v.v_id :: vstack) (resolve_value v) with Not_found -> true)) e.epos;
+			| TMeta(_,e1) | TParenthesis e1 | TCast(e1,_) ->
+				can_be_null_expr vstack e1
+			| TCall(e1,_) ->
+				begin match follow e1.etype with
+					| TFun(_,r) -> return (is_explicit_null r) e1.epos
+					| _ -> false
+				end
+		in
+		let check_null e p =
+			if can_be_null_expr [] e then begin
+				ssa.com.warning "Possible null exception" p;
+			end
+		in
+		let rec loop e = match e.eexpr with
+			| TField(e1,fa) ->
+				let e1 = loop e1 in
+				check_null e1 e.epos;
+				{e with eexpr = TField(e1,fa)}
+			| TMeta((Meta.Analyzer,[EConst(Ident "testIsNull"),_],_),e1) ->
+				if not (can_be_null_expr [] e) then error "Analyzer did not find a possible null exception" e.epos;
+				e
+			| TMeta((Meta.Analyzer,[EConst(Ident "testIsNotNull"),_],_),e1) ->
+				if (can_be_null_expr [] e) then error "Analyzer found a possible null exception" e.epos;
+				e
+			| _ ->
+				Type.map_expr loop e
+		in
+		loop e;
+end
+
+module LocalDce = struct
+	let apply com e =
+		let is_used v = Meta.has Meta.Used v.v_meta in
+		let use v = v.v_meta <- (Meta.Used,[],Ast.null_pos) :: v.v_meta in
+		let rec loop e = match e.eexpr with
+ 			| TLocal {v_extra = Some (_,Some {eexpr = TLocal v})} ->
+				use v;
+				e
+			| TVar(v,Some e1) when not (is_used v) ->
+				let e1 = loop e1 in
+				e1
+			| TBlock el ->
+				let rec block el = match el with
+					| e :: el ->
+						let el = block el in
+						if el <> [] && not (Optimizer.has_side_effect e) then
+							el
+						else begin
+							let e = loop e in
+							e :: el
+						end
+					| [] ->
+						[]
+				in
+				{e with eexpr = TBlock (block el)}
+			| _ ->
+				Type.map_expr loop e
+		in
+		loop e
+end
+
+type analyzer_config = {
+	analyzer_use : bool;
+	simplifier_apply : bool;
+	ssa_apply : bool;
+	const_propagation : bool;
+	check : bool;
+	ssa_unapply : bool;
+	simplifier_unapply : bool;
+}
+
+let run_ssa com config e =
+	let all_locals = ref PMap.empty in
+	let rec loop e = match e.eexpr with
+		| TVar(v,eo) ->
+			all_locals := PMap.add v.v_name true !all_locals;
+			begin match eo with None -> () | Some e -> loop e end
+		| _ ->
+			Type.iter loop e
+	in
+	loop e;
+	let n = ref (-1) in
+	let rec gen_local t =
+		incr n;
+		let name = "_" ^ (string_of_int !n) in
+		if PMap.mem name !all_locals then
+			gen_local t
+		else
+			alloc_var name t
+	in
+	let do_simplify = match com.platform with
+		| Cpp | Flash8 | Python -> true
+		| _ -> false
+	in
+	let with_timer s f =
+		let timer = timer s in
+		let r = f() in
+		timer();
+		r
+	in
+	try
+		let e = if do_simplify || config.analyzer_use then
+			with_timer "analyzer-simplify-apply" (fun () -> Simplifier.apply com gen_local e)
+		else
+			e
+		in
+		let e = if config.analyzer_use then
+				let e,ssa = with_timer "analyzer-ssa-apply" (fun () -> Ssa.apply com e) in
+				let e = if config.const_propagation then with_timer "analyzer-const-propagation" (fun () -> ConstPropagation.apply ssa e) else e in
+				(* let e = if config.check then with_timer "analyzer-checker" (fun () -> Checker.apply ssa e) else e in *)
+				let e = if config.ssa_unapply then with_timer "analyzer-ssa-unapply" (fun () -> Ssa.unapply com e) else e in
+				List.iter (fun f -> f()) ssa.Ssa.cleanup;
+				e
+		else
+			e
+		in
+		let e = if not do_simplify && not (Common.raw_defined com "analyzer-no-simplify-unapply") then
+			with_timer "analyzer-simplify-unapply" (fun () -> Simplifier.unapply e)
+		else
+			e
+		in
+		e
+	with Exit ->
+		e
+
+let update_config_from_meta 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 = flag_no_check -> { config with check = false}
+				| EConst (Ident s) when s = flag_check -> { config with check = true}
+				| EConst (Ident s) when s = flag_no_const_propagation -> { config with const_propagation = false}
+				| EConst (Ident s) when s = flag_const_propagation -> { config with const_propagation = true}
+				| _ -> config
+			) config el
+		| _ ->
+			config
+	) config meta
+
+let run_expression_filters com config t =
+	match t with
+	| TClassDecl c when (has_analyzer_option c.cl_meta flag_ignore) ->
+		()
+	| TClassDecl c ->
+		let config = update_config_from_meta config c.cl_meta in
+		let process_field cf =
+			match cf.cf_expr with
+			| Some e when not (has_analyzer_option cf.cf_meta flag_ignore) && not (Meta.has Meta.Extern cf.cf_meta) (* TODO: use is_removable_field *) ->
+				let config = update_config_from_meta config cf.cf_meta in
+				cf.cf_expr <- Some (run_ssa com config e);
+			| _ -> ()
+		in
+		List.iter process_field c.cl_ordered_fields;
+		List.iter process_field c.cl_ordered_statics;
+		(match c.cl_constructor with
+		| None -> ()
+		| Some f -> process_field f);
+ 		(match c.cl_init with
+		| None -> ()
+		| Some e ->
+			(* never optimize init expressions (too messy) *)
+			c.cl_init <- Some (run_ssa com {config with analyzer_use = false} e));
+	| TEnumDecl _ -> ()
+	| TTypeDecl _ -> ()
+	| TAbstractDecl _ -> ()
+
+let apply com =
+	let config = {
+		analyzer_use = true;
+		simplifier_apply = true;
+		ssa_apply = true;
+		const_propagation = not (Common.raw_defined com "analyzer-no-const-propagation");
+		check = not (Common.raw_defined com "analyzer-no-check");
+		ssa_unapply = not (Common.raw_defined com "analyzer-no-ssa-unapply");
+		simplifier_unapply = not (Common.raw_defined com "analyzer-no-simplify-unapply");
+	} in
+	List.iter (run_expression_filters com config) com.types

+ 1 - 0
ast.ml

@@ -32,6 +32,7 @@ module Meta = struct
 		| Access
 		| Access
 		| Accessor
 		| Accessor
 		| Allow
 		| Allow
+		| Analyzer
 		| Annotation
 		| Annotation
 		| ArrayAccess
 		| ArrayAccess
 		| Ast
 		| Ast

+ 3 - 0
common.ml

@@ -165,6 +165,7 @@ module Define = struct
 	type strict_defined =
 	type strict_defined =
 		| AbsolutePath
 		| AbsolutePath
 		| AdvancedTelemetry
 		| AdvancedTelemetry
+		| Analyzer
 		| As3
 		| As3
 		| CheckXmlProxy
 		| CheckXmlProxy
 		| CoreApi
 		| CoreApi
@@ -240,6 +241,7 @@ module Define = struct
 	let infos = function
 	let infos = function
 		| AbsolutePath -> ("absolute_path","Print absolute file path in trace output")
 		| AbsolutePath -> ("absolute_path","Print absolute file path in trace output")
 		| AdvancedTelemetry -> ("advanced-telemetry","Allow the SWF to be measured with Monocle tool")
 		| AdvancedTelemetry -> ("advanced-telemetry","Allow the SWF to be measured with Monocle tool")
+		| Analyzer -> ("analyzer","Use static analyzer for optimization (experimental)")
 		| As3 -> ("as3","Defined when outputing flash9 as3 source code")
 		| As3 -> ("as3","Defined when outputing flash9 as3 source code")
 		| CheckXmlProxy -> ("check_xml_proxy","Check the used fields of the xml proxy")
 		| CheckXmlProxy -> ("check_xml_proxy","Check the used fields of the xml proxy")
 		| CoreApi -> ("core_api","Defined in the core api context")
 		| CoreApi -> ("core_api","Defined in the core api context")
@@ -338,6 +340,7 @@ module MetaInfo = struct
 		| Access -> ":access",("Forces private access to package, type or field",[HasParam "Target path";UsedOnEither [TClass;TClassField]])
 		| Access -> ":access",("Forces private access to package, type or field",[HasParam "Target path";UsedOnEither [TClass;TClassField]])
 		| Accessor -> ":accessor",("Used internally by DCE to mark property accessors",[UsedOn TClassField;Internal])
 		| Accessor -> ":accessor",("Used internally by DCE to mark property accessors",[UsedOn TClassField;Internal])
 		| Allow -> ":allow",("Allows private access from package, type or field",[HasParam "Target path";UsedOnEither [TClass;TClassField]])
 		| Allow -> ":allow",("Allows private access from package, type or field",[HasParam "Target path";UsedOnEither [TClass;TClassField]])
+		| Analyzer -> ":analyzer",("Used to configure the static analyzer",[])
 		| Annotation -> ":annotation",("Annotation (@interface) definitions on -java-lib imports will be annotated with this metadata. Has no effect on types compiled by Haxe",[Platform Java; UsedOn TClass])
 		| Annotation -> ":annotation",("Annotation (@interface) definitions on -java-lib imports will be annotated with this metadata. Has no effect on types compiled by Haxe",[Platform Java; UsedOn TClass])
 		| ArrayAccess -> ":arrayAccess",("Allows [] access on an abstract",[UsedOnEither [TAbstract;TAbstractField]])
 		| ArrayAccess -> ":arrayAccess",("Allows [] access on an abstract",[UsedOnEither [TAbstract;TAbstractField]])
 		| Ast -> ":ast",("Internally used to pass the AST source into the typed AST",[Internal])
 		| Ast -> ":ast",("Internally used to pass the AST source into the typed AST",[Internal])

+ 69 - 169
filters.ml

@@ -38,143 +38,6 @@ let rec blockify_ast e =
 	| _ ->
 	| _ ->
 		Type.map_expr blockify_ast e
 		Type.map_expr blockify_ast e
 
 
-(*
-	Generates a block context which can be used to add temporary variables. It returns a tuple:
-
-	- a mapping function for expression lists to be used on TBlock elements
-	- the function to be called for declaring temporary variables
-	- the function to be called for closing the block, returning the block elements
-*)
-let mk_block_context com gen_temp =
-	let block_el = ref [] in
-	let push e = block_el := e :: !block_el in
-	let declare_temp t eo p =
-		let v = gen_temp t in
-		let e = mk (TVar (v,eo)) com.basic.tvoid p in
-		push e;
-		mk (TLocal v) t p
-	in
-	let push_block () =
-		let cur = !block_el in
-		block_el := [];
-		fun () ->
-			let added = !block_el in
-			block_el := cur;
-			List.rev added
-	in
-	let rec block f el =
-		let close = push_block() in
-		List.iter (fun e ->
-			push (f e)
-		) el;
-		close()
-	in
-	block,declare_temp,fun () -> !block_el
-
-(*
-	Moves expressions to temporary variables in order to ensure correct evaluation order. This effects
-
-	- call arguments (from TCall and TNew)
-	- array declaration arguments
-	- object fields
-	- binary operators (respects boolean short-circuit)
-	- array access
-*)
-let handle_side_effects com gen_temp e =
-	let block,declare_temp,close_block = mk_block_context com gen_temp in
-	let rec loop e =
-		match e.eexpr with
-		| TBlock el ->
-			{e with eexpr = TBlock (block loop el)}
-		| TCall({eexpr = TLocal v},_) when Meta.has Meta.Unbound v.v_meta ->
-			e
-		| TCall(e1,el) ->
-			let e1 = loop e1 in
-			{e with eexpr = TCall(e1,ordered_list el)}
-		| TNew(c,tl,el) ->
-			{e with eexpr = TNew(c,tl,ordered_list el)}
-		| TArrayDecl el ->
-			{e with eexpr = TArrayDecl (ordered_list el)}
-		| TObjectDecl fl ->
-			let el = ordered_list (List.map snd fl) in
-			{e with eexpr = TObjectDecl (List.map2 (fun (n,_) e -> n,e) fl el)}
-		| TBinop(OpBoolAnd | OpBoolOr as op,e1,e2) when Optimizer.has_side_effect e1 || Optimizer.has_side_effect e2 ->
-			let e1 = loop e1 in
-			let e_then = mk (TBlock (block loop [e2])) e2.etype e2.epos 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
-			mk (TIf(e_if,e_then,Some e_else)) com.basic.tbool e.epos
-		| TBinop((OpAssign | OpAssignOp _) as op,{eexpr = TArray(e11,e12)},e2) ->
-			let e1 = match ordered_list [e11;e12] with
-				| [e1;e2] ->
-					{e with eexpr = TArray(e1,e2)}
-				| _ ->
-					assert false
-			in
-			let e2 = loop e2 in
-			{e with eexpr = TBinop(op,e1,e2)}
-		| TBinop((OpAssign | OpAssignOp _) as op,e1,e2) ->
-			let e1 = loop e1 in
-			let e2 = loop e2 in
-			{e with eexpr = TBinop(op,e1,e2)}
- 		| TBinop(op,e1,e2) ->
-			begin match ordered_list [e1;e2] with
-				| [e1;e2] ->
-					{e with eexpr = TBinop(op,e1,e2)}
-				| _ ->
-					assert false
-			end
-		| TArray(e1,e2) ->
-			begin match ordered_list [e1;e2] with
-				| [e1;e2] ->
-					{e with eexpr = TArray(e1,e2)}
-				| _ ->
-					assert false
-			end
-		| TWhile(e1,e2,flag) when (match e1.eexpr with TParenthesis {eexpr = TConst(TBool true)} -> false | _ -> true) ->
-			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 ->
-					(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
-		| _ ->
-			Type.map_expr loop e
-	and ordered_list el =
-		match el with
-			| [e] ->
-				el
-			| _ ->
-				let bind e =
-					declare_temp e.etype (Some (loop e)) e.epos
-				in
-				if (List.exists Optimizer.has_side_effect) el then
-					List.map bind el
-				else
-					el
-	in
-	let e = loop e in
-	match close_block() with
-		| [] ->
-			e
-		| el ->
-			mk (TBlock (List.rev (e :: el))) e.etype e.epos
-
 (*
 (*
 	Pushes complex right-hand side expression inwards.
 	Pushes complex right-hand side expression inwards.
 
 
@@ -761,6 +624,13 @@ let check_unification com e t =
 		| _ ->
 		| _ ->
 			()
 			()
 	end;
 	end;
+	begin match e.eexpr,t with
+		| TLocal v,TType({t_path = ["cs"],("Ref" | "Out")},_) ->
+			(* TODO: this smells of hack, but we have to deal with it somehow *)
+			v.v_capture <- true
+		| _ ->
+			()
+	end;
 	e
 	e
 
 
 (* PASS 1 end *)
 (* PASS 1 end *)
@@ -1080,38 +950,68 @@ let run com tctx main =
 	end;
 	end;
 	if not (Common.defined com Define.NoDeprecationWarnings) then
 	if not (Common.defined com Define.NoDeprecationWarnings) then
 		Codegen.DeprecationCheck.run com;
 		Codegen.DeprecationCheck.run com;
-	(* PASS 1: general expression filters *)
- 	let filters = [
- 		Codegen.UnificationCallback.run (check_unification com);
-		Codegen.AbstractCast.handle_abstract_casts tctx;
-		blockify_ast;
-		(match com.platform with
-			| Cpp | Flash8 -> (fun e ->
-				let save = save_locals tctx in
-				let e = handle_side_effects com (Typecore.gen_local tctx) e in
-				save();
-				e)
-			| _ -> fun e -> e);
-		if com.foptimize then (fun e -> Optimizer.reduce_expression tctx (Optimizer.inline_constructors tctx e)) else Optimizer.sanitize com;
-		check_local_vars_init;
-		captured_vars com;
-	] in
-	List.iter (post_process tctx filters) com.types;
-	post_process_end();
-	List.iter (fun f -> f()) (List.rev com.filters);
-	(* save class state *)
-	List.iter (save_class_state tctx) com.types;
-	(* PASS 2: destructive type and expression filters *)
-	let filters = [
-		promote_complex_rhs com;
-		if com.config.pf_add_final_return then add_final_return else (fun e -> e);
-		rename_local_vars com;
-	] in
-	List.iter (fun t ->
-		remove_generic_base tctx t;
-		remove_extern_fields tctx t;
-		run_expression_filters tctx filters t;
-	) com.types;
+	let use_static_analyzer = Common.defined com Define.Analyzer in
+	(* this part will be a bit messy until we make the analyzer the default *)
+	if use_static_analyzer then begin
+		(* PASS 1: general expression filters *)
+	 	let filters = [
+	 		Codegen.UnificationCallback.run (check_unification com);
+			Codegen.AbstractCast.handle_abstract_casts tctx;
+			blockify_ast;
+			Optimizer.inline_constructors tctx;
+			captured_vars com;
+		] in
+		List.iter (post_process tctx filters) com.types;
+		Analyzer.apply com;
+		post_process_end();
+		List.iter (fun f -> f()) (List.rev com.filters);
+		(* save class state *)
+		List.iter (save_class_state tctx) com.types;
+		(* PASS 2: destructive type and expression filters *)
+		let filters = [
+			Optimizer.sanitize com;
+			if com.config.pf_add_final_return then add_final_return else (fun e -> e);
+			rename_local_vars com;
+		] in
+		List.iter (fun t ->
+			remove_generic_base tctx t;
+			remove_extern_fields tctx t;
+			run_expression_filters tctx filters t;
+		) com.types;
+	end else begin
+		(* PASS 1: general expression filters *)
+	 	let filters = [
+	 		Codegen.UnificationCallback.run (check_unification com);
+			Codegen.AbstractCast.handle_abstract_casts tctx;
+			blockify_ast;
+			(match com.platform with
+				| Cpp | Flash8 -> (fun e ->
+					let save = save_locals tctx in
+					let e = try Analyzer.Simplifier.apply com (Typecore.gen_local tctx) e with Exit -> e in
+					save();
+					e)
+				| _ -> fun e -> e);
+			if com.foptimize then (fun e -> Optimizer.reduce_expression tctx (Optimizer.inline_constructors tctx e)) else Optimizer.sanitize com;
+			check_local_vars_init;
+			captured_vars com;
+		] in
+		List.iter (post_process tctx filters) com.types;
+		post_process_end();
+		List.iter (fun f -> f()) (List.rev com.filters);
+		(* save class state *)
+		List.iter (save_class_state tctx) com.types;
+		(* PASS 2: destructive type and expression filters *)
+		let filters = [
+			promote_complex_rhs com;
+			if com.config.pf_add_final_return then add_final_return else (fun e -> e);
+			rename_local_vars com;
+		] in
+		List.iter (fun t ->
+			remove_generic_base tctx t;
+			remove_extern_fields tctx t;
+			run_expression_filters tctx filters t;
+		) com.types;
+	end;
 	(* update cache dependencies before DCE is run *)
 	(* update cache dependencies before DCE is run *)
 	Codegen.update_cache_dependencies com;
 	Codegen.update_cache_dependencies com;
 	(* check @:remove metadata before DCE so it is ignored there (issue #2923) *)
 	(* check @:remove metadata before DCE so it is ignored there (issue #2923) *)

+ 5 - 1
std/UInt.hx

@@ -25,7 +25,11 @@
 	The unsigned Int type is only defined for Flash9 and C#. It's currently
 	The unsigned Int type is only defined for Flash9 and C#. It's currently
 	handled the same as a normal Int.
 	handled the same as a normal Int.
 **/
 **/
-@:coreType @:notNull @:runtimeValue abstract UInt to Int from Int { }
+@:coreType
+@:notNull
+@:runtimeValue
+@:analyzer(no_const_propagation)
+abstract UInt to Int from Int { }
 #else
 #else
 /**
 /**
 	The unsigned Int type is only defined for Flash9 and C#.
 	The unsigned Int type is only defined for Flash9 and C#.

+ 3 - 3
std/cpp/_std/Xml.hx

@@ -43,7 +43,7 @@ enum XmlType {
 
 
 	private static var _parse = cpp.Lib.load("std","parse_xml",2);
 	private static var _parse = cpp.Lib.load("std","parse_xml",2);
 
 
-	public static function parse( str : String ) : Xml {
+	@:analyzer(ignore) public static function parse( str : String ) : Xml {
 		var x = new Xml();
 		var x = new Xml();
 		x._children = new Array();
 		x._children = new Array();
 		var parser = {
 		var parser = {
@@ -235,7 +235,7 @@ enum XmlType {
 	}
 	}
 
 
 
 
-	public function elements(): Iterator<Xml> {
+	@:analyzer(ignore) public function elements(): Iterator<Xml> {
 		if( _children == null )
 		if( _children == null )
 			throw "bad nodetype";
 			throw "bad nodetype";
       var children = _children;
       var children = _children;
@@ -268,7 +268,7 @@ enum XmlType {
 		}
 		}
 	}
 	}
 
 
-	public function elementsNamed( name : String ) : Iterator<Xml> {
+	@:analyzer(ignore) public function elementsNamed( name : String ) : Iterator<Xml> {
 		if( _children == null )
 		if( _children == null )
 			throw "bad nodetype";
 			throw "bad nodetype";
       var children = _children;
       var children = _children;

+ 1 - 0
std/cs/Out.hx

@@ -27,4 +27,5 @@ package cs;
 
 
 	Note: Using this type should be considered a bad practice unless overriding a native function is needed.
 	Note: Using this type should be considered a bad practice unless overriding a native function is needed.
 **/
 **/
+@:analyzer(no_simplification)
 typedef Out<T> = T;
 typedef Out<T> = T;

+ 1 - 0
std/cs/Ref.hx

@@ -27,4 +27,5 @@ package cs;
 
 
 	Note: Using this type should be considered a bad practice unless overriding a native function is needed.
 	Note: Using this type should be considered a bad practice unless overriding a native function is needed.
 **/
 **/
+@:analyzer(no_simplification)
 typedef Ref<T> = T;
 typedef Ref<T> = T;

+ 1 - 1
std/flash/_std/haxe/ds/IntMap.hx

@@ -51,7 +51,7 @@ package haxe.ds;
 		return untyped (__keys__(h)).iterator();
 		return untyped (__keys__(h)).iterator();
 	}
 	}
 
 
-	public function iterator() : Iterator<T> {
+	@:analyzer(ignore) public function iterator() : Iterator<T> {
 		return untyped {
 		return untyped {
 			ref : h,
 			ref : h,
 			it : keys(),
 			it : keys(),

+ 1 - 1
std/haxe/rtti/Rtti.hx

@@ -36,7 +36,7 @@ class Rtti {
 
 
 		If `c` is null, the result is unspecified.
 		If `c` is null, the result is unspecified.
 	**/
 	**/
-	static public function getRtti<T>(c:Class<T>):Null<Classdef> {
+	static public function getRtti<T>(c:Class<T>):Classdef {
 		var rtti = Reflect.field(c, "__rtti");
 		var rtti = Reflect.field(c, "__rtti");
 		var x = Xml.parse(rtti).firstElement();
 		var x = Xml.parse(rtti).firstElement();
 		var infos = new haxe.rtti.XmlParser().processElement(x);
 		var infos = new haxe.rtti.XmlParser().processElement(x);

+ 1 - 0
std/java/_std/haxe/ds/WeakMap.hx

@@ -66,6 +66,7 @@ import java.lang.ref.ReferenceQueue;
 		queue = new ReferenceQueue();
 		queue = new ReferenceQueue();
 	}
 	}
 
 
+	@:analyzer(ignore)
 	private function cleanupRefs():Void
 	private function cleanupRefs():Void
 	{
 	{
 		var x:Dynamic = null, nOccupied = nOccupied;
 		var x:Dynamic = null, nOccupied = nOccupied;

+ 2 - 0
std/php/_std/haxe/ds/IntMap.hx

@@ -22,7 +22,9 @@
 package haxe.ds;
 package haxe.ds;
 
 
 @:coreApi class IntMap<T> implements php.IteratorAggregate<T> implements haxe.Constraints.IMap<Int,T> {
 @:coreApi class IntMap<T> implements php.IteratorAggregate<T> implements haxe.Constraints.IMap<Int,T> {
+	@:analyzer(no_simplification)
 	private var h : ArrayAccess<Int>;
 	private var h : ArrayAccess<Int>;
+
 	public function new() : Void {
 	public function new() : Void {
 		h = untyped __call__('array');
 		h = untyped __call__('array');
 	}
 	}

+ 2 - 0
std/php/_std/haxe/ds/ObjectMap.hx

@@ -28,7 +28,9 @@ class ObjectMap <K:{ }, V> implements haxe.Constraints.IMap<K,V> {
 		return untyped __php__("spl_object_hash($key)");
 		return untyped __php__("spl_object_hash($key)");
 	}
 	}
 
 
+	@:analyzer(no_simplification)
 	var h : ArrayAccess<V>;
 	var h : ArrayAccess<V>;
+	@:analyzer(no_simplification)
 	var hk : ArrayAccess<K>;
 	var hk : ArrayAccess<K>;
 
 
 	public function new():Void {
 	public function new():Void {

+ 1 - 0
std/php/_std/haxe/ds/StringMap.hx

@@ -22,6 +22,7 @@
 package haxe.ds;
 package haxe.ds;
 
 
 @:coreApi class StringMap<T> implements php.IteratorAggregate<T> implements haxe.Constraints.IMap<String,T> {
 @:coreApi class StringMap<T> implements php.IteratorAggregate<T> implements haxe.Constraints.IMap<String,T> {
+	@:analyzer(no_simplification)
 	private var h : ArrayAccess<T>;
 	private var h : ArrayAccess<T>;
 
 
 	public function new() : Void {
 	public function new() : Void {

+ 1 - 0
std/python/Syntax.hx

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

+ 1 - 0
std/python/VarArgs.hx

@@ -3,6 +3,7 @@ package python;
 
 
 import python.lib.Builtin;
 import python.lib.Builtin;
 
 
+@:analyzer(no_simplification)
 abstract VarArgs (Dynamic)
 abstract VarArgs (Dynamic)
 {
 {
 	inline function new (d:Array<Dynamic>) this = d;
 	inline function new (d:Array<Dynamic>) this = d;

+ 11 - 0
tests/optimization/run.hxml

@@ -1,4 +1,15 @@
 -cp src
 -cp src
+-D analyzer
+--each
+
+-main TestAnalyzer
+--interp
+
+--next
+-main TestNullChecker
+--interp
+
+--next
 -js testopt.js
 -js testopt.js
 --macro Macro.register('Test')
 --macro Macro.register('Test')
 --macro Macro.register('TestJs')
 --macro Macro.register('TestJs')

+ 7 - 7
tests/optimization/src/Test.hx

@@ -33,10 +33,10 @@ class Test {
 	@:js('
 	@:js('
 		var c_x = 12;
 		var c_x = 12;
 		var c_y = "foo";
 		var c_y = "foo";
-		var x = c_x;
+		var x = 12;
 		c_x = 13;
 		c_x = 13;
-		x = c_x;
-		var y = c_y;
+		x = 13;
+		var y = "foo";
 	')
 	')
 	static function testInlineCtor1() {
 	static function testInlineCtor1() {
 		var c = new InlineCtor(12, "foo");
 		var c = new InlineCtor(12, "foo");
@@ -52,7 +52,7 @@ class Test {
 		a = 2;
 		a = 2;
 		var c_x = 12;
 		var c_x = 12;
 		var c_y = "foo";
 		var c_y = "foo";
-		a = c_x;
+		a = 12;
 	')
 	')
 	static function testInlineCtor2() {
 	static function testInlineCtor2() {
 		var a = 0;
 		var a = 0;
@@ -71,7 +71,7 @@ class Test {
 		a = 1;
 		a = 1;
 		var b_x = 2;
 		var b_x = 2;
 		var b_y = "b";
 		var b_y = "b";
-		b_x = a;
+		b_x = 1;
 	')
 	')
 	static function testInlineCtor3() {
 	static function testInlineCtor3() {
 		var a = 0;
 		var a = 0;
@@ -86,8 +86,8 @@ class Test {
 	@:js('
 	@:js('
 		var x_foo = 1;
 		var x_foo = 1;
 		var x_bar = 2;
 		var x_bar = 2;
-		var y = x_foo;
-		var z = x_bar;
+		var y = 1;
+		var z = 2;
 	')
 	')
 	static function testStructureInline1() {
 	static function testStructureInline1() {
 		var x = {
 		var x = {

+ 682 - 0
tests/optimization/src/TestAnalyzer.hx

@@ -0,0 +1,682 @@
+class TestAnalyzer extends TestBase {
+
+	static function main() {
+		new TestAnalyzer();
+	}
+
+	public function new() {
+		super();
+		TestBaseMacro.run();
+	}
+
+	var buf = "";
+
+	inline function append(s:String) {
+		buf += s;
+	}
+
+	override function setup() {
+		buf = "";
+	}
+
+	function test1() {
+		var test = {
+			append("1");
+			append("2");
+			var k = {
+				append("3");
+				99;
+			}
+			k;
+		}
+		test = {
+			append("4");
+			3;
+		}
+		assertEquals("1234", buf.toString());
+	}
+
+	function test2() {
+		var a = if (cond1()) {
+			append("1");
+			if (cond2(getInt())) {
+				append("2");
+			} else {
+				append("3");
+			}
+			buf.toString();
+		} else {
+			append("4");
+			buf.toString();
+		}
+		assertEquals("cond11getIntcond23", a);
+	}
+
+	function test3() {
+		var a = switch(getInt()) {
+			case 1:
+				1;
+			case _:
+				2;
+		}
+		assertEquals(1, a);
+		assertEquals("getInt", buf.toString());
+	}
+
+	function test4() {
+		var a = if (if(cond1()) cond2(0) else false) {
+			1;
+		} else {
+			2;
+		}
+		assertEquals(1, a);
+		assertEquals("cond1cond2", buf.toString());
+	}
+
+	function testCond1() {
+		var a;
+		if (cond1()) {
+			a = 1;
+		} else {
+			a = 2;
+		}
+		assertEquals(1, a);
+	}
+
+	function testCond2() {
+		var a;
+		if (cond1()) {
+			a = 1;
+		} else {
+			a = 1;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	//function testUnop() {
+		//var a = 0;
+		//assertEqualsConst(0, a++);
+		//assertEqualsConst(1, a);
+		//assertEqualsConst(2, ++a);
+		//assertEqualsConst(2, a);
+		//if (cond1()) {
+			//a++;
+		//}
+		//assertEquals(3, a);
+	//}
+
+	//function testMultiAssign() {
+		//var a;
+		//var b;
+		//var c = a = b = 1;
+		//assertEqualsConst(1, a);
+		//assertEqualsConst(1, b);
+		//assertEqualsConst(1, c);
+		//c = a = b = 2;
+		//assertEqualsConst(2, a);
+		//assertEqualsConst(2, b);
+		//assertEqualsConst(2, c);
+	//}
+
+	function testConst1() {
+		var a = 1;
+		a = 2;
+		var b = a;
+		assertEqualsConst(2, b);
+		//b++;
+		//assertEqualsConst(3, b);
+	}
+
+	function testConst2() {
+		var a = 1;
+		if (cond1()) {
+			a = 2;
+		} else {
+			a = 3;
+		}
+		assertEquals(2, a);
+	}
+
+	function testConst3() {
+		var a = 1;
+		if (cond1()) {
+			a = 2;
+		} else {
+			a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testConst4() {
+		var a = 1;
+		if (cond1()) {
+			a = 2;
+		}
+		assertEquals(2, a);
+	}
+
+	function testConst5() {
+		var a = 2;
+		if (cond1()) {
+			a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testConst6() {
+		var a = 2;
+		if (cond1()) {
+			a = 2;
+		} else {
+
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testConst7() {
+		var a = 2;
+		if (cond1()) {
+
+		} else {
+			a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testConst8() {
+		var a = 1;
+		if (cond1()) {
+			a = 2;
+		} else {
+
+		}
+		assertEquals(2, a);
+	}
+
+	function testConst9() {
+		var a = 1;
+		var b = 2;
+		if (cond1()) {
+
+		} else {
+			a = 2;
+			b = 3;
+		}
+		assertEquals(1, a);
+		assertEquals(2, b);
+	}
+
+	function testConst10() {
+		var a = 1;
+		var b = 2;
+		if (cond2(1)) {
+			a = 1;
+		} else if (cond1()) {
+			a = 1;
+			b = 3;
+		} else {
+			a = 1;
+		}
+		assertEqualsConst(1, a);
+		assertEquals(3, b);
+	}
+
+	function testConst11() {
+		var a = 1;
+		if (cond2(0)) {
+			if (cond2(0)) {
+				a = 2;
+			} else {
+				assertEqualsConst(1, a);
+			}
+		}
+		assertEquals(2, a);
+	}
+
+	function testSwitch1() {
+		var a = 1;
+		switch (getBool()) {
+			case true: a = 1;
+			case false:
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testSwitch2() {
+		var a = 1;
+		switch (getBool()) {
+			case true:
+			case false: a = 2;
+		}
+		assertEquals(1, a);
+	}
+
+	function testSwitch3() {
+		var a = 1;
+		switch (getBool()) {
+			case true: a = 2;
+			case false: a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testSwitch4() {
+		var a = 1;
+		switch (getInt()) {
+			case 1: a = 1;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testSwitch5() {
+		var a = 1;
+		switch (getInt()) {
+			case 1: a = 1;
+			case 2: a = 1;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testSwitch6() {
+		var a = 1;
+		switch (getInt()) {
+			case 1: a = 1;
+			case 2: a = 2;
+		}
+		assertEquals(1, a);
+	}
+
+	function testSwitch7() {
+		var a = 1;
+		switch (getInt()) {
+			case 1: a = 2;
+			default: a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testComplex1() {
+		var a = 1;
+		if (cond1()) {
+			if (cond2(0)) {
+				assertEqualsConst(1, a);
+				a = 2;
+				assertEqualsConst(2, a);
+			} else {
+				switch (getBool()) {
+					case true:
+						a = 2;
+					case false:
+						if (getInt() == -1) {
+							a = 2;
+						}
+						assertEquals(1, a);
+						a = 2;
+				}
+			}
+			assertEqualsConst(2, a);
+		}
+		assertEquals(2, a);
+	}
+
+	function testLoop1() {
+		var a = 1;
+		var r = 0;
+		while (true) {
+			r = a;
+			if (a > 1) {
+				break;
+			}
+			a = 2;
+		}
+		assertEquals(2, a);
+		assertEquals(2, r);
+	}
+
+	function testLoop2() {
+		var a = 1;
+		while (true) {
+			a = 1;
+			if (a == 1) {
+				break;
+			}
+			a = 1;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testLoop3() {
+		var a = 1;
+		while (cond1()) {
+			a = 2;
+			if (getInt() == 1) {
+				break;
+			}
+			a = 1;
+		}
+		assertEquals(2, a);
+	}
+
+	function testLoop4() {
+		var a = 1;
+		while (true) {
+			a = 2;
+			a = 1;
+			if (getInt() == 1) {
+				break;
+			}
+			a = 1;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testLoop5() {
+		var a = 1;
+		while (true) {
+			assertEqualsConst(1, a);
+			a = 2;
+			if (getInt() == 1) {
+				break;
+			}
+			a = 1;
+		}
+	}
+
+	function testLoop6() {
+		var a = 1;
+		while (true) {
+			assertEquals(1, a);
+			a = 2;
+			if (getInt() != 1) {
+				continue;
+			} else {
+				break;
+			}
+		}
+	}
+
+	function testLoop7() {
+		var a = 1;
+		while (cond1()) {
+			a = 2;
+			assertEqualsConst(2, a);
+			while (true) {
+				a = 3;
+				assertEqualsConst(3, a);
+				a = 2;
+				break;
+			}
+			assertEqualsConst(2, a);
+			break;
+		}
+		assertEquals(2, a);
+	}
+
+	function testLoop8() {
+		var a = 1;
+		while (true) {
+			assertEquals(1, a);
+			if (getBool()) {
+				break;
+			}
+			a = 2;
+		}
+		assertEquals(1, a);
+	}
+
+	function testTry1() {
+		var a = 1;
+		try {
+
+		} catch(e:Dynamic) {
+			assertEqualsConst(1, a);
+			a = 2;
+			assertEqualsConst(2, a);
+		}
+		// we do not check for unreachable catch cases at the moment
+		assertEquals(1, a);
+	}
+
+	function testTry2() {
+		var a = 1;
+		try {
+			assertEqualsConst(1, a);
+			throw true;
+		} catch(e:Dynamic) {
+			assertEqualsConst(1, a);
+			a = 2;
+			assertEqualsConst(2, a);
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testTry3() {
+		var a = 1;
+		try {
+			assertEqualsConst(1, a);
+			throws();
+			assertEqualsConst(1, a);
+		} catch(e:Dynamic) {
+			assertEqualsConst(1, a);
+			a = 2;
+			assertEqualsConst(2, a);
+		}
+		assertEquals(2, a);
+	}
+
+	function testTry4() {
+		var a = 1;
+		try {
+			assertEqualsConst(1, a);
+			a = 2;
+			assertEqualsConst(2, a);
+			throws();
+			assertEqualsConst(2, a);
+			a = 1;
+			assertEqualsConst(1, a);
+		} catch(e:Dynamic) {
+			a = 1;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testTry5() {
+		var a = 1;
+		try {
+			throws();
+			a = 2;
+			a = 1;
+		} catch (e:Dynamic) {
+
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testTry6() {
+		var a = 1;
+		try {
+			a = 2;
+			throws();
+			a = 1;
+			a = 2;
+		} catch (e:String) {
+
+		} catch (e:Dynamic) {
+
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testTry7() {
+		var a = 1;
+		try {
+			throws();
+			a = 2;
+		} catch (e:String) {
+			a = 2;
+		} catch (e:Dynamic) {
+			a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testTry8() {
+		var a = 1;
+		try {
+			if (cond1()) {
+				a = 2;
+				throw "out";
+			}
+		} catch(e:Dynamic) {
+
+		}
+		assertEquals(2, a);
+	}
+
+	function testTry9() {
+		var a = 1;
+		try {
+			throw "foo";
+		} catch(e:String) {
+			a = 2;
+		} catch(e:Dynamic) {
+			a = 2;
+		}
+		assertEqualsConst(2, a);
+	}
+
+	function testTry10() {
+		var a = 1;
+		try {
+			if (cond1()) {
+				throw "foo";
+			}
+		} catch(e:String) {
+			a = 2;
+		} catch(e:Dynamic) {
+			a = 2;
+		}
+		assertEquals(2, a);
+	}
+
+	function testTry11() {
+		var a = 1;
+		try {
+			if (cond1()) {
+				throw "foo";
+			}
+		} catch(e:String) {
+
+		} catch(e:Dynamic) {
+			a = 2;
+		}
+		assertEquals(1, a);
+	}
+
+	function testThrow1() {
+		var a = 1;
+		if (!cond1()) {
+			a = 2;
+			throw true;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testThrow2() {
+		var a = 1;
+		if (!cond1()) {
+			a = 2;
+			if (!cond1()) {
+				throw true;
+			}
+		}
+		assertEquals(1, a);
+	}
+
+	function testReturn1() {
+		var a = 1;
+		if (!cond1()) {
+			a = 2;
+			return;
+		}
+		assertEqualsConst(1, a);
+	}
+
+	function testBreak1() {
+		var a = 1;
+		while (cond1()) {
+			if (cond1()) {
+				a = 2;
+				break;
+			}
+			assertEqualsConst(1, a);
+		}
+		assertEquals(2, a);
+	}
+
+	function testContinue1() {
+		var a = 1;
+		while (true) {
+			if (a == 2) {
+				break;
+			}
+			if (cond1()) {
+				a = 2;
+				continue;
+			}
+			assertEquals(2, a);
+		}
+		assertEquals(2, a);
+	}
+
+	function testContinue2() {
+		var a = 1;
+		while (true) {
+			if (a == 4) {
+				break;
+			}
+			if (a > 3) {
+				a = 4;
+				continue;
+			}
+			++a;
+		}
+		assertEquals(4, a);
+	}
+
+	//function testMisc() {
+		//var a = 1;
+		//function call(a, b, c) { return a + b + c; }
+		//assertEquals(5, call(assertEqualsConst(1, a), assertEqualsConst(2, a = a + 1), assertEqualsConst(2, a)));
+		//assertEquals(22, call(assertEqualsConst(2, a++), assertEqualsConst(4, ++a), assertEqualsConst(16, a *= a)));
+		//assertEquals(50, call(a, a = a + 1, a));
+	//}
+
+	function cond1() {
+		append("cond1");
+		return true;
+	}
+
+	function cond2(i) {
+		append("cond2");
+		return i == 0;
+	}
+
+	function getInt() {
+		append("getInt");
+		return 1;
+	}
+
+	function getBool() {
+		return true;
+	}
+
+	function throws() {
+		throw true;
+	}
+
+	function assertEqualsConst<T>(expected:T, actual:T, ?p) {
+		assertEquals(expected, actual, p);
+		return actual;
+	}
+}

+ 22 - 0
tests/optimization/src/TestBase.hx

@@ -0,0 +1,22 @@
+package ;
+
+class TestBase {
+
+	var numTests:Int;
+	var numFailures:Int;
+
+	function new() {
+		numTests = 0;
+		numFailures = 0;
+	}
+
+	function assertEquals<T>(expected:T, actual:T, ?p:haxe.PosInfos) {
+		++numTests;
+		if (expected != actual) {
+			++numFailures;
+			haxe.Log.trace('$actual should be $expected', p);
+		}
+	}
+
+	function setup() { }
+}

+ 80 - 0
tests/optimization/src/TestBaseMacro.hx

@@ -0,0 +1,80 @@
+import haxe.macro.Context;
+import haxe.macro.Expr;
+import haxe.macro.Type;
+
+using StringTools;
+using haxe.macro.Tools;
+
+class TestBaseMacro {
+	macro static public function run() {
+		var c = Context.getLocalClass();
+		var fields = c.get().fields.get();
+		var acc = [];
+		var eSetup = macro setup();
+		for (field in fields) {
+			if (field.name.startsWith("test")) {
+				acc.push(eSetup);
+				acc.push(macro $i{field.name}());
+			}
+		}
+		acc.push(macro trace("Done " +numTests+ " tests (" +numFailures+ " failures)"));
+		Context.onGenerate(check);
+		return macro $b{acc};
+	}
+
+	#if macro
+	static function check(types:Array<Type>) {
+		for (t in types) {
+			switch (t) {
+				case TInst(c = _.get() => { superClass: { t: _.get().name => "TestBase" }},_):
+					checkClass(c.get());
+				case _:
+			}
+		}
+	}
+
+	static function checkClass(c:ClassType) {
+		for (field in c.fields.get()) {
+			checkExpr(field.expr());
+		}
+	}
+
+	static function checkExpr(e:TypedExpr) {
+		switch (e.expr) {
+			case TCall({ expr: TField(_, FInstance(_, _.get() => {name: "assertEqualsConst"}))}, el):
+				switch [el[0].expr, el[1].expr] {
+					case [TConst(tc1), TConst(tc2)]:
+						if (!constEquals(tc1, tc2)) {
+							Context.warning('$tc2 should be $tc1', e.pos);
+						}
+					case [e1, e2]:
+						Context.warning('$e2 should be $e1', e.pos);
+				}
+			case TCall({ expr: TField(_, FInstance(_, _.get() => {name: "assertEquals"}))}, el):
+				for (e in el) {
+					checkExpr(e);
+				}
+				switch (el[1].expr) {
+					case (TConst(tc)):
+						Context.warning('Unexpected constant $tc in assertEquals, use assertEqualsConst if this is intended', e.pos);
+					case _:
+				}
+			case _:
+				e.iter(checkExpr);
+		}
+	}
+
+	static function constEquals(const1:TConstant, const2:TConstant) {
+		return switch [const1, const2] {
+			case [TInt(i1), TInt(i2)]: i1 == i2;
+			case [TFloat(s1), TFloat(s2)]: s1 == s2;
+			case [TString(s1), TString(s2)]: s1 == s2;
+			case [TBool(b1), TBool(b2)]: b1 == b2;
+			case [TNull, TNull]: true;
+			case [TThis, TThis]: true;
+			case [TSuper, TSuper]: true;
+			case _: false;
+		}
+	}
+	#end
+}

+ 78 - 0
tests/optimization/src/TestNullChecker.hx

@@ -0,0 +1,78 @@
+package ;
+
+class TestNullChecker extends TestBase {
+
+	static function main() {
+		new TestNullChecker();
+	}
+
+	public function new() {
+		super();
+		TestBaseMacro.run();
+	}
+
+	function test1() {
+		var ns = getNullString();
+		@:analyzer(testIsNull) ns;
+		ns = "foo";
+		@:analyzer(testIsNotNull) ns;
+	}
+
+	function test2() {
+		var s = getString();
+		@:analyzer(testIsNotNull) s;
+		s = getNullString();
+		@:analyzer(testIsNull) s;
+	}
+
+	function test3() {
+		var ns = getNullString();
+		if (ns == null) {
+			@:analyzer(testIsNull) ns;
+			ns = getString();
+			@:analyzer(testIsNotNull) ns;
+		}
+		@:analyzer(testIsNotNull) ns;
+	}
+
+	function test4() {
+		var ns = getNullString();
+		if (ns != null) {
+			@:analyzer(testIsNotNull) ns;
+			ns = getNullString();
+			@:analyzer(testIsNull) ns;
+		}
+		@:analyzer(testIsNull) ns;
+	}
+
+	function test5() {
+		var ns = getNullString();
+		if (ns != null) {
+			@:analyzer(testIsNotNull) ns;
+		} else {
+			@:analyzer(testIsNull) ns;
+			ns = getString();
+		}
+		@:analyzer(testIsNotNull) ns;
+	}
+
+	function test6() {
+		var ns = getNullString();
+		if (ns != null) {
+			@:analyzer(testIsNotNull) ns;
+		} else {
+			if (ns == null) {
+				ns = getString();
+			}
+		}
+		@:analyzer(testIsNotNull) ns;
+	}
+
+	function getString() {
+		return "foo";
+	}
+
+	function getNullString():Null<String> {
+		return null;
+	}
+}

+ 1 - 0
tests/unit/TestType.hx

@@ -665,6 +665,7 @@ class TestType extends Test {
 		eq(c.fProp(9), "test09");
 		eq(c.fProp(9), "test09");
 	}
 	}
 
 
+	@:analyzer(ignore)
 	function testVoidFunc() {
 	function testVoidFunc() {
 		exc(function() { throw null; return 1; } );
 		exc(function() { throw null; return 1; } );
 		exc(function() { throw null; return "foo"; } );
 		exc(function() { throw null; return "foo"; } );

+ 1 - 1
tests/unit/UnitBuilder.hx

@@ -133,7 +133,7 @@ class UnitBuilder {
 							el2.push(mkEq((macro $e1[$v{i}]), e2, e.pos));
 							el2.push(mkEq((macro $e1[$v{i}]), e2, e.pos));
 						}
 						}
 						if (el2.length == 0)
 						if (el2.length == 0)
-							mkEq((macro $e1.length), (macro 0), e.pos);
+							mkEq((macro @:pos(e1.pos) $e1.length), (macro 0), e.pos);
 						else
 						else
 							macro { $a{el2}; };
 							macro { $a{el2}; };
 					case EBinop(OpEq, e1, e2):
 					case EBinop(OpEq, e1, e2):

+ 2 - 1
tests/unit/compile-each.hxml

@@ -4,4 +4,5 @@
 -cp "C:\Program Files\The Haxe Effect\src/dev/null"
 -cp "C:\Program Files\The Haxe Effect\src/dev/null"
 -resource res1.txt
 -resource res1.txt
 -resource res2.bin
 -resource res2.bin
--dce full
+-dce full
+-D analyzer

+ 3 - 0
typer.ml

@@ -3444,6 +3444,9 @@ and type_expr ctx (e,p) (with_type:with_type) =
 					| _ -> Type.map_expr loop e
 					| _ -> Type.map_expr loop e
 				in
 				in
 				loop e
 				loop e
+			| (Meta.Analyzer,_,_) ->
+				let e = e() in
+				{e with eexpr = TMeta(m,e)}
 			| _ -> e()
 			| _ -> e()
 		in
 		in
 		ctx.meta <- old;
 		ctx.meta <- old;