Selaa lähdekoodia

[analyzer] reject OpAssignOp fusion on php only if the rhs has a side effect

closes #7500
Simon Krajewski 7 vuotta sitten
vanhempi
commit
ffa72bc4e3

+ 12 - 13
src/optimization/analyzerTexpr.ml

@@ -105,10 +105,9 @@ let target_handles_unops com = match com.platform with
 	| Lua | Python -> false
 	| Lua | Python -> false
 	| _ -> true
 	| _ -> true
 
 
-let target_handles_assign_ops com = match com.platform with
-	(* Technically PHP can handle assign ops, but unfortunately x += y is not always
-	   equivalent to x = x + y in case y has side-effects. *)
-	| Lua | Php -> false
+let target_handles_assign_ops com e2 = match com.platform with
+	| Php -> not (has_side_effect e2)
+	| Lua -> false
 	| Cpp when not (Common.defined com Define.Cppia) -> false
 	| Cpp when not (Common.defined com Define.Cppia) -> false
 	| _ -> true
 	| _ -> true
 
 
@@ -529,7 +528,7 @@ module InterferenceReport = struct
 		let s_hashtbl f h =
 		let s_hashtbl f h =
 			String.concat ", " (Hashtbl.fold (fun k _ acc -> (f k) :: acc) h [])
 			String.concat ", " (Hashtbl.fold (fun k _ acc -> (f k) :: acc) h [])
 		in
 		in
-		Type.Printer.s_record_fields "\t" [
+		Type.Printer.s_record_fields "" [
 			"ir_var_reads",s_hashtbl string_of_int ir.ir_var_reads;
 			"ir_var_reads",s_hashtbl string_of_int ir.ir_var_reads;
 			"ir_var_writes",s_hashtbl string_of_int ir.ir_var_writes;
 			"ir_var_writes",s_hashtbl string_of_int ir.ir_var_writes;
 			"ir_field_reads",s_hashtbl (fun x -> x) ir.ir_field_reads;
 			"ir_field_reads",s_hashtbl (fun x -> x) ir.ir_field_reads;
@@ -621,14 +620,14 @@ module Fusion = struct
 		| OpArrow ->
 		| OpArrow ->
 			false
 			false
 
 
-	let use_assign_op com op e1 e2 =
+	let use_assign_op com op e1 e2 e3 =
 		let skip e = match com.platform with
 		let skip e = match com.platform with
 			| Eval -> Texpr.skip e
 			| Eval -> Texpr.skip e
 			| _ -> e
 			| _ -> e
 		in
 		in
 		let e1 = skip e1 in
 		let e1 = skip e1 in
 		let e2 = skip e2 in
 		let e2 = skip e2 in
-		is_assign_op op && target_handles_assign_ops com && Texpr.equal e1 e2 && not (has_side_effect e1) && match com.platform with
+		is_assign_op op && target_handles_assign_ops com e3 && Texpr.equal e1 e2 && not (has_side_effect e1) && match com.platform with
 			| Cs when is_null e1.etype || is_null e2.etype -> false (* C# hates OpAssignOp on Null<T> *)
 			| Cs when is_null e1.etype || is_null e2.etype -> false (* C# hates OpAssignOp on Null<T> *)
 			| _ -> true
 			| _ -> true
 
 
@@ -696,9 +695,9 @@ module Fusion = struct
 			        (is_compiler_generated || config.optimize && config.fusion && config.user_var_fusion && not has_type_params)
 			        (is_compiler_generated || config.optimize && config.fusion && config.user_var_fusion && not has_type_params)
 			in
 			in
 			if config.fusion_debug then begin
 			if config.fusion_debug then begin
-				print_endline (Printf.sprintf "FUSION\n\tvar %s<%i> = %s" v.v_name v.v_id (s_expr_pretty e));
-				print_endline (Printf.sprintf "\tcan_be_fused:%b: num_uses:%i <= 1 && num_writes:%i = 0 && can_be_used_as_value:%b && (is_compiler_generated:%b || config.optimize:%b && config.fusion:%b && config.user_var_fusion:%b)"
-					b num_uses num_writes can_be_used_as_value is_compiler_generated config.optimize config.fusion config.user_var_fusion)
+				print_endline (Printf.sprintf "\nFUSION: %s\n\tvar %s<%i> = %s" (if b then "true" else "false") v.v_name v.v_id (s_expr_pretty e));
+				print_endline (Printf.sprintf "CONDITION:\n\tnum_uses:%i <= 1 && num_writes:%i = 0 && can_be_used_as_value:%b && (is_compiler_generated:%b || config.optimize:%b && config.fusion:%b && config.user_var_fusion:%b)"
+					num_uses num_writes can_be_used_as_value is_compiler_generated config.optimize config.fusion config.user_var_fusion)
 			end;
 			end;
 			b
 			b
 		in
 		in
@@ -778,8 +777,8 @@ module Fusion = struct
 				let found = ref false in
 				let found = ref false in
 				let blocked = ref false in
 				let blocked = ref false in
 				let ir = InterferenceReport.from_texpr e1 in
 				let ir = InterferenceReport.from_texpr e1 in
-				if config.fusion_debug then print_endline (Printf.sprintf "\tInterferenceReport: %s\n\t%s"
-					 (InterferenceReport.to_string ir) (Type.s_expr_pretty true "\t" false (s_type (print_context())) (mk (TBlock el) t_dynamic null_pos)));
+				if config.fusion_debug then print_endline (Printf.sprintf "INTERFERENCE: %s\nINTO: %s"
+					 (InterferenceReport.to_string ir) (Type.s_expr_pretty true "" false (s_type (print_context())) (mk (TBlock el) t_dynamic null_pos)));
 				(* This function walks the AST in order of evaluation and tries to find an occurrence of v1. If successful, that occurrence is
 				(* This function walks the AST in order of evaluation and tries to find an occurrence of v1. If successful, that occurrence is
 				   replaced with e1. If there's an interference "on the way" the replacement is canceled. *)
 				   replaced with e1. If there's an interference "on the way" the replacement is canceled. *)
 				let rec replace e =
 				let rec replace e =
@@ -1017,7 +1016,7 @@ module Fusion = struct
 				with Exit ->
 				with Exit ->
 					fuse (e1 :: acc) (e2 :: el)
 					fuse (e1 :: acc) (e2 :: el)
 				end
 				end
-			| {eexpr = TBinop(OpAssign,e1,{eexpr = TBinop(op,e2,e3)})} as e :: el when use_assign_op com op e1 e2 ->
+			| {eexpr = TBinop(OpAssign,e1,{eexpr = TBinop(op,e2,e3)})} as e :: el when use_assign_op com op e1 e2 e3 ->
 				let rec loop e = match e.eexpr with
 				let rec loop e = match e.eexpr with
 					| TLocal v -> state#dec_reads v;
 					| TLocal v -> state#dec_reads v;
 					| _ -> Type.iter loop e
 					| _ -> Type.iter loop e

+ 3 - 3
src/optimization/analyzerTexprTransformer.ml

@@ -718,7 +718,7 @@ and func ctx i =
 			let eo = Option.map loop eo in
 			let eo = Option.map loop eo in
 			let v' = get_var_origin ctx.graph v in
 			let v' = get_var_origin ctx.graph v in
 			{e with eexpr = TVar(v',eo)}
 			{e with eexpr = TVar(v',eo)}
-		| TBinop(OpAssign,e1,({eexpr = TBinop(op,e2,e3)} as e4)) when target_handles_assign_ops ctx.com ->
+		| TBinop(OpAssign,e1,({eexpr = TBinop(op,e2,e3)} as e4)) when target_handles_assign_ops ctx.com e3 ->
 			let e1 = loop e1 in
 			let e1 = loop e1 in
 			let e2 = loop e2 in
 			let e2 = loop e2 in
 			let e3 = loop e3 in
 			let e3 = loop e3 in
@@ -733,8 +733,8 @@ and func ctx i =
 			begin match e1.eexpr,e2.eexpr with
 			begin match e1.eexpr,e2.eexpr with
 				| TLocal v1,TLocal v2 when v1 == v2 && not v1.v_capture && is_valid_assign_op op ->
 				| TLocal v1,TLocal v2 when v1 == v2 && not v1.v_capture && is_valid_assign_op op ->
 					begin match op,e3.eexpr with
 					begin match op,e3.eexpr with
-						| OpAdd,TConst (TInt i32) when Int32.to_int i32 = 1 && target_handles_assign_ops ctx.com -> {e with eexpr = TUnop(Increment,Prefix,e1)}
-						| OpSub,TConst (TInt i32) when Int32.to_int i32 = 1 && target_handles_assign_ops ctx.com -> {e with eexpr = TUnop(Decrement,Prefix,e1)}
+						| 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)}
 						| _ -> {e with eexpr = TBinop(OpAssignOp op,e1,e3)}
 					end
 					end
 				| _ ->
 				| _ ->