Browse Source

try a different approach

Simon Krajewski 1 year ago
parent
commit
86cebcab90

+ 1 - 0
.gitignore

@@ -137,3 +137,4 @@ lib.sexp
 src/compiler/version.ml
 tests/party
 tests/misc/projects/Issue10863/error.log
+tests/misc/coroutines/dump

+ 6 - 0
src-json/meta.json

@@ -142,6 +142,12 @@
 		"targets": ["TAbstract"],
 		"links": ["https://haxe.org/manual/types-abstract-core-type.html"]
 	},
+	{
+		"name": "Coroutine",
+		"metadata": ":coroutine",
+		"doc": "Transform function into a coroutine",
+		"targets": ["TClassField"]
+	},
 	{
 		"name": "CppFileCode",
 		"metadata": ":cppFileCode",

+ 1 - 0
src/context/common.ml

@@ -830,6 +830,7 @@ let create compilation_step cs version args display_mode =
 			tstring = mk_mono();
 			tnull = (fun _ -> die "Could use locate abstract Null<T> (was it redefined?)" __LOC__);
 			tarray = (fun _ -> die "Could not locate class Array<T> (was it redefined?)" __LOC__);
+			tcoro = (fun _ -> die "Could not locate abstract Coroutine<T> (was it redefined?)" __LOC__);
 		};
 		std = null_class;
 		file_keys = new file_keys;

+ 26 - 4
src/context/typecore.ml

@@ -105,6 +105,11 @@ type typer_pass_tasks = {
 	mutable tasks : (unit -> unit) list;
 }
 
+type function_mode =
+	| FunFunction
+	| FunCoroutine
+	| FunNotFunction
+
 type typer_globals = {
 	mutable delayed : typer_pass_tasks Array.t;
 	mutable delayed_min_index : int;
@@ -140,7 +145,7 @@ type typer_globals = {
    is shared by local TFunctions. *)
 and typer_expr = {
 	curfun : current_fun;
-	in_function : bool;
+	function_mode : function_mode;
 	mutable ret : t;
 	mutable opened : anon_status ref list;
 	mutable monomorphs : monomorphs;
@@ -149,6 +154,7 @@ and typer_expr = {
 	mutable with_type_stack : WithType.t list;
 	mutable call_argument_stack : expr list list;
 	mutable macro_depth : int;
+	mutable is_coroutine : bool;
 }
 
 and typer_field = {
@@ -237,10 +243,10 @@ module TyperManager = struct
 			in_call_args = false;
 		}
 
-	let create_ctx_e curfun in_function =
+	let create_ctx_e curfun function_mode =
 		{
 			curfun;
-			in_function;
+			function_mode;
 			ret = t_dynamic;
 			opened = [];
 			monomorphs = {
@@ -251,6 +257,7 @@ module TyperManager = struct
 			with_type_stack = [];
 			call_argument_stack = [];
 			macro_depth = 0;
+			is_coroutine = false;
 		}
 
 	let clone_for_module ctx m =
@@ -293,8 +300,17 @@ module TyperManager = struct
 
 	let clone_for_type_parameter_expression ctx =
 		let f = create_ctx_f ctx.f.curfield in
-		let e = create_ctx_e ctx.e.curfun false in
+		let e = create_ctx_e ctx.e.curfun FunNotFunction in
 		create ctx ctx.m ctx.c f e PTypeField ctx.type_params
+
+	let is_coroutine_context ctx =
+		ctx.e.function_mode = FunCoroutine
+
+	let is_function_context ctx = match ctx.e.function_mode with
+		| FunFunction | FunCoroutine ->
+			true
+		| FunNotFunction ->
+			false
 end
 
 type field_host =
@@ -687,6 +703,12 @@ let safe_mono_close ctx m p =
 		Unify_error l ->
 			raise_or_display ctx l p
 
+(* TODO: this is wrong *)
+let coroutine_type ctx args ret =
+	let args = args @ [("_hx_continuation",false,(tfun [ret; t_dynamic] ctx.com.basic.tvoid))] in
+	let ret = ctx.com.basic.tvoid in
+	TFun(args,ret)
+
 let relative_path ctx file =
 	ctx.com.class_paths#relative_path file
 

+ 15 - 0
src/core/tFunctions.ml

@@ -622,6 +622,21 @@ let rec follow_lazy_and_mono t = match t with
 	| _ ->
 		t
 
+type maybe_coro =
+	| Coro of tsignature
+	| NotCoro of t
+
+let follow_with_coro t = match follow t with
+	| TAbstract({a_path = ([],"Coroutine")},[t]) ->
+		begin match follow t with
+			| TFun(args,ret) ->
+				Coro (args,ret)
+			| t ->
+				NotCoro t
+		end
+	| t ->
+		NotCoro t
+
 let rec ambiguate_funs t =
 	match follow t with
 	| TFun _ -> TFun ([], t_dynamic)

+ 1 - 0
src/core/tType.ml

@@ -460,6 +460,7 @@ type basic_types = {
 	mutable tnull : t -> t;
 	mutable tstring : t;
 	mutable tarray : t -> t;
+	mutable tcoro : (string * bool * t) list -> t -> t;
 }
 
 type class_field_scope =

+ 19 - 1
src/optimization/analyzer.ml

@@ -739,7 +739,16 @@ module Debug = struct
 	let dot_debug_node g ch nil bb =
 		let s = Printf.sprintf "(%i)" bb.bb_id in
 		let s = List.fold_left (fun s ni -> s ^ match ni with
-			| NIExpr -> if DynArray.length bb.bb_el = 0 then "" else "\n" ^  String.concat "\n" (DynArray.to_list (DynArray.map s_expr_pretty bb.bb_el))
+			| NIExpr ->
+				let sl = DynArray.to_list (DynArray.map s_expr_pretty bb.bb_el) in
+				let sl = match terminator_to_texpr_maybe bb.bb_terminator with
+					| None -> sl
+					| Some e -> sl @ [s_expr_pretty e]
+				in
+				begin match sl with
+					| [] -> ""
+					| _ -> "\n" ^  String.concat "\n" sl
+				end
 			| NIPhi -> if DynArray.length bb.bb_phi = 0 then "" else "\n" ^ String.concat "\n" (DynArray.to_list (DynArray.map (fun e -> s_expr_pretty e) bb.bb_phi))
 			| NIVars -> if bb.bb_var_writes = [] then "" else "\n" ^ String.concat ", " (List.map (fun v -> s_var v) bb.bb_var_writes)
 			| NILoopGroups -> if bb.bb_loop_groups = [] then "" else "\nLoops: " ^ (String.concat ", " (List.map string_of_int bb.bb_loop_groups))
@@ -795,6 +804,8 @@ module Debug = struct
 			edge bb_next "next";
 		| SEMerge bb_next ->
 			edge bb_next "merge"
+		| SESuspend (call, bb_next) ->
+			edge bb_next ("suspend " ^ s_expr_pretty (mk (TCall (call.efun, call.args)) t_dynamic call.pos))
 		| SESwitch ss ->
 			List.iter (fun (el,bb) -> edge bb ("case " ^ (String.concat " | " (List.map s_expr_pretty el)))) ss.ss_cases;
 			(match ss.ss_default with None -> () | Some bb -> edge bb "default");
@@ -1108,6 +1119,13 @@ module Run = struct
 			let e = reduce_control_flow com e in
 			maybe_debug();
 			cf.cf_expr <- Some e;
+
+			(* lose Coroutine<T> type here *)
+			(match follow_with_coro cf.cf_type with
+			| Coro (args, ret) ->
+				let args = args @ [("",false,tfun [ret; t_dynamic] com.basic.tvoid)] in
+				cf.cf_type <- TFun (args, com.basic.tvoid);
+			| _ -> ())
 		| _ -> ()
 
 	let run_on_field com config c cf =

+ 3 - 0
src/optimization/analyzerConfig.ml

@@ -38,6 +38,7 @@ type t = {
 	detail_times : int;
 	user_var_fusion : bool;
 	fusion_debug : bool;
+	coro_debug : bool;
 }
 
 let flag_optimize = "optimize"
@@ -74,6 +75,7 @@ let get_base_config com =
 		detail_times = (try int_of_string (Common.defined_value_safe com ~default:"0" Define.AnalyzerTimes) with _ -> 0);
 		user_var_fusion = (match com.platform with Flash | Jvm -> false | _ -> true) && (Common.raw_defined com "analyzer_user_var_fusion" || (not com.debug && not (Common.raw_defined com "analyzer_no_user_var_fusion")));
 		fusion_debug = false;
+		coro_debug = false;
 	}
 
 let update_config_from_meta com config ml =
@@ -97,6 +99,7 @@ let update_config_from_meta com config ml =
 						| "dot_debug" -> { config with debug_kind = DebugDot }
 						| "full_debug" -> { config with debug_kind = DebugFull }
 						| "fusion_debug" -> { config with fusion_debug = true }
+						| "coro_debug" -> { config with coro_debug = true }
 						| "as_var" -> config
 						| _ ->
 							let options = Warning.from_meta ml in

+ 390 - 0
src/optimization/analyzerCoro.ml

@@ -0,0 +1,390 @@
+open Globals
+open Type
+open AnalyzerTypes
+open BasicBlock
+open Graph
+open Texpr
+
+let block_to_texpr_coroutine ctx bb vcontinuation vresult verror p =
+	assert(bb.bb_closed);
+
+	let open Texpr.Builder in
+	let com = ctx.com in
+
+	let eerror = make_local verror null_pos in
+
+	let mk_int i = make_int com.basic i null_pos in
+
+	let mk_assign estate eid =
+		mk (TBinop (OpAssign,estate,eid)) eid.etype null_pos
+	in
+
+	let vstate = alloc_var VGenerated "_hx_state" com.basic.tint p in
+	add_var_flag vstate VCaptured;
+	declare_var ctx.graph vstate bb;
+	let estate = make_local vstate p in
+	let set_state id = mk_assign estate (mk_int id) in
+
+	let vexcstate = alloc_var VGenerated "_hx_exceptionState" com.basic.tint p in
+	add_var_flag vexcstate VCaptured;
+	declare_var ctx.graph vexcstate bb;
+	let eexcstate = make_local vexcstate p in
+	let set_excstate id = mk_assign eexcstate (mk_int id) in
+
+	let tstatemachine = tfun [t_dynamic; t_dynamic] com.basic.tvoid in
+	let vstatemachine = alloc_var VGenerated "_hx_stateMachine" tstatemachine p in
+	add_var_flag vstatemachine VCaptured;
+	declare_var ctx.graph vstatemachine bb;
+	let estatemachine = make_local vstatemachine p in
+
+	let get_next_state_id =
+		let counter = ref 0 in
+		fun () -> (let id = !counter in incr counter; id)
+	in
+
+	let get_rethrow_state_id =
+		let rethrow_state_id = ref (-1) in
+		fun () -> begin
+			if !rethrow_state_id = (-1) then rethrow_state_id := get_next_state_id ();
+			!rethrow_state_id;
+		end
+	in
+
+	let mk_continuation_call eresult p =
+		let econtinuation = make_local vcontinuation p in
+		mk (TCall (econtinuation, [eresult; make_null t_dynamic p])) com.basic.tvoid p
+	in
+	let mk_continuation_call_error eerror p =
+		let econtinuation = make_local vcontinuation p in
+		mk (TCall (econtinuation, [make_null t_dynamic p; eerror])) com.basic.tvoid p
+	in
+
+	let mk_suspending_call call =
+		let p = call.pos in
+
+		(* lose Coroutine<T> type for the called function not to confuse further filters and generators *)
+		let tcoroutine = tfun [t_dynamic; t_dynamic] com.basic.tvoid in
+		let tfun = match follow_with_coro call.efun.etype with
+			| Coro (args, ret) ->
+				let tcontinuation = tfun [ret; t_dynamic] com.basic.tvoid in
+				let args = args @ [("",false,tcontinuation)] in
+				TFun (args, tcoroutine)
+			| NotCoro _ ->
+				die "Unexpected coroutine type" __LOC__
+		in
+		let efun = { call.efun with etype = tfun } in
+		let args = call.args @ [ estatemachine ] in
+		let ecreatecoroutine = mk (TCall (efun, args)) tcoroutine call.pos in
+		let enull = make_null t_dynamic p in
+		mk (TCall (ecreatecoroutine, [enull; enull])) com.basic.tvoid call.pos
+	in
+
+	(* TODO: stolen from exceptions.ml. we should really figure out the filter ordering here *)
+	let std_is e t =
+		let std_cls =
+			(* TODO: load it? *)
+			match (try List.find (fun t -> t_path t = ([],"Std")) com.types with Not_found -> die "" __LOC__) with
+			| TClassDecl cls -> cls
+			| _ -> die "" __LOC__
+		in
+		let isOfType_field =
+			try PMap.find "isOfType" std_cls.cl_statics
+			with Not_found -> die "" __LOC__
+		in
+		let type_expr = mk (TTypeExpr (module_type_of_type t)) t_dynamic null_pos in
+		let isOfType_expr = Typecore.make_static_field_access std_cls isOfType_field isOfType_field.cf_type null_pos in
+		mk (TCall (isOfType_expr, [e; type_expr])) com.basic.tbool null_pos
+	in
+
+
+	let states = ref [] in
+
+	let exc_states = ref [] in
+
+	let debug_endline s =
+		if ctx.config.coro_debug then
+			print_endline s
+	in
+	(* TODO: maybe merge this into block_to_texpr somehow, and only introduce new states when there is a suspension point *)
+	debug_endline "---";
+	let rec loop bb state_id back_state_id current_el while_loop exc_state_id_getter =
+		let p = bb.bb_pos in
+		(* TODO: only do this in the end, avoid unnecessary List.rev *)
+		let el = DynArray.to_list bb.bb_el in
+
+		let ereturn = mk (TReturn None) com.basic.tvoid p in
+
+		let add_state el =
+			states := (state_id,mk (TBlock el) com.basic.tvoid null_pos) :: !states
+		in
+		let get_cond_branch () = match bb.bb_terminator with TermCondBranch e -> e | _ -> die "" __LOC__ in
+
+		match bb.bb_syntax_edge with
+		| SESuspend (call, bb_next) ->
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "suspend cur:%d,next:%d,back:%d" state_id next_state_id back_state_id);
+			loop bb_next next_state_id back_state_id [] while_loop exc_state_id_getter;
+			let ecallcoroutine = mk_suspending_call call in
+			let esetstate = set_state next_state_id in
+			add_state (current_el @ el @ [esetstate; ecallcoroutine; ereturn])
+
+		| SENone ->
+			debug_endline (Printf.sprintf "none cur:%d,back:%d" state_id back_state_id);
+			(match bb.bb_terminator with
+			| TermBreak _ -> (* todo use pos *)
+				let _,next_state_id = Option.get while_loop in
+				let esetstate = set_state next_state_id in
+				add_state (current_el @ el @ [esetstate])
+			| TermContinue _ -> (* todo use pos *)
+				let body_state_id,_ = Option.get while_loop in
+				let esetstate = set_state body_state_id in
+				add_state (current_el @ el @ [esetstate])
+			| TermReturn _ | TermReturnValue _ -> (* todo use pos *)
+				let esetstate = set_state (-1) in
+				let eresult = match bb.bb_terminator with
+					| TermReturnValue (e,_) -> e
+					| _ -> make_null t_dynamic p
+				in
+				let ecallcontinuation = mk_continuation_call eresult p in
+				add_state (current_el @ el @ [esetstate; ecallcontinuation; ereturn])
+			| TermNone when back_state_id = -1 ->
+				let esetstate = set_state (-1) in
+				let ecallcontinuation = mk_continuation_call (make_null t_dynamic p) p in
+				add_state (current_el @ el @ [esetstate; ecallcontinuation; ereturn])
+			| TermNone ->
+				add_state (current_el @ el @ [set_state back_state_id])
+			| TermThrow (e,p) ->
+				let ethrow = mk (TThrow e) t_dynamic p in
+				add_state (current_el @ el @ [ethrow])
+			| TermCondBranch _ ->
+				die "unexpected TermCondBranch" __LOC__)
+
+		| SEMerge bb_next ->
+			debug_endline (Printf.sprintf "merge cur:%d,back:%d" state_id back_state_id);
+			loop bb_next state_id back_state_id (current_el @ el) while_loop exc_state_id_getter
+
+		| SESubBlock (bb_sub,bb_next) ->
+			let sub_state_id = get_next_state_id () in
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "sub cur:%d,sub:%d,next:%d,back:%d" state_id sub_state_id next_state_id back_state_id);
+			loop bb_next next_state_id back_state_id [] while_loop exc_state_id_getter;
+			loop bb_sub sub_state_id next_state_id [] while_loop exc_state_id_getter;
+			add_state (current_el @ el @ [set_state sub_state_id])
+
+		| SEIfThen (bb_then,bb_next,p) ->
+			let econd = get_cond_branch () in
+			let then_state_id = get_next_state_id () in
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "if-then cur:%d,then:%d,next:%d,back:%d" state_id then_state_id next_state_id back_state_id);
+			loop bb_then then_state_id next_state_id [] while_loop exc_state_id_getter;
+			loop bb_next next_state_id back_state_id [] while_loop exc_state_id_getter;
+			let eif = mk (TIf (econd, set_state then_state_id, Some (set_state next_state_id))) com.basic.tint p in
+			add_state (current_el @ el @ [eif])
+
+		| SEIfThenElse (bb_then,bb_else,bb_next,t,p) ->
+			let econd = get_cond_branch () in
+			let then_state_id = get_next_state_id () in
+			let else_state_id = get_next_state_id () in
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "if-then-else cur:%d,then:%d,else:%d,next:%d,back:%d" state_id then_state_id else_state_id next_state_id back_state_id);
+			loop bb_then then_state_id next_state_id [] while_loop exc_state_id_getter;
+			loop bb_else else_state_id next_state_id [] while_loop exc_state_id_getter;
+			loop bb_next next_state_id back_state_id [] while_loop exc_state_id_getter;
+			let eif = mk (TIf (econd, set_state then_state_id, Some (set_state else_state_id))) com.basic.tint p in
+			add_state (current_el @ el @ [eif])
+
+		| SESwitch switch ->
+			let esubj = get_cond_branch () in
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "switch cur:%d,next:%d,back:%d" state_id next_state_id back_state_id);
+			let ecases = List.map (fun (patterns,bb) ->
+				(* TODO: variable capture and other fancy things O_o *)
+				let case_state_id = get_next_state_id () in
+				debug_endline (Printf.sprintf "  case %d" case_state_id);
+				loop bb case_state_id next_state_id [] while_loop exc_state_id_getter;
+				{case_patterns = patterns;case_expr = set_state case_state_id}
+			) switch.ss_cases in
+			let default_state_id = match switch.ss_default with
+				| Some bb ->
+					let default_state_id = get_next_state_id () in
+					loop bb default_state_id next_state_id [] while_loop exc_state_id_getter;
+					default_state_id
+				| None ->
+					next_state_id
+			in
+			debug_endline (Printf.sprintf "  default %d" default_state_id);
+			let eswitch = mk_switch esubj ecases (Some (set_state default_state_id)) true in
+			let eswitch = mk (TSwitch eswitch) com.basic.tvoid p in
+			loop switch.ss_next next_state_id back_state_id [] while_loop exc_state_id_getter;
+			add_state (current_el @ el @ [eswitch])
+
+		| SEWhile (bb_body, bb_next, p) ->
+			let body_state_id = get_next_state_id () in
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "while cur:%d,body:%d,next:%d,back:%d" state_id body_state_id next_state_id back_state_id);
+			let new_while_loop = Some (body_state_id,next_state_id) in
+			(* TODO: next is empty? *)
+			loop bb_body body_state_id body_state_id [] new_while_loop exc_state_id_getter;
+			loop bb_next next_state_id back_state_id [] while_loop exc_state_id_getter;
+			add_state (current_el @ el @ [set_state body_state_id]);
+
+		| SETry (bb_try,_,catches,bb_next,p) ->
+			let try_state_id = get_next_state_id () in
+			let new_exc_state_id = get_next_state_id () in
+			let next_state_id = get_next_state_id () in
+			debug_endline (Printf.sprintf "try cur:%d,try:%d,catch:%d,next:%d,back:%d" state_id try_state_id new_exc_state_id next_state_id back_state_id);
+			loop bb_try try_state_id next_state_id [set_excstate new_exc_state_id] while_loop (fun () -> new_exc_state_id); (* TODO: add test for nested try/catch *)
+			let esetexcstate = set_excstate (exc_state_id_getter ()) in
+			let catch_case =
+				let erethrow = mk (TThrow eerror) t_dynamic null_pos in
+				let eif =
+					List.fold_left (fun enext (vcatch,bb_catch) ->
+						let catch_state_id = get_next_state_id () in
+						let ecatchvar = mk (TVar (vcatch, Some eerror)) com.basic.tvoid null_pos in
+						loop bb_catch catch_state_id next_state_id [esetexcstate; ecatchvar] while_loop exc_state_id_getter;
+
+						(* TODO: exceptions filter... *)
+						match follow vcatch.v_type with
+						| TDynamic _ ->
+							set_state catch_state_id (* no next *)
+						| t ->
+							let etypecheck = std_is (make_local verror null_pos) vcatch.v_type in
+							mk (TIf (etypecheck, set_state catch_state_id, Some enext)) com.basic.tvoid null_pos
+					) erethrow catches
+				in
+				(new_exc_state_id, eif)
+			in
+			exc_states := catch_case :: !exc_states;
+			loop bb_next next_state_id back_state_id [esetexcstate (* TODO: test propagation after try/catch *)] while_loop exc_state_id_getter;
+			add_state (current_el @ el @ [set_state try_state_id])
+	in
+	loop bb (get_next_state_id ()) (-1) [] None get_rethrow_state_id;
+
+	let states = !states @ !exc_states in
+
+	(* TODO: this (and the coroutine transform in general) should probably be run before captured vars handling *)
+	(* very ugly, but seems to work: extract locals that are used across states *)
+	let var_usages = Hashtbl.create 5 in
+	begin
+		let use v state_id =
+			let m = try
+				Hashtbl.find var_usages v.v_id
+			with Not_found ->
+				let m = Hashtbl.create 1 in
+				Hashtbl.add var_usages v.v_id m;
+				m
+			in
+			Hashtbl.replace m state_id true
+		in
+		List.iter (fun (state_id, expr) ->
+			let rec loop e =
+				match e.eexpr with
+				| TVar (v, eo) ->
+					Option.may loop eo;
+					use v state_id;
+				| TLocal v ->
+					use v state_id;
+				| _ ->
+					Type.iter loop e
+			in
+			loop expr
+		) states;
+	end;
+	let states, decls = begin
+		let is_used_across_states v_id =
+			let m = Hashtbl.find var_usages v_id in
+			(Hashtbl.length m) > 1
+		in
+		let rec loop cases cases_acc decls =
+			match cases with
+			| (id,expr) :: rest ->
+				let decls = ref decls in
+				let expr = begin
+					let rec loop e =
+						match e.eexpr with
+						| TVar (v, eo) when is_used_across_states v.v_id ->
+							decls := v :: !decls;
+							let elocal = make_local v e.epos in
+							(match eo with
+							| None -> elocal
+							| Some einit -> mk (TBinop (OpAssign,elocal,einit)) v.v_type e.epos)
+						| _ ->
+							Type.map_expr loop e
+					in
+					loop expr
+				end in
+				loop rest ((id,expr) :: cases_acc) !decls
+			| [] ->
+				List.rev cases_acc, decls
+		in
+		loop states [] []
+	end in
+
+	(* TODO:
+		we can optimize while and switch in some cases:
+		- if there's only one state (no suspensions) - don't wrap into while/switch, don't introduce state var
+	*)
+
+	let rethrow_state_id = get_rethrow_state_id () in
+	let rethrow_state = (rethrow_state_id, mk (TThrow eerror) com.basic.tvoid null_pos) in
+	let states = states @ [rethrow_state] in
+
+	let ethrow = mk (TBlock [
+		set_state rethrow_state_id;
+		mk (TThrow (make_string com.basic "Invalid coroutine state" p)) com.basic.tvoid p
+	]) com.basic.tvoid null_pos
+	in
+
+	let switch =
+		let cases = List.map (fun (id,e) -> {case_patterns = [mk_int id];case_expr = e}) states in
+		mk_switch estate cases (Some ethrow) true
+	in
+	let eswitch = mk (TSwitch switch) com.basic.tvoid p in
+
+	let etry = mk (TTry (
+		eswitch,
+		[
+			let vcaught = alloc_var VGenerated "e" t_dynamic null_pos in
+			declare_var ctx.graph vcaught bb;
+			(vcaught, mk (TIf (
+				mk (TBinop (OpEq, estate, mk_int rethrow_state_id)) com.basic.tbool null_pos,
+				mk (TBlock [
+					mk_assign eexcstate (mk_int rethrow_state_id);
+					mk_continuation_call_error (make_local vcaught null_pos) null_pos;
+					mk (TReturn None) com.basic.tvoid null_pos;
+				]) com.basic.tvoid null_pos,
+				Some (mk (TBlock [
+					mk_assign estate eexcstate;
+					mk_assign eerror (make_local vcaught null_pos);
+				]) com.basic.tvoid null_pos)
+			)) com.basic.tvoid null_pos)
+		]
+	)) com.basic.tvoid null_pos in
+
+	let eloop = mk (TWhile (make_bool com.basic true p, etry, DoWhile)) com.basic.tvoid p in
+
+	let eif = mk (TIf (
+		mk (TBinop (
+			OpNotEq,
+			eerror,
+			make_null verror.v_type p
+		)) com.basic.tbool p,
+		mk_assign estate eexcstate,
+		None
+	)) com.basic.tvoid p in
+
+	let estatemachine_def = mk (TFunction {
+		tf_args = [(vresult,None); (verror,None)];
+		tf_type = com.basic.tvoid;
+		tf_expr = mk (TBlock [eif; eloop]) com.basic.tvoid null_pos
+	}) tstatemachine p in
+
+	let state_var = mk (TVar (vstate, Some (make_int com.basic 0 p))) com.basic.tvoid p in
+	let excstate_var = mk (TVar (vexcstate, Some (make_int com.basic rethrow_state_id p))) com.basic.tvoid p in
+	let shared_vars = List.map (fun v -> mk (TVar (v,None)) com.basic.tvoid null_pos) decls in
+	let shared_vars = List.rev (excstate_var :: state_var :: shared_vars) in
+
+	mk (TBlock (shared_vars @ [
+		mk (TVar (vstatemachine, Some estatemachine_def)) com.basic.tvoid p;
+		mk (TReturn (Some estatemachine)) com.basic.tvoid p;
+	])) com.basic.tvoid p

+ 10 - 0
src/optimization/analyzerTexpr.ml

@@ -96,6 +96,16 @@ let can_throw e =
 	with Exit ->
 		true
 
+
+let terminator_to_texpr_maybe = function
+| AnalyzerTypes.BasicBlock.TermReturn p -> Some (mk (TReturn None) t_dynamic p)
+| TermBreak p -> Some (mk TBreak t_dynamic p)
+| TermContinue p -> Some (mk TContinue t_dynamic p)
+| TermReturnValue(e1,p) -> Some (mk (TReturn (Some e1)) t_dynamic p)
+| TermThrow(e1,p) -> Some (mk (TThrow e1) t_dynamic p)
+| TermCondBranch e1 -> Some e1 (* TODO: this shouldn't be here *)
+| _ -> None
+
 let rec can_be_inlined e = match e.eexpr with
 	| TConst _ -> true
 	| TParenthesis e1 | TMeta(_,e1) -> can_be_inlined e1

+ 61 - 15
src/optimization/analyzerTexprTransformer.ml

@@ -44,7 +44,17 @@ let rec func ctx bb tf t p =
 	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;
+	let coroutine = match follow_with_coro t with
+		| Coro _ ->
+			let v_result = alloc_var VGenerated "_hx_result" t_dynamic p in
+			let v_error = alloc_var VGenerated "_hx_error" t_dynamic p in
+			declare_var ctx.graph v_result bb_root;
+			declare_var ctx.graph v_error bb_root;
+			Some (v_result,v_error)
+		| NotCoro _ ->
+			None
+	in
+	add_function g tf t p bb_root coroutine;
 	add_cfg_edge bb bb_root CFGFunction;
 	let bb_breaks = ref [] in
 	let bb_continue = ref None in
@@ -331,8 +341,34 @@ let rec func ctx bb tf t p =
 		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)}
-			| _ -> die "" __LOC__
+			| efun :: el ->
+				let is_coroutine efun =
+					match follow_with_coro efun.etype with
+					| Coro _ -> true
+					| NotCoro _ -> false
+				in
+				(match coroutine with
+					| Some (vresult,_) when is_coroutine efun ->
+						let bb_next = create_node BKNormal e1.etype e1.epos in
+						add_cfg_edge bb bb_next CFGGoto;
+						let syntax_edge = SESuspend (
+							{
+								efun = efun;
+								args = el;
+								pos = e.epos;
+							},
+							bb_next
+						) in
+						set_syntax_edge bb syntax_edge;
+						close_node bb;
+						let eresult = Texpr.Builder.make_local vresult e.epos in
+						let eresult = mk_cast eresult e.etype e.epos in
+						bb_next,eresult
+					| _ ->
+						bb,{e with eexpr = TCall (efun,el)}
+				)
+			| _ ->
+				die "" __LOC__
 	and array_assign_op bb op e ea e1 e2 e3 =
 		let bb,e1 = bind_to_temp bb e1 in
 		let bb,e2 = bind_to_temp bb e2 in
@@ -686,15 +722,6 @@ let from_tfunction ctx tf t p =
 	close_node g.g_root;
 	g.g_exit <- bb_exit
 
-let terminator_to_texpr_maybe = function
-	| TermReturn p -> Some (mk (TReturn None) t_dynamic p)
-	| TermBreak p -> Some (mk TBreak t_dynamic p)
-	| TermContinue p -> Some (mk TContinue t_dynamic p)
-	| TermReturnValue(e1,p) -> Some (mk (TReturn (Some e1)) t_dynamic p)
-	| TermThrow(e1,p) -> Some (mk (TThrow e1) t_dynamic p)
-	| TermCondBranch e1 -> Some e1 (* TODO: this shouldn't be here *)
-	| _ -> None
-
 let rec block_to_texpr_el ctx bb =
 	if bb.bb_dominator == ctx.graph.g_unreachable then
 		[]
@@ -730,6 +757,8 @@ let rec block_to_texpr_el ctx bb =
 				}) ss.ss_cases in
 				let switch = mk_switch (get_terminator()) cases (Option.map block ss.ss_default) ss.ss_exhaustive in
 				Some ss.ss_next,Some (mk (TSwitch switch) ctx.com.basic.tvoid ss.ss_pos)
+			| SESuspend _ ->
+				assert false
 		in
 		let bb_next,e_term = loop bb bb.bb_syntax_edge in
 		let el = DynArray.to_list bb.bb_el in
@@ -751,8 +780,25 @@ and block_to_texpr ctx bb =
 	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 tfi = Hashtbl.find ctx.graph.g_functions i in
+	let tf = tfi.tf_tf in
+	let bb = tfi.tf_bb in
+	let p = tfi.tf_pos in
+	let e,tf_args,tf_type =
+		match tfi.tf_coroutine with
+		| Some (vresult,verror) ->
+			let vcontinuation = alloc_var VGenerated "_hx_continuation" (tfun [tf.tf_type; t_dynamic] ctx.com.basic.tvoid) p in
+			add_var_flag vcontinuation VCaptured;
+			declare_var ctx.graph vcontinuation bb;
+			let e = AnalyzerCoro.block_to_texpr_coroutine ctx bb vcontinuation vresult verror p in
+			(* All actual arguments will be captured after the transformation. *)
+			List.iter (fun (v,_) -> add_var_flag v VCaptured) tf.tf_args;
+			let tf_args = tf.tf_args @ [(vcontinuation,None)] in
+			let sm_type = tfun [t_dynamic; t_dynamic] ctx.com.basic.tvoid in
+			e, tf_args, sm_type
+		| None ->
+			block_to_texpr ctx bb, tf.tf_args, tf.tf_type
+	in
 	let rec loop e = match e.eexpr with
 		| TLocal v ->
 			{e with eexpr = TLocal (get_var_origin ctx.graph v)}
@@ -795,7 +841,7 @@ and func ctx i =
 			Type.map_expr loop e
 	in
 	let e = loop e in
-	mk (TFunction {tf with tf_expr = e}) t p
+	mk (TFunction {tf with tf_args = tf_args; tf_type = tf_type; tf_expr = e}) tfi.tf_t p
 
 let to_texpr ctx =
 	func ctx ctx.entry.bb_id

+ 14 - 4
src/optimization/analyzerTypes.ml

@@ -73,6 +73,7 @@ module BasicBlock = struct
 		| SEWhile of t * t * pos                                 (* `while` with "body" and "next" *)
 		| SESubBlock of t * t                                    (* "sub" with "next" *)
 		| SEMerge of t                                           (* Merge to same block *)
+		| SESuspend of (suspend_call * t)                        (* Suspension point *)
 		| SENone                                                 (* No syntax exit *)
 
 	and syntax_switch = {
@@ -253,7 +254,14 @@ end
 module Graph = struct
 	open BasicBlock
 
-	type tfunc_info = BasicBlock.t * Type.t * pos * tfunc
+	type tfunc_info = {
+		tf_bb : BasicBlock.t;
+		tf_t : Type.t;
+		tf_pos : pos;
+		tf_tf : tfunc;
+		tf_coroutine : (tvar * tvar) option;
+	}
+
 	type texpr_lookup = BasicBlock.t * texpr_lookup_target
 	type var_write = BasicBlock.t list
 	type 'a itbl = (int,'a) Hashtbl.t
@@ -339,8 +347,8 @@ module Graph = struct
 
 	(* nodes *)
 
-	let add_function g tf t p bb =
-		Hashtbl.add g.g_functions bb.bb_id (bb,t,p,tf)
+	let add_function g tf_tf tf_t tf_pos tf_bb tf_coroutine =
+		Hashtbl.add g.g_functions tf_bb.bb_id ({tf_bb;tf_t;tf_pos;tf_tf;tf_coroutine})
 
 	let alloc_id =
 		let r = ref 1 in
@@ -590,11 +598,13 @@ module Graph = struct
 					loop scopes bb_next
 				| SEMerge bb ->
 					loop scopes bb
+				| SESuspend (_, bb) ->
+					loop scopes bb
 				| SENone ->
 					()
 			end
 		in
-		Hashtbl.iter (fun _ (bb,_,_,_) -> loop [0] bb) g.g_functions
+		Hashtbl.iter (fun _ tfi -> loop [0] tfi.tf_bb) g.g_functions
 end
 
 type analyzer_context = {

+ 27 - 14
src/typing/callUnification.ml

@@ -179,7 +179,7 @@ let unify_call_args ctx el args r callp inline force_inline in_overload =
 	in
 	let el = try loop el args with exc -> restore(); raise exc; in
 	restore();
-	el,TFun(args,r)
+	el
 
 type overload_kind =
 	| OverloadProper (* @:overload or overload *)
@@ -288,10 +288,9 @@ let unify_field_call ctx fa el_typed el p inline =
 	let attempt_call cf in_overload =
 		let monos = Monomorph.spawn_constrained_monos map cf.cf_params in
 		let t = map (apply_params cf.cf_params monos cf.cf_type) in
-		match follow t with
-		| TFun(args,ret) ->
+		let make args ret coro =
 			let args_typed,args = unify_typed_args ctx tmap args el_typed p in
-			let el,_ =
+			let el =
 				try
 					unify_call_args ctx el args ret p inline is_forced_inline in_overload
 				with DisplayException.DisplayException de ->
@@ -299,13 +298,22 @@ let unify_field_call ctx fa el_typed el p inline =
 			in
 			(* here *)
 			let el = el_typed @ el in
-			let tf = TFun(args_typed @ args,ret) in
+			let args = (args_typed @ args) in
+			let tf = if coro then ctx.t.tcoro args ret else TFun(args,ret) in
 			let mk_call () =
 				let ef = mk (TField(fa.fa_on,FieldAccess.apply_fa cf fa.fa_host)) t fa.fa_pos in
 				!make_call_ref ctx ef el ret ~force_inline:inline p
 			in
 			make_field_call_candidate el ret monos tf cf (mk_call,extract_delayed_display())
-		| t ->
+		in
+		match follow_with_coro t with
+		| Coro(args,ret) when not (TyperManager.is_coroutine_context ctx) ->
+			raise_typing_error "Cannot directly call coroutine from a normal function, use start/create methods instead" p
+		| Coro(args,ret) ->
+			make args ret true
+		| NotCoro (TFun(args,ret)) ->
+			make args ret false
+		| NotCoro t ->
 			raise_typing_error (s_type (print_context()) t ^ " cannot be called") p
 	in
 	let unknown_ident_error = ref None in
@@ -545,14 +553,19 @@ object(self)
 			in
 			mk (TCall (e,el)) t p
 		in
-		let rec loop t = match follow t with
-		| TFun (args,r) ->
+		let make args ret coro =
+			if coro && not (TyperManager.is_coroutine_context ctx) then raise_typing_error "Cannot directly call coroutine from a normal function, use start/create methods instead" p;
 			let args_typed,args_left = unify_typed_args ctx (fun t -> t) args el_typed p in
-			let el, tfunc = unify_call_args ctx el args_left r p false false false in
+			let el = unify_call_args ctx el args_left ret p false false false in
 			let el = el_typed @ el in
-			let r = match tfunc with TFun(_,r) -> r | _ -> die "" __LOC__ in
-			mk (TCall (e,el)) r p
-		| TAbstract(a,tl) as t ->
+			mk (TCall (e,el)) ret p
+		in
+		let rec loop t = match follow_with_coro t with
+		| Coro(args,ret) ->
+			make args ret true
+		| NotCoro(TFun(args,ret)) ->
+			make args ret false
+		| NotCoro(TAbstract(a,tl) as t) ->
 			let check_callable () =
 				if Meta.has Meta.Callable a.a_meta then
 					loop (Abstract.get_underlying_type a tl)
@@ -567,12 +580,12 @@ object(self)
 			| _ ->
 				check_callable();
 			end
-		| TMono _ ->
+		| NotCoro (TMono _)->
 			let t = mk_mono() in
 			let el = el_typed @ List.map (fun e -> type_expr ctx e WithType.value) el in
 			unify ctx (tfun (List.map (fun e -> e.etype) el) t) e.etype e.epos;
 			mk (TCall (e,el)) t p
-		| t ->
+		| NotCoro t ->
 			default t
 		in
 		loop e.etype

+ 3 - 3
src/typing/macroContext.ml

@@ -64,7 +64,7 @@ let typing_timer ctx need_type f =
 
 	let ctx = if need_type && ctx.pass < PTypeField then begin
 		enter_field_typing_pass ctx.g ("typing_timer",[]);
-		TyperManager.clone_for_expr ctx ctx.e.curfun false
+		TyperManager.clone_for_expr ctx ctx.e.curfun ctx.e.function_mode
 	end else
 		ctx
 	in
@@ -917,7 +917,7 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
 			incr index;
 			(EArray ((EArrayDecl [e],p),(EConst (Int (string_of_int (!index), None)),p)),p)
 		) el in
-		let elt = fst (CallUnification.unify_call_args mctx constants (List.map fst eargs) t_dynamic p false false false) in
+		let elt = CallUnification.unify_call_args mctx constants (List.map fst eargs) t_dynamic p false false false in
 		List.map2 (fun ((n,_,t),mct) e ->
 			let e, et = (match e.eexpr with
 				(* get back our index and real expression *)
@@ -990,7 +990,7 @@ let type_macro ctx mode cpath f (el:Ast.expr list) p =
 
 let call_macro mctx args margs call p =
 	mctx.c.curclass <- null_class;
-	let el, _ = CallUnification.unify_call_args mctx args margs t_dynamic p false false false in
+	let el = CallUnification.unify_call_args mctx args margs t_dynamic p false false false in
 	call (List.map (fun e -> try Interp.make_const e with Exit -> raise_typing_error "Argument should be a constant" e.epos) el)
 
 let resolve_init_macro com e =

+ 16 - 2
src/typing/typeload.ml

@@ -414,6 +414,15 @@ and load_instance' ctx ptp get_params =
 			| [] -> t_dynamic
 			| [TPType t] -> TDynamic (Some (load_complex_type ctx true t))
 			| _ -> raise_typing_error "Too many parameters for Dynamic" ptp.pos_full
+		(* else if info.build_path = ([],"Coroutine") then
+			match t.tparams with
+			| [TPType t] ->
+				begin match load_complex_type ctx true t with
+				| TFun(args,ret,_) -> TFun(args,ret,true)
+				| _ -> raise_typing_error "Argument type should be function" ptp.pos_full
+				end
+			| _ ->
+				raise_typing_error "Wrong number of arguments for Coroutine<T>" ptp.pos_full *)
 		else if info.build_params = [] then begin match t.tparams with
 			| [] ->
 				info.build_apply []
@@ -866,8 +875,13 @@ let init_core_api ctx c =
 			| _ ->
 				raise_typing_error ("Field " ^ f.cf_name ^ " has different property access than core type") p;
 		end;
-		(match follow f.cf_type, follow f2.cf_type with
-		| TFun (pl1,_), TFun (pl2,_) ->
+		(match follow_with_coro f.cf_type, follow_with_coro f2.cf_type with
+		| Coro _,NotCoro _ ->
+			raise_typing_error "Method should be coroutine" p
+		| NotCoro _,Coro _ ->
+			raise_typing_error "Method should not be coroutine" p;
+		| NotCoro (TFun (pl1,_)), NotCoro(TFun (pl2,_))
+		| Coro (pl1,_), Coro(pl2,_) ->
 			if List.length pl1 != List.length pl2 then raise_typing_error "Argument count mismatch" p;
 			List.iter2 (fun (n1,_,_) (n2,_,_) ->
 				if n1 <> n2 then raise_typing_error ("Method parameter name '" ^ n2 ^ "' should be '" ^ n1 ^ "'") p;

+ 10 - 4
src/typing/typeloadCheck.ml

@@ -92,11 +92,16 @@ let valid_redefinition map1 map2 f1 t1 f2 t2 = (* child, parent *)
 	) in
 	match f1.cf_kind,f2.cf_kind with
 	| Method m1, Method m2 when not (m1 = MethDynamic) && not (m2 = MethDynamic) ->
-		begin match follow t1, follow t2 with
-		| TFun (args1,r1) , TFun (args2,r2) -> (
+		begin match follow_with_coro t1, follow_with_coro t2 with
+		| Coro _,NotCoro _ ->
+			raise (Unify_error [Unify_custom "Method should be coroutine"])
+		| NotCoro _,Coro _ ->
+			raise (Unify_error [Unify_custom "Method should not be coroutine"]);
+		| NotCoro (TFun (args1,r1)), NotCoro(TFun (args2,r2))
+		| Coro (args1,r1), Coro(args2,r2) ->
 			if not (List.length args1 = List.length args2) then raise (Unify_error [Unify_custom "Different number of function arguments"]);
 			let i = ref 0 in
-			try
+			begin try
 				valid r1 r2;
 				List.iter2 (fun (n,o1,a1) (_,o2,a2) ->
 					incr i;
@@ -105,7 +110,8 @@ let valid_redefinition map1 map2 f1 t1 f2 t2 = (* child, parent *)
 				) args1 args2;
 			with Unify_error l ->
 				let msg = if !i = 0 then Invalid_return_type else Invalid_function_argument(!i,List.length args1) in
-				raise (Unify_error (Cannot_unify (t1,t2) :: msg :: l)))
+				raise (Unify_error (Cannot_unify (t1,t2) :: msg :: l))
+			end
 		| _ ->
 			die "" __LOC__
 		end

+ 10 - 7
src/typing/typeloadFields.ml

@@ -736,7 +736,7 @@ module TypeBinding = struct
 		let c = cctx.tclass in
 		let t = cf.cf_type in
 		let p = cf.cf_pos in
-		let ctx = TyperManager.clone_for_expr ctx_f (if fctx.is_static then FunStatic else FunMember) false in
+		let ctx = TyperManager.clone_for_expr ctx_f (if fctx.is_static then FunStatic else FunMember) FunNotFunction in
 		if (has_class_flag c CInterface) then unexpected_expression ctx.com fctx "Initialization on field of interface" (pos e);
 		cf.cf_meta <- ((Meta.Value,[e],null_pos) :: cf.cf_meta);
 		let check_cast e =
@@ -827,9 +827,9 @@ module TypeBinding = struct
 		| Some e ->
 			bind_var_expression ctx cctx fctx cf e
 
-	let bind_method ctx_f cctx fctx fmode cf t args ret e p =
+	let bind_method ctx_f cctx fctx fmode cf t args ret e function_mode p =
 		let c = cctx.tclass in
-		let ctx = TyperManager.clone_for_expr ctx_f fmode true in
+		let ctx = TyperManager.clone_for_expr ctx_f fmode function_mode in
 		let bind r =
 			incr stats.s_methods_typed;
 			if (Meta.has (Meta.Custom ":debug.typing") (c.cl_meta @ cf.cf_meta)) then ctx.com.print (Printf.sprintf "Typing method %s.%s\n" (s_type_path c.cl_path) cf.cf_name);
@@ -1259,7 +1259,10 @@ let create_method (ctx,cctx,fctx) c f fd p =
 
 	ctx.type_params <- params @ ctx.type_params;
 	let args,ret = setup_args_ret ctx cctx fctx (fst f.cff_name) fd p in
-	let t = TFun (args#for_type,ret) in
+	let is_coroutine = Meta.has Meta.Coroutine f.cff_meta in
+	let function_mode = if is_coroutine then FunCoroutine else FunFunction in
+	let targs = args#for_type in
+	let t = if is_coroutine then ctx.t.tcoro targs ret else TFun (targs,ret) in
 	let cf = {
 		(mk_field name ~public:(is_public (ctx,cctx) f.cff_access parent) t f.cff_pos (pos f.cff_name)) with
 		cf_doc = f.cff_doc;
@@ -1330,18 +1333,18 @@ let create_method (ctx,cctx,fctx) c f fd p =
 	init_meta_overloads ctx (Some c) cf;
 	ctx.f.curfield <- cf;
 	if fctx.do_bind then
-		TypeBinding.bind_method ctx cctx fctx fmode cf t args ret fd.f_expr (match fd.f_expr with Some e -> snd e | None -> f.cff_pos)
+		TypeBinding.bind_method ctx cctx fctx fmode cf t args ret fd.f_expr function_mode (match fd.f_expr with Some e -> snd e | None -> f.cff_pos)
 	else begin
 		if fctx.is_display_field then begin
 			delay ctx.g PTypeField (fun () ->
 				(* We never enter type_function so we're missing out on the argument processing there. Let's do it here. *)
-				let ctx = TyperManager.clone_for_expr ctx fmode true in
+				let ctx = TyperManager.clone_for_expr ctx fmode function_mode in
 				ignore(args#for_expr ctx)
 			);
 			check_field_display ctx fctx c cf;
 		end else
 			delay ctx.g PTypeField (fun () ->
-				let ctx = TyperManager.clone_for_expr ctx fmode true in
+				let ctx = TyperManager.clone_for_expr ctx fmode function_mode in
 				args#verify_extern ctx
 			);
 		if fd.f_expr <> None then begin

+ 52 - 12
src/typing/typer.ml

@@ -1048,7 +1048,7 @@ and type_new ctx ptp el with_type force_inline p =
 		| None ->
 			raise_typing_error_ext (make_error (No_constructor (TClassDecl c)) p)
 		| Some(tl,tr) ->
-			let el,_ = unify_call_args ctx el tl tr p false false false in
+			let el = unify_call_args ctx el tl tr p false false false in
 			mk (TNew (c,params,el)) t p
 		end
 	| TAbstract({a_impl = Some c} as a,tl) when not (Meta.has Meta.MultiType a.a_meta) ->
@@ -1212,7 +1212,7 @@ and type_map_declaration ctx e1 el with_type p =
 	let el = (mk (TVar (v,Some enew)) t_dynamic p) :: (List.rev el) in
 	mk (TBlock el) tmap p
 
-and type_local_function ctx_from kind f with_type p =
+and type_local_function ctx_from kind f with_type want_coroutine p =
 	let name,inline = match kind with FKNamed (name,inline) -> Some name,inline | _ -> None,false in
 	let params = TypeloadFunction.type_function_params ctx_from f TPHLocal (match name with None -> "localfun" | Some (n,_) -> n) p in
 	if params <> [] then begin
@@ -1229,7 +1229,18 @@ and type_local_function ctx_from kind f with_type p =
 		| FunMemberAbstractLocal -> FunMemberAbstractLocal
 		| _ -> FunMemberClassLocal
 	in
-	let ctx = TyperManager.clone_for_expr ctx_from curfun true in
+	let is_coroutine = match v, with_type with
+		| None, WithType.WithType (texpected,_) ->
+			(match follow_with_coro texpected with
+			| Coro _ ->
+				true
+			| _ ->
+				false)
+		| _ ->
+			want_coroutine
+	in
+	let function_mode = if is_coroutine then FunCoroutine else FunFunction in
+	let ctx = TyperManager.clone_for_expr ctx_from curfun function_mode in
 	let old_tp = ctx.type_params in
 	ctx.type_params <- params @ ctx.type_params;
 	if not inline then ctx.e.in_loop <- false;
@@ -1256,8 +1267,9 @@ and type_local_function ctx_from kind f with_type p =
 		let m = new unification_matrix (arity + 1) in
 		let rec loop l = match l with
 			| t :: l ->
-				begin match follow t with
-				| TFun(args,ret) when List.length args = arity ->
+				begin match follow_with_coro t with
+				| NotCoro(TFun(args,ret))
+				| Coro(args,ret) when List.length args = arity ->
 					List.iteri (fun i (_,_,t) ->
 						(* We don't want to bind monomorphs because we want the widest type *)
 						let t = dynamify_monos t in
@@ -1290,14 +1302,15 @@ and type_local_function ctx_from kind f with_type p =
 	(match with_type with
 	| WithType.WithType(t,_) ->
 		let rec loop stack t =
-			(match follow t with
-			| TFun (args2,tr) when List.length args2 = List.length targs ->
+			(match follow_with_coro t with
+			| NotCoro (TFun (args2,tr))
+			| Coro(args2,tr) when List.length args2 = List.length targs ->
 				List.iter2 (fun (_,_,t1) (_,_,t2) ->
 					maybe_unify_arg t1 t2
 				) targs args2;
 				(* unify for top-down inference unless we are expecting Void *)
 				maybe_unify_ret tr
-			| TAbstract(a,tl) ->
+			| NotCoro (TAbstract(a,tl)) ->
 				begin match get_abstract_froms ctx a tl with
 					| [(_,t2)] ->
 						if not (List.exists (shallow_eq t) stack) then loop (t :: stack) t2
@@ -1325,7 +1338,7 @@ and type_local_function ctx_from kind f with_type p =
 		if name = None then display_error ctx.com "Unnamed lvalue functions are not supported" p
 	| _ ->
 		());
-	let ft = TFun (targs,rt) in
+	let ft = if is_coroutine then ctx.t.tcoro targs rt else TFun(targs,rt) in
 	let v = (match v with
 		| None -> None
 		| Some v ->
@@ -1342,7 +1355,8 @@ and type_local_function ctx_from kind f with_type p =
 	} in
 	let e = mk (TFunction tf) ft p in
 	match v with
-	| None -> e
+	| None ->
+		e
 	| Some v ->
 		Typeload.generate_args_meta ctx.com None (fun m -> v.v_meta <- m :: v.v_meta) f.f_args;
 		let open LocalUsage in
@@ -1638,6 +1652,12 @@ and type_meta ?(mode=MGet) ctx m e1 with_type p =
 			| (EReturn e, p) -> type_return ~implicit:true ctx e with_type p
 			| _ -> e()
 			end
+		| (Meta.Coroutine,_,_) ->
+			begin match fst e1 with
+			| EFunction (kind, f) ->
+				type_local_function ctx kind f with_type true p
+			| _ -> e()
+			end
 		(* Allow `${...}` reification because it's a noop and happens easily with macros *)
 		| (Meta.Dollar "",_,p) ->
 			e()
@@ -1701,6 +1721,12 @@ and type_call_access ctx e el mode with_type p_inline p =
 		build_call_access ctx acc el mode with_type p
 
 and type_call_builtin ctx e el mode with_type p =
+	let create_coroutine e args ret p =
+		let args = args @ [("_hx_continuation",false,(tfun [ret; t_dynamic] ctx.com.basic.tvoid))] in
+		let ret = ctx.com.basic.tvoid in
+		let el = unify_call_args ctx el args ret p false false false in
+		mk (TCall (e, el)) (tfun [t_dynamic; t_dynamic] ctx.com.basic.tvoid) p
+	in
 	match e, el with
 	| (EConst (Ident "trace"),p) , e :: el ->
 		if Common.defined ctx.com Define.NoTraces then
@@ -1730,6 +1756,20 @@ and type_call_builtin ctx e el mode with_type p =
 		(match follow e.etype with
 			| TFun signature -> type_bind ctx e signature args p
 			| _ -> raise Exit)
+	| (EField (e,"start",_),_), args ->
+		let e = type_expr ctx e WithType.value in
+		(match follow_with_coro e.etype with
+			| Coro (args, ret) ->
+				let ecoro = create_coroutine e args ret p in
+				let enull = Builder.make_null t_dynamic p in
+				mk (TCall (ecoro, [enull; enull])) ctx.com.basic.tvoid p
+			| _ -> raise Exit)
+	| (EField (e,"create",_),_), args ->
+		let e = type_expr ctx e WithType.value in
+		(match follow_with_coro e.etype with
+			| Coro (args, ret) ->
+				create_coroutine e args ret p
+			| _ -> raise Exit)
 	| (EConst (Ident "$type"),_) , e1 :: el ->
 		let e1 = type_expr ctx e1 with_type in
 		let s = s_type (print_context()) e1.etype in
@@ -1935,7 +1975,7 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) =
 		let e = Matcher.Match.match_expr ctx e1 cases def with_type false p in
 		wrap e
 	| EReturn e ->
-		if not ctx.e.in_function then begin
+		if not (TyperManager.is_function_context ctx) then begin
 			display_error ctx.com "Return outside function" p;
 			match e with
 			| None ->
@@ -1970,7 +2010,7 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) =
 	| EUnop (op,flag,e) ->
 		type_unop ctx op flag e with_type p
 	| EFunction (kind,f) ->
-		type_local_function ctx kind f with_type p
+		type_local_function ctx kind f with_type false p
 	| EUntyped e ->
 		let old = ctx.f.untyped in
 		ctx.f.untyped <- true;

+ 5 - 0
src/typing/typerDisplay.ml

@@ -286,6 +286,11 @@ let rec handle_signature_display ctx e_ast with_type =
 					(match follow e.etype with
 						| TFun signature -> e
 						| _ -> def ())
+				| (EField (e,("start" | "create"),_),p) ->
+					let e = type_expr ctx e WithType.value in
+					(match follow_with_coro e.etype with
+						| Coro(args,ret) -> {e with etype = coroutine_type ctx args ret}
+						| _ -> def ())
 				| _ ->	def()
 			in
 			let tl = match e1.eexpr with

+ 6 - 1
src/typing/typerEntry.ml

@@ -53,7 +53,7 @@ let create com macros =
 			get_build_infos = (fun() -> None);
 		};
 		f = TyperManager.create_ctx_f null_field;
-		e = TyperManager.create_ctx_e FunStatic false;
+		e = TyperManager.create_ctx_e FunStatic FunFunction;
 		pass = PBuildModule;
 		allow_inline = true;
 		allow_transform = true;
@@ -111,6 +111,11 @@ let create com macros =
 						TLazy r
 				in
 				ctx.t.tnull <- mk_null;
+			| "Coroutine" ->
+				let mk_coro args ret =
+					TAbstract(a,[TFun(args,ret)])
+				in
+				ctx.t.tcoro <- mk_coro
 			| _ -> ())
 		| TEnumDecl _ | TClassDecl _ | TTypeDecl _ ->
 			()

+ 20 - 1
std/StdTypes.hx

@@ -20,7 +20,6 @@
  * DEALINGS IN THE SOFTWARE.
  */
 // standard Haxe types
-
 /**
 	The standard `Void` type. Only `null` values can be of the type `Void`.
 
@@ -170,3 +169,23 @@ typedef KeyValueIterable<K, V> = {
 	@see https://haxe.org/manual/types-abstract-array-access.html
 **/
 extern interface ArrayAccess<T> {}
+
+/**
+	Coroutine function.
+**/
+@:callable
+@:coreType
+abstract Coroutine<T:haxe.Constraints.Function> {
+	/**
+		Suspend running coroutine and expose the continuation callback
+		for resuming coroutine execution.
+	**/
+	@:coroutine
+	public static extern function suspend<T>(f:(cont:(T, Null<Dynamic>) -> Void)->Void):T;
+
+	#if js // TODO: implement this all properly for all the targets
+	static function __init__():Void {
+		js.Syntax.code("{0} = {1}", Coroutine.suspend, cast function(f, cont) return (_, _) -> f(cont));
+	}
+	#end
+}

+ 2 - 0
tests/misc/coroutines/.gitignore

@@ -0,0 +1,2 @@
+/test.js
+/test.js.map

+ 6 - 0
tests/misc/coroutines/build.hxml

@@ -0,0 +1,6 @@
+--class-path src
+--library utest
+--main Main
+--debug
+--js test.js
+--cmd node test.js

+ 10 - 0
tests/misc/coroutines/src/Main.hx

@@ -0,0 +1,10 @@
+function main() {
+	utest.UTest.run([
+		new TestBasic(),
+		new TestControlFlow(),
+		new TestGenerator(),
+		#if js
+		new TestJsPromise(),
+		#end
+	]);
+}

+ 47 - 0
tests/misc/coroutines/src/TestBasic.hx

@@ -0,0 +1,47 @@
+class TestBasic extends utest.Test {
+	function testSimpleStart(async:Async) {
+		simple.start(42, (result,error) -> {
+			Assert.equals(42, result);
+			async.done();
+		});
+	}
+
+	function testSimpleCreate(async:Async) {
+		var cont = simple.create(42, (result,error) -> {
+			Assert.equals(42, result);
+			async.done();
+		});
+		cont(null, null);
+	}
+
+	function testErrorDirect(async:Async) {
+		error.start((result, error) -> {
+			// TODO: Exceptions.filter is currently run before coroutine processor
+			// so we get wrapped exception here... think what we want to do with this
+			var error:haxe.Exception = error;
+			Assert.equals("nope", error.message);
+			async.done();
+		});
+	}
+
+	function testErrorPropagation(async:Async) {
+		@:coroutine function propagate() {
+			error();
+		}
+		propagate.start((result, error) -> {
+			// TODO: Exceptions.filter is currently run before coroutine processor
+			// so we get wrapped exception here... think what we want to do with this
+			var error:haxe.Exception = error;
+			Assert.equals("nope", error.message);
+			async.done();
+		});
+	}
+
+	@:coroutine static function simple(arg:Int):Int {
+		return arg;
+	}
+
+	@:coroutine static function error() {
+		throw "nope";
+	}
+}

+ 132 - 0
tests/misc/coroutines/src/TestControlFlow.hx

@@ -0,0 +1,132 @@
+class TestControlFlow extends utest.Test {
+	function testIfThen(async:Async) {
+		@:coroutine function f(x) {
+			if (x) return 1;
+			return 2;
+		}
+		mapCalls.start([true, false], f, (result,error) -> {
+			Assert.same([1, 2], result);
+			async.done();
+		});
+	}
+
+	function testIfThenReturnNoValue(async:Async) {
+		var v;
+		@:coroutine function f(x) {
+			v = 1;
+			if (x) {
+				return;
+			}
+			v = 2;
+		}
+		@:coroutine function f2(x) { f(x); return v; }
+		mapCalls.start([true, false], f2, (result,error) -> {
+			Assert.same([1, 2], result);
+			async.done();
+		});
+	}
+
+	function testIfThenElse(async:Async) {
+		@:coroutine function f(x) {
+			return if (x) 1 else 2;
+		}
+		mapCalls.start([true, false], f, (result,error) -> {
+			Assert.same([1, 2], result);
+			async.done();
+		});
+	}
+
+	function testSwitchNoDefault(async:Async) {
+		@:coroutine function f(x) {
+			switch (x) {
+				case 1: return "a";
+				case 2: return "b";
+				case 3: return "c";
+			}
+			return "d";
+		}
+		mapCalls.start([1, 2, 3, 4], f, (result,error) -> {
+			Assert.same(["a", "b", "c", "d"], result);
+			async.done();
+		});
+	}
+
+	function testSwitchDefault(async:Async) {
+		@:coroutine function f(x) {
+			switch (x) {
+				case 1: return "a";
+				case 2: return "b";
+				case 3: return "c";
+				default: return "d";
+			}
+			return "e";
+		}
+		mapCalls.start([1, 2, 3, 4], f, (result,error) -> {
+			Assert.same(["a", "b", "c", "d"], result);
+			async.done();
+		});
+	}
+
+	function testLoop(async:Async) {
+		@:coroutine function f(x) {
+			var results = [];
+			var i = 0;
+			while (i < 10) {
+				if (i == 5 && x == 1) break;
+				if (i == 6 && x == 2) { i++; continue; }
+				results.push(i);
+				i++;
+			}
+			return results;
+		}
+		mapCalls.start([0, 1, 2], f, (result,error) -> {
+			Assert.same([
+				[0,1,2,3,4,5,6,7,8,9],
+				[0,1,2,3,4],
+				[0,1,2,3,4,5,7,8,9]
+			], result);
+			async.done();
+		});
+	}
+
+	function testTryCatch(async:Async) {
+		mapCalls.start([new E1(), new E2()], tryCatch, (result,error) -> {
+			Assert.same(["e1", "e2"], result);
+			async.done();
+		});
+	}
+
+	function testTryCatchFail(async:Async) {
+		tryCatch.start(new E3(), (result,error) -> {
+			Assert.isOfType(error, E3);
+			async.done();
+		});
+	}
+
+	@:coroutine function tryCatch(e:haxe.Exception) {
+		try {
+			throw e;
+		} catch (e:E1) {
+			return "e1";
+		} catch (e:E2) {
+			return "e2";
+		}
+		return "none";
+	}
+}
+
+@:coroutine
+private function mapCalls<TArg,TRet>(args:Array<TArg>, f:Coroutine<TArg->TRet>):Array<TRet> {
+	return [for (arg in args) f(arg)];
+}
+
+private class E1 extends haxe.Exception {
+	public function new() super("E1");
+}
+
+private class E2 extends haxe.Exception {
+	public function new() super("E1");
+}
+private class E3 extends haxe.Exception {
+	public function new() super("E1");
+}

+ 77 - 0
tests/misc/coroutines/src/TestGenerator.hx

@@ -0,0 +1,77 @@
+class TestGenerator extends utest.Test {
+	function testSimple() {
+		var iter = sequence(yield -> {
+			yield(1);
+			yield(2);
+			yield(3);
+		});
+		Assert.same([1,2,3], [for (v in iter) v]);
+	}
+
+	function testTreeIter() {
+		@:coroutine function iterTreeRec<T>(yield:Yield<T>, tree:Tree<T>) {
+			yield(tree.leaf);
+			if (tree.left != null) iterTreeRec(yield, tree.left);
+			if (tree.right != null) iterTreeRec(yield, tree.right);
+		}
+
+		function iterTree<T>(tree:Tree<T>):Iterator<T> {
+			return sequence(yield -> iterTreeRec(yield, tree));
+		}
+
+		var tree:Tree<Int> = {
+			leaf: 1,
+			left: {
+				leaf: 2,
+				left: {leaf: 3},
+				right: {leaf: 4, left: {leaf: 5}},
+			},
+			right: {
+				leaf: 6,
+				left: {leaf: 7}
+			}
+		};
+
+		Assert.same([1,2,3,4,5,6,7], [for (v in iterTree(tree)) v]);
+	}
+}
+
+private typedef Yield<T> = Coroutine<T->Void>;
+
+private function sequence<T>(f:Coroutine<Yield<T>->Void>):Iterator<T> {
+	var finished = false;
+	var nextValue:T = null;
+
+	var nextStep = null;
+
+	function finish(_, _) {
+		finished = true;
+	}
+
+	@:coroutine function yield(value:T) {
+		nextValue = value;
+		Coroutine.suspend(cont -> nextStep = cont);
+	}
+
+	function hasNext():Bool {
+		if (nextStep == null) {
+			nextStep = f.create(yield, finish);
+			nextStep(null, null);
+		}
+		return !finished;
+	}
+
+	function next():T {
+		var value = nextValue;
+		nextStep(null, null);
+		return value;
+	}
+
+	return {hasNext: hasNext, next: next};
+}
+
+private typedef Tree<T> = {
+	var leaf:T;
+	var ?left:Tree<T>;
+	var ?right:Tree<T>;
+}

+ 77 - 0
tests/misc/coroutines/src/TestJsPromise.hx

@@ -0,0 +1,77 @@
+import js.lib.Error;
+import js.lib.Promise;
+
+@:coroutine
+private function await<T>(p:Promise<T>):T {
+	return Coroutine.suspend(cont -> p.then(r -> cont(r, null), e -> cont(null, e)));
+}
+
+private function promise<T>(c:Coroutine<()->T>):Promise<T> {
+	return new Promise((resolve,reject) -> c.start((result, error) -> if (error != null) reject(error) else resolve(result)));
+}
+
+class TestJsPromise extends utest.Test {
+	function testAwait(async:Async) {
+		var p = Promise.resolve(41);
+
+		@:coroutine function awaiting() {
+			var x = await(p);
+			return x + 1;
+		}
+
+		awaiting.start((result,error) -> {
+			Assert.equals(42, result);
+			async.done();
+		});
+	}
+
+	function testPromise(async:Async) {
+		var p = promise(() -> 42);
+		p.then(result -> {
+			Assert.equals(42, result);
+			async.done();
+		});
+	}
+
+	function testAsyncAwait(async:Async) {
+		var p1 = Promise.resolve(41);
+
+		var p2 = promise(() -> {
+			var x = await(p1);
+			return x + 1;
+		});
+
+		p2.then(result -> {
+			Assert.equals(42, result);
+			async.done();
+		});
+	}
+
+	function testAwaitRejected(async:Async) {
+		var p = Promise.reject("oh no");
+
+		@:coroutine function awaiting() {
+			var x = await(p);
+			return x + 1;
+		}
+
+		awaiting.start((result,error) -> {
+			Assert.equals("oh no", error);
+			async.done();
+		});
+	}
+
+	function testThrowInPromise(async:Async) {
+		var p = promise(() -> throw new Error("oh no"));
+		p.then(
+			function(result) {
+				Assert.fail();
+			},
+			function(error) {
+				Assert.isOfType(error, Error);
+				Assert.equals("oh no", (error : Error).message);
+				async.done();
+			}
+		);
+	}
+}

+ 2 - 0
tests/misc/coroutines/src/import.hx

@@ -0,0 +1,2 @@
+import utest.Assert;
+import utest.Async;

+ 4 - 0
tests/runci/targets/Js.hx

@@ -76,6 +76,10 @@ class Js {
 		changeDirectory(getMiscSubDir("es6"));
 		runCommand("haxe", ["run.hxml"]);
 
+		infoMsg("Test coroutines:");
+		changeDirectory(getMiscSubDir("coroutines"));
+		runCommand("haxe", ["build.hxml"]);
+
 		haxelibInstallGit("HaxeFoundation", "hxnodejs");
 		final env = Sys.environment();
 		if (