PageRenderTime 126ms CodeModel.GetById 2ms app.highlight 112ms RepoModel.GetById 1ms app.codeStats 0ms

/src/xf/nucleus/graph/KernelGraphOps.d

https://bitbucket.org/h3r3tic/boxen
D | 1887 lines | 1357 code | 282 blank | 248 comment | 159 complexity | 16557f717d13441de86ccca3eee2d0c7 MD5 | raw file
   1module xf.nucleus.graph.KernelGraphOps;
   2
   3private {
   4	import xf.Common;
   5	import xf.nucleus.Function;
   6	import xf.nucleus.KernelImpl;
   7	import xf.nucleus.kernel.KernelDef;
   8	import xf.nucleus.graph.KernelGraph;
   9
  10	// for the conversion
  11	import xf.nucleus.Param;
  12	import xf.nucleus.TypeSystem;
  13	import xf.nucleus.graph.Graph;
  14	import xf.nucleus.graph.GraphOps;
  15	import xf.nucleus.TypeConversion;
  16	import xf.mem.StackBuffer;
  17	import xf.mem.SmallTempArray;
  18	import xf.utils.LocalArray;
  19	import xf.utils.FormatTmp;
  20	import tango.text.convert.Format;
  21	// ----
  22
  23	import xf.nucleus.Log : error = nucleusError, log = nucleusLog;
  24}
  25
  26public {
  27	import xf.nucleus.graph.GraphDefs;
  28}
  29
  30
  31
  32struct ConvCtx {
  33	SemanticConverterIter
  34			semanticConverters;
  35			
  36	bool delegate(cstring, KernelImpl*)
  37			getKernel;
  38}
  39
  40
  41/**
  42 * Whether to insert converters between regular graph nodes and an Output.
  43 * It's useful not to perform the conversion when fusing graphs together, so
  44 * that a conversion forth and back may be avoided without an opitimization step.
  45 * In such a case, Skip would be selected.
  46 *
  47 * Conversely, the data must be fully converted for nodes that escape the graph,
  48 * that is, Output (or Rasterize) nodes. In this case, go with Perform
  49 */
  50enum OutputNodeConversion {
  51	Perform,
  52	Skip
  53}
  54
  55
  56/**
  57 * Finds the output parameter which is connected to a given /input/
  58 * parameter in the graph. Essentially - the source of data flow
  59 */
  60bool findSrcParam(
  61	KernelGraph kg,
  62	GraphNodeId dstNid,
  63	cstring dstParam,
  64	GraphNodeId* srcNid,
  65	Param** srcParam
  66) {
  67	foreach (src; kg.flow.iterIncomingConnections(dstNid)) {
  68		foreach (fl; kg.flow.iterDataFlow(src, dstNid)) {
  69			if (fl.to == dstParam) {
  70				*srcNid = src;
  71				*srcParam = kg.getNode(src).getOutputParam(fl.from);
  72				return true;
  73			}
  74		}
  75	}
  76
  77	return false;
  78}
  79
  80
  81/**
  82 * Acquires an output parameter from the node if it's a regular one,
  83 * or tracks it in the graph and returns the connected one if it's an
  84 * Output node.
  85 *
  86 * Use it to obtain an output param of a node to which you may want to
  87 * connect something. Or basically to get the 'real' output when
  88 * Output nodes are involved
  89 */
  90bool getOutputParamIndirect(
  91	KernelGraph kg,
  92	GraphNodeId dstNid,
  93	cstring dstParam,
  94	GraphNodeId* srcNid,
  95	Param** srcParam
  96) {
  97	final node = kg.getNode(dstNid);
  98
  99	/+if (KernelGraph.NodeType.Bridge == node.type && ) {
 100		if (
 101	} else +/if (KernelGraph.NodeType.Output == node.type) {
 102		return findSrcParam(kg, dstNid, dstParam, srcNid, srcParam);
 103	} else {
 104		if ((*srcParam = node.getOutputParam(dstParam)) !is null) {
 105			*srcNid = dstNid;
 106			return true;
 107		} else {
 108			return false;
 109		}
 110	}
 111}
 112
 113
 114/**
 115 * Connect two disjoint subgraphs by the means of an explicitly marked input node
 116 * 
 117 * Auto flow is performed as if the two graphs were auto-connected in separation,
 118 * yet some conversions are potentially avoided.
 119 *
 120 * This operation removes the input node if it's of the Input type or leaves it be
 121 * if it's a different node. This makes it possible e.g. to connect a complete graph
 122 * with designated Input and Output nodes to an existing graph, or just tack a single
 123 * note into one.
 124 *
 125 * The _findSrcParam callback will have to provide the parameters on the input side
 126 * of the Output node in the existing (connected, converted) graph.
 127 * 
 128 * Note: currently the callback is queried via the names of the /Input/ node
 129 * in the destination graph
 130 */
 131void fuseGraph(
 132	KernelGraph	graph,
 133	GraphNodeId	input,
 134	ConvCtx ctx,
 135	GraphNodeId[] dstGraphTopological,
 136	bool delegate(
 137		Param* dstParam,
 138		GraphNodeId* srcNid,
 139		Param** srcParam
 140	) _findSrcParam,
 141	OutputNodeConversion outNodeConversion
 142) {
 143	return fuseGraph(
 144		graph,
 145		input,
 146		ctx,
 147		(int delegate(ref GraphNodeId) sink) {
 148			foreach (nid; dstGraphTopological) {
 149				if (int r = sink(nid)) {
 150					return r;
 151				}
 152			}
 153			return 0;
 154		},
 155		_findSrcParam,
 156		outNodeConversion
 157	);
 158}
 159
 160
 161/// ditto
 162void fuseGraph(
 163	KernelGraph	graph,
 164	GraphNodeId	input,
 165	ConvCtx ctx,
 166	int delegate(int delegate(ref GraphNodeId)) dstGraphTopological,
 167	bool delegate(
 168		Param* dstParam,
 169		GraphNodeId* srcNid,
 170		Param** srcParam
 171	) _findSrcParam,
 172	OutputNodeConversion outNodeConversion
 173) {
 174	scope stack = new StackBuffer;
 175
 176	alias KernelGraph.NodeType NT;
 177
 178	foreach (dummy; graph.flow.iterIncomingConnections(input)) {
 179		error(
 180			"The 'input' node may not have any incoming connections"
 181			" prior to calling fuseGraph"
 182		);
 183	}
 184
 185	bool inputNodeIsFunc = KernelGraph.NodeType.Input != graph.getNode(input).type;
 186
 187	// A list of all output ports on the Input node, to be used in custom
 188	// auto flow port generation
 189	final outputPorts = LocalDynArray!(NodeParam)(stack);
 190	foreach (ref fromParam; *graph.getNode(input).getParamList()) {
 191		if (fromParam.isOutput) {
 192			outputPorts.pushBack(NodeParam(input, &fromParam));
 193		}
 194	}						
 195
 196	bool isOutputNode(GraphNodeId id) {
 197		return NT.Output == graph.getNode(id).type;
 198	}
 199
 200	// Do auto flow for the second graph, extending the incoming flow of the
 201	// input node to the inputs of the output node from the first graph
 202	foreach (id; dstGraphTopological) {
 203		if (inputNodeIsFunc && input == id) {
 204			/*
 205			 * This is a special case for when the destination graph's /input/
 206			 * node it not of the Input type but e.g. a Func node. If this wasn't
 207			 * the case, we'd be connecting the Input node's /successors/ to the
 208			 * source graph in a more involved operation. This case is simpler
 209			 * and only involves connecting the input node directly to what's
 210			 * in the source graph.
 211			 */
 212			doAutoFlow(
 213				graph,
 214				id,
 215				ctx,
 216				OutputNodeConversion.Skip == outNodeConversion && isOutputNode(id)
 217					? FlowGenMode.DirectConnection
 218					: FlowGenMode.InsertConversionNodes,
 219				(Param* dstParam, void delegate(NodeParam) incomingSink) {
 220					GraphNodeId	fromNode;
 221					Param*		fromParam;
 222					
 223					if (!_findSrcParam(
 224						dstParam,
 225						&fromNode,
 226						&fromParam
 227					)) {
 228						assert (false,
 229							"Could not find a source parameter"
 230							" for the Output node twin to the"
 231							" Func node used in graph fusion."
 232							" This should have been triggered"
 233							" earlier, when resolving the Output."
 234							" The param was '" ~ dstParam.name ~ "'."
 235						);
 236					}
 237
 238					assert (fromParam.isOutput);
 239
 240					/*
 241					 * Add the param returned by the user to the list of nodes
 242					 * for which we're considering auto conversion. This will
 243					 * usually be just one node that the user supplies, however
 244					 * it could happen that there's e.g. a Data node connected
 245					 * to the input on the destination side, so we cover that
 246					 * case by using AutoFlow instead of a direct connection
 247					 */
 248					incomingSink(NodeParam(fromNode, fromParam));
 249				}
 250			);
 251
 252			// Convert SemanticExp to Semantic, nuff said
 253			simplifyParamSemantics(graph, id);
 254
 255		} else if (input == id) {
 256			scope stack2 = new StackBuffer;
 257			final toRemove = LocalDynArray!(ConFlow)(stack2);
 258			
 259			foreach (outCon; graph.flow.iterOutgoingConnections(input)) {
 260				if (OutputNodeConversion.Perform == outNodeConversion || !isOutputNode(outCon)) {
 261					foreach (outFl; graph.flow.iterDataFlow(input, outCon)) {
 262						GraphNodeId	fromNode;
 263						Param*		fromParam;
 264
 265						if (Param* fromTmp = graph.getNode(input).getOutputParam(outFl.from)) {
 266							if (!_findSrcParam(
 267								fromTmp,
 268								&fromNode,
 269								&fromParam
 270							)) {
 271								error(
 272									"_findSrcParam returned false for param '{}' and node {}.",
 273									fromTmp.name,
 274									input.id
 275								);
 276							}
 277
 278							if (doManualFlow(
 279								graph,
 280								fromNode, outCon,
 281								DataFlow(fromParam.name, outFl.to),
 282								ctx
 283							)) {
 284								toRemove.pushBack(ConFlow(
 285									input, outFl.from,
 286									outCon, outFl.to
 287								));
 288							}
 289						} else {
 290							error(
 291								"Src param '{}' not found in node {}.",
 292								outFl.from,
 293								input.id
 294							);
 295						}
 296					}
 297				}
 298			}
 299
 300			foreach (rem; toRemove) {
 301				graph.flow.removeDataFlow(rem.fromNode, rem.fromParam, rem.toNode, rem.toParam);
 302			}
 303		} else {
 304			doAutoFlow(
 305				graph,
 306				id,
 307				ctx,
 308				OutputNodeConversion.Skip == outNodeConversion && isOutputNode(id)
 309					? FlowGenMode.DirectConnection
 310					: FlowGenMode.InsertConversionNodes,
 311
 312				/* Using custom enumeration of incoming nodes/ports
 313				 * because we'll need to treat the Input node specially.
 314				 *
 315				 * Basically, we want the semantics of connecting the Output
 316				 * and Input nodes together and resolving auto flow regularly.
 317				 * This is not done in such a straightforward way, since
 318				 * 1) Can't connect an Output node to an Input node, because
 319				 *    Output nodes only have _input_ ports and Input nodes
 320				 *    only have _output_ ports.
 321				 * 2) There might be some additional conversions done in the
 322				 *    straightforward approach. They would subsequently require
 323				 *    an optimization pass.
 324				 */
 325				(Param* toParam, void delegate(NodeParam) incomingSink) {
 326					foreach (fromId; graph.flow.iterIncomingConnections(id)) {
 327						/* Ordinary stuff so far */
 328						if (graph.flow.hasAutoFlow(fromId, id)) {
 329
 330							/* Now, if this node has automatic flow from the
 331							 * Input node, we will want to override it. Only in case
 332							 * that the input node is of the Input type. The other
 333							 * case is done for in the first clause of the top-most
 334							 * conditional in this function.
 335							 */
 336							if (!inputNodeIsFunc && input == fromId) {
 337								/* First, we figure out to which port auto
 338								 * flow would connect this param to, but we
 339								 * don't connect to it and instead dig deeper
 340								 */
 341
 342								findAutoFlow(
 343									graph,
 344									outputPorts.data,
 345									id,
 346									toParam,
 347									ctx,
 348
 349									/* This dg will receive the port to which
 350									 * auto flow would resolve to on the
 351									 * side of the Input node
 352									 */
 353									(	ConvSinkItem[] convChain,
 354										GraphNodeId intermediateId,
 355										Param* intermediateParam
 356									) {
 357										if (input == intermediateId) {
 358											/* Now, the Input node is the graph2-side
 359											 * connector, which is a twin to an
 360											 * Output node in graph1 and which has
 361											 * incoming params connected to. We now
 362											 * find which param connects to the port
 363											 * equivalent to what we just identified
 364											 * for the Input node.
 365											 */
 366
 367											GraphNodeId	fromNode;
 368											Param*		fromParam;
 369											
 370											if (!_findSrcParam(
 371												toParam,
 372												&fromNode,
 373												&fromParam
 374											)) {
 375												assert (false,
 376													"Could not find a source parameter"
 377													" for the Output node twin to the"
 378													" Input node used in graph fusion."
 379													" This should have been triggered"
 380													" earlier, when resolving the Output."
 381												);
 382											}
 383
 384											assert (fromParam.isOutput);
 385
 386											/* Finally, the original port from graph1
 387											 * is added to the list of all connections
 388											 * to consider for the auto flow resolving
 389											 * process for the particular param we're
 390											 * evaluating a few scopes higher :P
 391											 */
 392
 393											incomingSink(NodeParam(fromNode, fromParam));
 394										} else {
 395											incomingSink(NodeParam(
 396												intermediateId,
 397												intermediateParam
 398											));
 399										}
 400									},
 401
 402									/*
 403									 * This call to findAutoFlow doesn't need to find
 404									 * sources for flow into all of the inputs, as
 405									 * the outer doAutoFlow may decide to use other
 406									 * nodes as well, say when using Data nodes
 407									 * in addition to external input
 408									 */
 409									ErrorHandlingMode.Ignore
 410								);
 411							} else {
 412								/* Data flow from a node different than the
 413								 * Input node. Regular stuff
 414								 */
 415								
 416								final fromNode = graph.getNode(fromId);
 417								ParamList* fromParams = fromNode.getParamList();
 418
 419								foreach (ref fromParam; *fromParams) {
 420									if (fromParam.isOutput) {
 421										incomingSink(NodeParam(fromId, &fromParam));
 422									}
 423								}
 424							}
 425						}
 426					}
 427				}
 428			);
 429
 430			// Convert SemanticExp to Semantic, nuff said
 431			simplifyParamSemantics(graph, id);
 432
 433			{
 434				scope stack2 = new StackBuffer;
 435				final toRemove = LocalDynArray!(ConFlow)(stack2);
 436
 437				foreach (con; graph.flow.iterOutgoingConnections(id)) {
 438					if (OutputNodeConversion.Perform == outNodeConversion || !isOutputNode(con)) {
 439						foreach (fl; graph.flow.iterDataFlow(id, con)) {
 440							if (doManualFlow(
 441								graph,
 442								id,
 443								con, fl,
 444								ctx
 445							)) {
 446								toRemove.pushBack(ConFlow(
 447									id, fl.from,
 448									con, fl.to
 449								));
 450							}
 451						}
 452					}
 453				}
 454
 455				foreach (rem; toRemove) {
 456					graph.flow.removeDataFlow(rem.fromNode, rem.fromParam, rem.toNode, rem.toParam);
 457				}
 458			}
 459		}
 460	}
 461
 462	// The Input node is useless now, but don't remove any other node types
 463	if (KernelGraph.NodeType.Input == graph.getNode(input).type) {
 464		graph.removeNode(input);
 465	}
 466}
 467
 468
 469/**
 470 * Redirects the data flow from the 'output' node into the nodes connected
 471 * to the 'input' node, then removes both nodes, thus fusing two separate
 472 * sub-graphs.
 473 *
 474 * This should optimally be called before resolving auto flow, so that
 475 * some conversions may potentially be avoided.
 476 */
 477void fuseGraph(
 478	KernelGraph	graph,
 479	GraphNodeId	output,
 480	int delegate(int delegate(ref GraphNodeId)) graph1NodeIter,
 481	GraphNodeId	input,
 482	ConvCtx ctx,
 483	OutputNodeConversion outNodeConversion
 484) {
 485	scope stack = new StackBuffer;
 486
 487	foreach (dummy; graph.flow.iterOutgoingConnections(output)) {
 488		error(
 489			"The 'output' node may not have any outgoing connections"
 490			" prior to calling fuseGraph"
 491		);
 492	}
 493
 494	// Not disposed anywhere since its storage is on the StackBuffer
 495	DynamicBitSet graph1Nodes;
 496	
 497	graph1Nodes.alloc(graph.capacity, &stack.allocRaw);
 498	graph1Nodes.clearAll();
 499
 500	//markPrecedingNodes(graph.backend_readOnly, &graph1Nodes, null, output);
 501	foreach (nid; graph1NodeIter) {
 502		graph1Nodes.set(nid.id);
 503	}
 504
 505	final topological = stack.allocArray!(GraphNodeId)(graph.numNodes);
 506	findTopologicalOrder(graph.backend_readOnly, topological);
 507
 508	int iterGraph1Nodes(int delegate(ref GraphNodeId) sink) {
 509		foreach (id; topological) {
 510			if (graph1Nodes.isSet(id.id)) {
 511				if (int r = sink(id)) {
 512					return r;
 513				}
 514			}
 515		}
 516		return 0;
 517	}
 518
 519	int iterGraph2Nodes(int delegate(ref GraphNodeId) sink) {
 520		foreach (id; topological) {
 521			if (!graph1Nodes.isSet(id.id)) {
 522				if (int r = sink(id)) {
 523					return r;
 524				}
 525			}
 526		}
 527		return 0;
 528	}
 529
 530	convertGraphDataFlowExceptOutput(
 531		graph,
 532		ctx,
 533		&iterGraph1Nodes
 534	);
 535
 536	bool outputNodeIsFunc = KernelGraph.NodeType.Output != graph.getNode(output).type;
 537
 538	fuseGraph(
 539		graph,
 540		input,
 541		ctx,
 542		&iterGraph2Nodes,
 543		(
 544			Param* dstParam,
 545			GraphNodeId* srcNid,
 546			Param** srcParam
 547		) {
 548			/*
 549			 * We perform the lookup by name only (TODO?).
 550			 */
 551
 552			return .getOutputParamIndirect(
 553				graph,
 554				output,
 555				dstParam.name,
 556				srcNid,
 557				srcParam
 558			);
 559		},
 560		outNodeConversion
 561	);
 562
 563	// The Output node is useless now, but don't remove any other node types
 564	if (!outputNodeIsFunc) {
 565		graph.removeNode(output);
 566	}
 567	
 568	graph.flow.removeAllAutoFlow();
 569}
 570
 571
 572enum FlowGenMode {
 573	InsertConversionNodes,
 574	DirectConnection,
 575	NoAction
 576}
 577
 578
 579struct NodeParam {
 580	GraphNodeId	node;
 581	Param*		param;
 582}
 583
 584
 585private struct ConFlow {
 586	GraphNodeId	fromNode;
 587	cstring		fromParam;
 588	GraphNodeId	toNode;
 589	cstring		toParam;
 590}
 591
 592
 593void verifyDataFlowNames(KernelGraph graph) {
 594	final flow = graph.flow();
 595	
 596	foreach (fromId; graph.iterNodes) {
 597		final fromNode = graph.getNode(fromId);
 598		
 599		foreach (toId; flow.iterOutgoingConnections(fromId)) {
 600			final toNode = graph.getNode(toId);
 601			
 602			foreach (fl; flow.iterDataFlow(fromId, toId)) {
 603				final op = fromNode.getOutputParam(fl.from);
 604				if (op is null) {
 605					error(
 606						"verifyDataFlowNames: The source node for flow {}->{}"
 607						" doesn't have an output parameter called '{}'",
 608						fromId.id, toId.id, fl.from
 609					);
 610				}
 611				assert (op.isOutput);
 612
 613				final ip = toNode.getInputParam(fl.to);
 614				if (ip is null) {
 615					error(
 616						"verifyDataFlowNames: The target node for flow {}->{}"
 617						" doesn't have an input parameter called '{}'",
 618						fromId.id, toId.id, fl.to
 619					);
 620				}
 621				assert (ip.isInput);
 622				assert (ip.hasPlainSemantic);
 623			}
 624		}
 625	}
 626
 627	scope stack = new StackBuffer;
 628
 629	foreach (toId; graph.iterNodes) {
 630		final toNode = graph.getNode(toId);
 631		ParamList* plist;
 632		
 633		if (KernelGraph.NodeType.Kernel == toNode.type) {
 634			plist = &toNode.kernel.kernel.func.params;
 635		} else {
 636			plist = toNode.getParamList();
 637		}
 638
 639		DynamicBitSet paramHasInput;
 640		paramHasInput.alloc(plist.length, &stack.allocRaw);
 641		paramHasInput.clearAll();
 642
 643		void onDuplicateFlow(cstring to) {
 644			cstring[] sources;
 645
 646			foreach (fromId; flow.iterIncomingConnections(toId)) {
 647				final fromNode = graph.getNode(fromId);
 648			
 649				foreach (fl; flow.iterDataFlow(fromId, toId)) {
 650					if (fl.to == to) {
 651						sources ~= Format("{}.{}", fromId.id, fl.from);
 652					}
 653				}
 654			}
 655
 656			error(
 657				"Duplicate flow to {}.{} from {}.",
 658				toId.id, to, sources
 659			);
 660		}
 661
 662		foreach (fromId; flow.iterIncomingConnections(toId)) {
 663			final fromNode = graph.getNode(fromId);
 664		
 665			foreach (fl; flow.iterDataFlow(fromId, toId)) {
 666				Param* dst;
 667				if (plist.getInput(fl.to, &dst)) {
 668					final idx = plist.indexOf(dst);
 669					if (paramHasInput.isSet(idx)) {
 670						onDuplicateFlow(fl.to);
 671					} else {
 672						paramHasInput.set(idx);
 673					}
 674				} else {
 675					error(
 676						"verifyDataFlowNames: The target node for flow {}->{}"
 677						" doesn't have an input parameter called '{}'",
 678						fromId.id, toId.id, fl.to
 679					);
 680				}
 681			}
 682		}
 683	}
 684}
 685
 686
 687private cstring _fmtChain(ConvSinkItem[] chain) {
 688	cstring res;
 689	foreach (c; chain) {
 690		res ~= Format(
 691			"    {} -> <{}>\n",
 692			c.converter.func.name,
 693			c.afterConversion.toString
 694		);
 695	}
 696	return res;
 697}
 698
 699
 700// returns true if any new connections were inserted
 701private bool _insertConversionNodes(
 702	KernelGraph graph,
 703	ConvSinkItem[] chain,
 704	GraphNodeId fromId,
 705	Param* fromParam,
 706	GraphNodeId toId,
 707	Param* toParam
 708) {
 709	GraphNodeId	srcId		= fromId;
 710	Param*		srcParam	= fromParam;
 711
 712	foreach (c; chain) {
 713		final cnodeId = graph.addNode(KernelGraph.NodeType.Func);
 714		final cnode = graph.getNode(cnodeId).func();
 715		cnode.func = c.converter.func;
 716
 717		assert (2 == cnode.func.params.length);
 718		assert (cnode.func.params[0].isInput);
 719		assert (cnode.func.params[0].hasPlainSemantic);
 720		assert (cnode.func.params[1].isOutput);
 721		
 722		graph.flow.addDataFlow(
 723			srcId,
 724			srcParam.name,
 725			cnodeId,
 726			cnode.func.params[0].name
 727		);
 728		
 729		void* delegate(uword) mem = &graph._mem.pushBack;
 730		cnode.params._allocator = mem;
 731		cnode.params.add(cnode.func.params[0].dup(mem));
 732		
 733		auto p = cnode.params.add(ParamDirection.Out, cnode.func.params[1].name);
 734		p.hasPlainSemantic = true;
 735		*p.semantic() = c.afterConversion.dup(mem);
 736		// No need to care about the default value here.
 737		// No need to care about whether the param wants auto flow or not, either
 738
 739		srcId = cnodeId;
 740		srcParam = cnode.params[1];
 741	}
 742
 743	bool newFlow;
 744
 745	graph.flow.addDataFlow(
 746		srcId,
 747		srcParam.name,
 748		toId,
 749		toParam.name,
 750		&newFlow
 751	);
 752
 753	return newFlow || chain.length > 0;
 754}
 755
 756
 757private enum ErrorHandlingMode {
 758	Throw,
 759	Ignore
 760}
 761
 762
 763private void findAutoFlow(
 764	KernelGraph graph,
 765	NodeParam[] fromParams,
 766	GraphNodeId toId,
 767	Param* toParam,
 768	ConvCtx ctx,
 769	void delegate(
 770			ConvSinkItem[] convChain,
 771			GraphNodeId fromId,
 772			Param* fromParam
 773	) result,
 774	ErrorHandlingMode errorHandlingMode = ErrorHandlingMode.Throw
 775) {
 776	scope stack = new StackBuffer;
 777
 778	struct ConvInfo {
 779		ConvSinkItem[]	chain;
 780		GraphNodeId		fromId;
 781		Param*			fromParam;
 782		ConvInfo*		next;
 783	}
 784	
 785	int			bestCost = int.max;
 786	ConvInfo*	bestConvs;
 787
 788	foreach (from_; fromParams) {
 789		GraphNodeId	fromId = from_.node;
 790		Param*		fromParam = from_.param;
 791		
 792		log.trace("findAutoFlow {} -> {}", fromId.id, toId.id);
 793
 794		scope stack2 = new StackBuffer;
 795		
 796		assert (fromParam.isOutput);
 797		
 798		int convCost = 0;
 799
 800		assert (toParam.isInput);
 801		assert (fromParam.hasPlainSemantic, fromParam.name);
 802		assert (toParam.hasPlainSemantic, toParam.name);
 803		
 804		findConversion(
 805			*fromParam.semantic,
 806			*toParam.semantic,
 807			ctx.semanticConverters,
 808			stack2,
 809			(ConvSinkItem[] convChain) {
 810				if (convCost > bestCost) {
 811					return;
 812				}
 813				
 814				stack2.mergeWith(stack);
 815				final info = stack._new!(ConvInfo)(
 816					convChain,
 817					fromId,
 818					fromParam,
 819					cast(ConvInfo*)null		// stupid DMD
 820				);
 821				
 822				if (convCost < bestCost) {
 823					// replace
 824					bestConvs = info;
 825					bestCost = convCost;
 826				} else if (convCost == bestCost) {
 827					// add
 828					info.next = bestConvs;
 829					bestConvs = info;
 830				}
 831			},
 832			&convCost
 833		);
 834	}
 835
 836	if (bestCost < int.max) {
 837		assert (bestConvs !is null);
 838		if (bestConvs.next !is null) {
 839			// Ambiguity error
 840			
 841			cstring errMsg = Format(
 842				"Auto flow ambiguity while trying to find flow to an input param"
 843				" '{}' in graph node {}.\n"
 844				"Found multiple conversion paths with the cost {}:\n",
 845				toParam.toString, toId.id, bestCost
 846			);
 847
 848			for (auto it = bestConvs; it !is null; it = it.next) {
 849				errMsg ~= Format(
 850					"  Path from node {}:\n",
 851					it.fromId.id
 852				);
 853				errMsg ~= _fmtChain(it.chain);
 854			}
 855
 856			error("{}", errMsg);
 857		} else {
 858			// Found a unique conversion path
 859
 860			result(
 861				bestConvs.chain,
 862				bestConvs.fromId, bestConvs.fromParam
 863			);
 864		}
 865	} else if (ErrorHandlingMode.Throw == errorHandlingMode) {
 866		// Conversion path not found
 867
 868		uword numConv = 0;
 869		cstring suffix;
 870		
 871		foreach (from_; fromParams) {
 872			++numConv;
 873			if (suffix is null) {
 874				suffix = Format("{}.{}", from_.node.id, from_.param.name);
 875			} else {
 876				suffix ~= Format(", {}.{}", from_.node.id, from_.param.name);
 877			}
 878		}
 879
 880		assert (toParam.wantAutoFlow, toParam.toString);
 881
 882		// This error probably shouldn't even be triggered, as the
 883		// node may be unused.
 884		/+error(
 885			"Auto flow not found for an input param"
 886			" '{}' in graph node {}.\n"
 887			"Considered {} converters from:\n[{}]",
 888			toParam.toString, toId.id, numConv, suffix
 889		);+/
 890	}
 891}
 892
 893
 894private void simplifyParamSemantics(KernelGraph graph, GraphNodeId id) {
 895	final node = graph.getNode(id);
 896
 897	if (KernelGraph.NodeType.Kernel == node.type) {
 898		foreach (ref Param par; node.kernel.kernel.func.params) {
 899			if (!par.hasPlainSemantic) {
 900				error("Special Kernel-type nodes must only have plain semantics.");
 901			}
 902		}
 903	}
 904
 905	else if (KernelGraph.NodeType.Func == node.type) {
 906		final params = &node.func.params;
 907		foreach (ref Param par; *params) {
 908			if (par.isInput || par.hasPlainSemantic) {
 909				continue;
 910			}
 911
 912			Semantic plainSem = Semantic(&graph._mem.pushBack);
 913
 914			findOutputSemantic(
 915				&par,
 916
 917				// getFormalParamSemantic
 918				(cstring name) {
 919					foreach (ref Param p; *params) {
 920						if (p.isInput && p.name == name) {
 921							return *p.semantic();
 922						}
 923					}
 924					error(
 925						"simplifyParamSemantics: output param '{}' refers to a"
 926						" nonexistent formal parameter '{}'.",
 927						par.name,
 928						name
 929					);
 930					assert (false);
 931				},
 932
 933				// getActualParamSemantic
 934				(cstring name) {
 935					GraphNodeId	fromId;
 936					cstring		fromName;
 937
 938					// Note: O (cons * flow * inputPorts). Maybe too slow?
 939					foreach (fromId_; graph.flow.iterIncomingConnections(id)) {
 940						foreach (fl; graph.flow.iterDataFlow(fromId_, id)) {
 941							if (fl.to == name) {
 942								if (fromName !is null) {
 943									error(
 944										"The semantic conversion process introduced"
 945										" duplicate flow\n    to {}.{}"
 946										" from {}.{} and {}.{}.",
 947										id.id, name,
 948										fromId.id, fromName,
 949										fromId_.id, fl.from
 950									);
 951								}
 952								
 953								fromId = fromId_;
 954								fromName = fl.from;
 955							}
 956						}
 957					}
 958
 959					if (fromName !is null) {
 960						Param* dstParam;
 961						if (params.getInput(name, &dstParam)) {
 962							assert (dstParam !is null);
 963							if (!dstParam.wantAutoFlow) {
 964								error(
 965									"Output param {} of kernel {} used by node {}"
 966									" has an semantic expression using the actual"
 967									" parameter of the input {}, which has been"
 968									" marked as noauto and has no direct connection.",
 969									par.name, node.func.func.name, id.id,
 970									name
 971								);
 972							}
 973						} else {
 974							assert (false, name);
 975						}
 976
 977						return *graph.getNode(fromId)
 978							.getOutputParam(fromName).semantic();
 979					} else {
 980						assert (false, "simplifyParamSemantics: No flow to " ~ name);
 981					}
 982				},
 983				
 984				&plainSem
 985			);
 986
 987			par.hasPlainSemantic = true;
 988			*par.semantic() = plainSem;
 989		}
 990
 991		foreach (ref Param par; *params) {
 992			if (par.isOutput) {
 993				continue;
 994			}
 995
 996			GraphNodeId	fromId;
 997			cstring		fromName;
 998
 999			// Note: O (cons * flow * inputPorts). Maybe too slow?
1000			foreach (fromId_; graph.flow.iterIncomingConnections(id)) {
1001				foreach (fl; graph.flow.iterDataFlow(fromId_, id)) {
1002					if (fl.to == par.name) {
1003						if (fromName !is null) {
1004							error(
1005								"The semantic conversion process introduced"
1006								" duplicate flow\n    to {}.{}"
1007								" from {}.{} and {}.{}.",
1008								id.id, par.name,
1009								fromId.id, fromName,
1010								fromId_.id, fl.from
1011							);
1012						}
1013						
1014						fromId = fromId_;
1015						fromName = fl.from;
1016					}
1017				}
1018			}
1019
1020			if (fromName is null) {
1021				if (par.wantAutoFlow) {
1022					// the loop is to test whether there are any outgoing
1023					// connections at all
1024					foreach (id; graph.flow.iterOutgoingConnections(id)) {
1025						error("No flow to '{}' D:", par.name);
1026					}
1027				}
1028			} else {
1029				final srcParam = graph.getNode(fromId)
1030					.getOutputParam(fromName);
1031
1032				*par.semantic() = *srcParam.semantic();
1033			}
1034		}
1035	}
1036}
1037
1038
1039private void doAutoFlow(
1040		KernelGraph graph,
1041		GraphNodeId toId,
1042		ConvCtx ctx,
1043		FlowGenMode flowGenMode
1044) {
1045	return doAutoFlow(
1046		graph,
1047		toId,
1048		ctx,
1049		flowGenMode,
1050		(Param*, void delegate(NodeParam) incomingSink) {
1051			foreach (fromId; graph.flow.iterIncomingConnections(toId)) {
1052				if (graph.flow.hasAutoFlow(fromId, toId)) {
1053					final fromNode = graph.getNode(fromId);
1054					ParamList* fromParams = fromNode.getParamList();
1055
1056					foreach (ref fromParam; *fromParams) {
1057						if (fromParam.isOutput) {
1058							incomingSink(NodeParam(fromId, &fromParam));
1059						}
1060					}						
1061				}
1062			}
1063		}
1064	);
1065}
1066
1067private void doAutoFlow(
1068		KernelGraph graph,
1069		GraphNodeId toId,
1070		ConvCtx ctx,
1071		FlowGenMode flowGenMode,
1072		void delegate(Param*, void delegate(NodeParam)) incomingGen
1073) {
1074	scope stack = new StackBuffer;
1075
1076	final toNode = graph.getNode(toId);
1077	ParamList* plist = toNode.getParamList();
1078	
1079	switch (toNode.type) {
1080		case KernelGraph.NodeType.Func:
1081		case KernelGraph.NodeType.Output:
1082		case KernelGraph.NodeType.Kernel:
1083		case KernelGraph.NodeType.Bridge:
1084			break;
1085
1086		default: return;		// just outputs here
1087	}
1088
1089	DynamicBitSet portHasDataFlow;
1090	portHasDataFlow.alloc(plist.length, (uword num) { return stack.allocRaw(num); });
1091	portHasDataFlow.clearAll();
1092
1093	foreach (i, ref param; *plist) {
1094		if (!param.wantAutoFlow) {
1095			portHasDataFlow.set(i);
1096		}
1097	}
1098
1099	// Note: O (cons * flow * inputPorts). Maybe too slow?
1100	foreach (fromId; graph.flow.iterIncomingConnections(toId)) {
1101		foreach (fl; graph.flow.iterDataFlow(fromId, toId)) {
1102			final p = plist.get(fl.to);
1103			assert (p !is null, "auto flow: fl.to missing: " ~ fl.to);
1104			portHasDataFlow.set(plist.indexOf(p));
1105		}
1106	}
1107
1108	foreach (paramI, ref param; *plist) {
1109		if (param.isInput && !portHasDataFlow.isSet(paramI)) {
1110			scope stack2 = new StackBuffer;
1111			
1112			final fromIds = LocalDynArray!(NodeParam)(stack2);
1113			
1114			incomingGen(&param, (NodeParam np) {
1115				fromIds.pushBack(np);
1116			});
1117
1118			findAutoFlow(graph, fromIds.data, toId, &param, ctx,
1119				(	ConvSinkItem[] convChain,
1120					GraphNodeId fromId,
1121					Param* fromParam
1122				) {
1123					switch (flowGenMode) {
1124						case FlowGenMode.InsertConversionNodes: {
1125							log.info("Found a conversion path. Inserting nodes.");
1126
1127							_insertConversionNodes(
1128								graph,
1129								convChain,
1130								fromId, fromParam,
1131								toId, &param
1132							);
1133						} break;
1134
1135						case FlowGenMode.DirectConnection: {
1136							graph.flow.addDataFlow(
1137								fromId, fromParam.name,
1138								toId, param.name
1139							);
1140						} break;
1141
1142						default: assert (false);
1143					}
1144				}
1145			);
1146		}
1147	}
1148}
1149
1150
1151private bool isTypeKernel(cstring type, KernelImpl* info, ConvCtx ctx) {
1152	return ctx.getKernel(type, info);
1153}
1154
1155
1156/*
1157 * Returns true if the node or its incoming subtree contains free params
1158 * not filtered out by the supplied delegate
1159 */
1160private bool buildFunctionSubgraph(
1161		KernelGraph graph,
1162		GraphNodeId id,
1163		bool delegate(Param*) paramFilter,
1164		
1165		void delegate(Param*, GraphNodeId) freeParamSink,
1166		DynamicBitSet* clonedNodesSet,
1167		DynamicBitSet* visitedNodesSet,
1168		GraphNodeId[] oldToNew,
1169		KernelGraph newGraph,
1170		GraphNodeId newGraphDataNode,
1171		GraphNodeId oldGraphCompNode,
1172		GraphNodeId* newNode
1173) {
1174	if (clonedNodesSet.isSet(id.id)) {
1175		*newNode = oldToNew[id.id];
1176		assert (newNode.valid);
1177		return true;
1178	} else if (visitedNodesSet.isSet(id.id)) {
1179		return false;
1180	}
1181	visitedNodesSet.set(id.id);
1182	
1183	//log.trace("buildFunctionSubgraph");
1184	
1185	bool result = false;
1186	scope stack = new StackBuffer;
1187
1188	ParamList* plist = graph.getNode(id).getParamList();
1189
1190	DynamicBitSet paramHasInput;
1191	paramHasInput.alloc(plist.length, &stack.allocRaw);
1192	paramHasInput.clearAll();
1193
1194	foreach (from; graph.flow.iterIncomingConnections(id)) {
1195		foreach (fl; graph.flow.iterDataFlow(from, id)) {
1196			Param* dst;
1197			if (!plist.getInput(fl.to, &dst)) {
1198				error(
1199					"Shit happened. Destination flow invalid for {}.{}->{}.{}",
1200					from.id, fl.from, id.id, fl.to
1201				);
1202			} else {
1203				paramHasInput.set(plist.indexOf(dst));
1204			}
1205		}
1206
1207		GraphNodeId prevNode;
1208		
1209		//log.trace("buildFunctionSubgraph -> buildFunctionSubgraph");
1210
1211		if (buildFunctionSubgraph(
1212			graph,
1213			from,
1214			paramFilter,
1215			freeParamSink,
1216			clonedNodesSet,
1217			visitedNodesSet,
1218			oldToNew,
1219			newGraph,
1220			newGraphDataNode,
1221			oldGraphCompNode,
1222			&prevNode
1223		)) {
1224			if (!result) {
1225				auto node = graph.getNode(id);
1226				*newNode = newGraph.addNode(node.type);
1227				oldToNew[id.id] = *newNode;
1228				node.copyTo(newGraph.getNode(*newNode));
1229				clonedNodesSet.set(id.id);
1230				result = true;
1231			}
1232
1233			foreach (fl; graph.flow.iterDataFlow(from, id)) {
1234				newGraph.flow.addDataFlow(prevNode, fl.from, *newNode, fl.to);
1235			}
1236		}
1237
1238		//log.trace("buildFunctionSubgraph returned.");
1239	}
1240
1241	foreach (i, ref param; *plist) {
1242		if (!param.isInput) {
1243			continue;
1244		}
1245		
1246		if (!paramHasInput.isSet(i)) {
1247			if (paramFilter(&param)) {
1248				if (!result) {
1249					auto node = graph.getNode(id);
1250					*newNode = newGraph.addNode(node.type);
1251					oldToNew[id.id] = *newNode;
1252					node.copyTo(newGraph.getNode(*newNode));
1253					clonedNodesSet.set(id.id);
1254					result = true;
1255				}
1256				
1257				freeParamSink(
1258					newGraph.getNode(*newNode).getInputParam(param.name),
1259					*newNode
1260				);
1261			}
1262		}
1263	}
1264
1265	if (result) {
1266		auto dataNode = newGraph.getNode(newGraphDataNode).data();
1267		auto oldCompNode = graph.getNode(oldGraphCompNode).composite();
1268		
1269		foreach (i, ref param; *plist) {
1270			if (!param.isInput) {
1271				continue;
1272			}
1273
1274			if (paramHasInput.isSet(i)) {
1275				// This param will have to be bridged via the Data node
1276				
1277				formatTmp((Fmt fmt) {
1278					fmt.format("comp__p{}", dataNode.params.length);
1279				},
1280				(cstring pname) {
1281					/*
1282					 * Find where from the flow comes into the param back in the
1283					 * original graph. We'll copy the flow into the function node
1284					 * which graph the subgraph we're constructing.
1285					 */
1286					GraphNodeId	oldSrcNid;
1287					Param*		oldSrcParam;
1288
1289					if (!findSrcParam(
1290						graph,
1291						id,
1292						param.name,
1293						&oldSrcNid,
1294						&oldSrcParam
1295					)) {
1296						error("Huh. Flow not found to param {}. But... but... D:", param.name);
1297					}
1298
1299					/*
1300					 * Only copy the flow if the source is not already in the cloned
1301					 * subgraph. Otherwise the flow would be duplicate
1302					 */
1303					if (!clonedNodesSet.isSet(oldSrcNid.id)) {
1304						/*
1305						 * Add a param to the data node in the new subgraph.
1306						 * Use a unique name for it.
1307						 */
1308						auto dparam = dataNode.params.add(param, pname);
1309						dparam.dir = ParamDirection.Out;
1310
1311						/*
1312						 * Now add flow from the newly added Data node param to the
1313						 * node we've just cloned as the main part of this function.
1314						 */
1315						newGraph.flow.addDataFlow(
1316							newGraphDataNode,
1317							pname,
1318							*newNode,
1319							param.name
1320						);
1321
1322						/*
1323						 * Add the same param as in the Data node to the function node
1324						 * we're building in the old graph
1325						 */
1326
1327						auto fparam = oldCompNode.params.add(param, pname);
1328						fparam.dir = ParamDirection.In;
1329
1330						/*
1331						 * Finally, create flow to the function node
1332						 */
1333
1334						graph.flow.addDataFlow(
1335							oldSrcNid,
1336							oldSrcParam.name,
1337							oldGraphCompNode,
1338							pname
1339						);
1340					}
1341				});
1342			}
1343		}
1344	}
1345
1346	return result;
1347}
1348
1349
1350// returns true if any new connections were inserted
1351private bool doManualFlow(
1352		KernelGraph graph,
1353		GraphNodeId fromId,
1354		GraphNodeId toId,
1355		DataFlow fl,
1356		ConvCtx ctx
1357) {
1358	scope stack = new StackBuffer;
1359
1360	final fromNode = graph.getNode(fromId);
1361	assert (fromNode !is null, "doManualfrom: fromNode not found");
1362	final fromParam = fromNode.getOutputParam(fl.from);
1363	assert (fromParam !is null, "doManualfrom: fromParam not found: " ~ fl.from);
1364	assert (fromParam.hasPlainSemantic);
1365
1366	final toNode = graph.getNode(toId);
1367	assert (toNode !is null, "doManualfrom: toNode not found");
1368	final toParam = toNode.getInputParam(fl.to);
1369	assert (toParam !is null, "doManualfrom: toParam not found: " ~ fl.to);
1370	assert (toParam.hasPlainSemantic);
1371
1372	{
1373		KernelImpl dstKernelImpl;
1374
1375		final srcType = fromParam.type;
1376		
1377		if (	toParam.hasTypeConstraint
1378			&&	isTypeKernel(toParam.type, &dstKernelImpl, ctx)
1379			&&	(!fromParam.hasTypeConstraint || srcType != toParam.type)
1380		) {
1381			// The destination is a kernel, this is functional composition, not regular
1382			// param flow.
1383
1384			if (dstKernelImpl.type != KernelImpl.Type.Kernel) {
1385				error(
1386					"Kernels used in functional composition may not be graph"
1387					" kernels. There's flow to a graph kernel '{}': {}.{} -> {}.{}.",
1388					toParam.type, fromId.id, fl.from, toId.id, fl.to
1389				);
1390			}
1391
1392			auto dstFunc = dstKernelImpl.kernel.func;
1393			assert (dstFunc !is null);
1394
1395			Param* funcOutputParam = null;
1396
1397			uword numFuncOutputParams;
1398			foreach (ref param; dstFunc.params) {
1399				if (param.isOutput) {
1400					++numFuncOutputParams;
1401					funcOutputParam = &param;
1402				}
1403			}
1404
1405			//assureNotCyclic(graph);
1406
1407			assert (
1408				1 == numFuncOutputParams,
1409				"TODO: currently kernels used in functional composition may only"
1410				" have one output param."
1411			);
1412
1413			assert (funcOutputParam !is null);
1414
1415			KernelGraph subgraph = createKernelGraph();
1416
1417			//assureNotCyclic(graph);
1418
1419			/*
1420			 * There will be a subgraph of comprising the nodes being used in
1421			 * functional composition. Create an output node for it
1422			 */
1423			final outNodeId = subgraph.addNode(KernelGraph.NodeType.Output);
1424			final outNode = subgraph.getNode(outNodeId).output();
1425			final outNodeParam = outNode.params.add(*funcOutputParam);
1426			outNodeParam.dir = ParamDirection.In;
1427
1428			/*
1429			 * Similarly with the input node.
1430			 */
1431			final inNodeId = subgraph.addNode(KernelGraph.NodeType.Input);
1432			final inNode = subgraph.getNode(inNodeId).input();
1433			foreach (ref param; dstFunc.params) {
1434				inNode.params.add(param).dir = ParamDirection.Out;
1435			}
1436
1437			const kernelOutputName = "kernel";
1438
1439			/*
1440			 * The functional composition will happen via a Composite node which
1441			 * will reside in the original graph and codegen into a struct in Cg
1442			 */
1443			final compNodeId = graph.addNode(KernelGraph.NodeType.Composite);
1444			final compNode = graph.getNode(compNodeId).composite();
1445			auto compOutParam = compNode.params.add(ParamDirection.Out, kernelOutputName);
1446			compOutParam.hasPlainSemantic = true;
1447			compOutParam.type = toParam.type;
1448
1449			/*
1450			 * Params which go into the composition node on the side of the old
1451			 * graph, will appear in a data node on the side of the new graph.
1452			 */
1453			final dataNodeId = subgraph.addNode(KernelGraph.NodeType.Data);
1454			final dataNode = subgraph.getNode(dataNodeId).data();
1455			dataNode.sourceKernelType = SourceKernelType.Composite;
1456
1457			compNode.targetFunc = dstFunc;
1458			compNode.graph = subgraph;
1459			compNode.dataNode = dataNodeId;
1460			compNode.inNode = inNodeId;
1461			compNode.outNode = outNodeId;
1462
1463			//assureNotCyclic(graph);
1464
1465			/*
1466			 * The input node's params must now be connected to the params in the
1467			 * source node's incoming tree which have no incoming flow.
1468			 * The matching is done by name, then semantic conversion is applied
1469			 * to each of them
1470			 */
1471
1472			GraphNodeId fromNodeInSubgraph;
1473
1474			//assureNotCyclic(graph);
1475
1476			DynamicBitSet clonedNodesSet;
1477			clonedNodesSet.alloc(graph.capacity, &stack.allocRaw);
1478			clonedNodesSet.clearAll();
1479
1480			DynamicBitSet visitedNodesSet;
1481			visitedNodesSet.alloc(graph.capacity, &stack.allocRaw);
1482			visitedNodesSet.clearAll();
1483
1484			final oldToNew = stack.allocArray!(GraphNodeId)(graph.capacity);
1485			
1486
1487			//log.trace("doManualFlow -> buildFunctionSubgraph");
1488
1489			buildFunctionSubgraph(
1490				graph,
1491				fromId,
1492
1493				/* paramFilter */
1494				(Param* param) {
1495					assert (param.isInput);
1496					foreach (fp; dstFunc.params) {
1497						if (fp.isInput && fp.name == param.name) {
1498							return true;
1499						}
1500					}
1501					return false;
1502				},
1503				
1504				/* freeParamSink */
1505				(Param* param, GraphNodeId node) {
1506					Param* inParam = null;
1507					inNode.params.getOutput(param.name, &inParam);
1508					assert (inParam !is null);
1509
1510					//assureNotCyclic(graph);
1511					
1512					if (!findConversion(
1513						*inParam.semantic,
1514						*param.semantic,
1515						ctx.semanticConverters,
1516						stack,
1517						(ConvSinkItem[] convChain) {
1518							_insertConversionNodes(
1519								subgraph,
1520								convChain,
1521								inNodeId,
1522								inParam,
1523								node,
1524								param
1525							);
1526						}
1527					)) {
1528						error(
1529							"Could not find a conversion for direct flow:"
1530							" {}:{} -> {}:{} when performing functional composition"
1531							" using kernel {}. The input param in the kernel was {}.",
1532							inNodeId.id, inParam.toString,
1533							node.id, param.toString,
1534							toParam.type, funcOutputParam.toString
1535						);
1536					}
1537
1538					//assureNotCyclic(graph);
1539				},
1540
1541				&clonedNodesSet,
1542				&visitedNodesSet,
1543				oldToNew,
1544				subgraph,
1545				dataNodeId,
1546				compNodeId,
1547				&fromNodeInSubgraph
1548			);
1549
1550			//assureNotCyclic(graph);
1551
1552			Semantic funcOutputPlainSem = Semantic(&subgraph._mem.pushBack);
1553			
1554			findOutputSemantic(
1555				funcOutputParam,
1556
1557				// getFormalParamSemantic
1558				(cstring name) {
1559					foreach (ref Param p; dstFunc.params) {
1560						if (p.isInput && p.name == name) {
1561							return *p.semantic();
1562						}
1563					}
1564					error(
1565						"simplifyParamSemantics: output param '{}' refers to a"
1566						" nonexistent formal parameter '{}'.",
1567						funcOutputParam.name,
1568						name
1569					);
1570					assert (false);
1571				},
1572
1573				// getActualParamSemantic
1574				(cstring name) {
1575					error(
1576						"Kernels used for functional composition must not use"
1577						" actual input param semantics."
1578					);
1579					return Semantic.init;
1580				},
1581				
1582				&funcOutputPlainSem
1583			);
1584
1585			compNode.returnType = funcOutputPlainSem.getTrait("type");
1586
1587			//assureNotCyclic(graph);
1588
1589			/*
1590			 * The function's output param must be convertible to the destination
1591			 * param's semantic. The destination will sample the output param.
1592			 * Because the sampling may be performed directly by a kernel func,
1593			 * any conversion nodes must be inserted into the subgraph generated
1594			 * for the function.
1595			 */
1596			if (!findConversion(
1597				*fromParam.semantic,
1598				funcOutputPlainSem,
1599				ctx.semanticConverters,
1600				stack,
1601				(ConvSinkItem[] convChain) {
1602					_insertConversionNodes(
1603						subgraph,
1604						convChain,
1605						fromNodeInSubgraph,
1606						fromParam,
1607						outNodeId,
1608						outNodeParam
1609					);
1610				}
1611			)) {
1612				error(
1613					"Could not find a conversion for direct flow:"
1614					" {}:{} -> {}:{} when performing functional composition"
1615					" using kernel {}. The output param in the kernel was {}.",
1616					fromId.id, fromParam.toString,
1617					outNodeId.id, outNodeParam.toString,
1618					toParam.type, funcOutputParam.toString
1619				);
1620			}
1621
1622			/*
1623			 * Finally, connect the output of the Composite node with the
1624			 * destination param from which we started the operation
1625			 */
1626			graph.flow.addDataFlow(
1627				compNodeId,
1628				kernelOutputName,
1629				toId,
1630				toParam.name
1631			);
1632
1633			//assureNotCyclic(graph);
1634
1635			/*
1636			 * New connections were added, remove the original one which caused
1637			 * the manual data flow func to be invoked.
1638			 */
1639			return true;
1640		}
1641	}
1642
1643	bool anyNewCons = false;
1644
1645	if (!findConversion(
1646		*fromParam.semantic,
1647		*toParam.semantic,
1648		ctx.semanticConverters,
1649		stack,
1650		(ConvSinkItem[] convChain) {
1651			anyNewCons = _insertConversionNodes(
1652				graph,
1653				convChain,
1654				fromId,
1655				fromParam,
1656				toId,
1657				toParam
1658			);
1659		}
1660	)) {
1661		error(
1662			"Could not find a conversion for direct flow:"
1663			" {}:{} -> {}:{}",
1664			fromId.id, fromParam.toString,
1665			toId.id, toParam.toString
1666		);
1667	}
1668
1669	return anyNewCons;
1670}
1671
1672
1673
1674// Assumes the consists of param/calc nodes only
1675void convertGraphDataFlow(
1676	KernelGraph graph,
1677	ConvCtx ctx
1678) {
1679	scope stack = new StackBuffer;
1680
1681	final topological = stack.allocArray!(GraphNodeId)(graph.numNodes);
1682	findTopologicalOrder(graph.backend_readOnly, topological);
1683
1684	return convertGraphDataFlow(graph, ctx, topological);
1685}
1686
1687
1688void convertGraphDataFlow(
1689	KernelGraph graph,
1690	ConvCtx ctx,
1691	GraphNodeId[] topological
1692) {
1693	foreach (id; topological) {
1694		doAutoFlow(
1695			graph,
1696			id,
1697			ctx,
1698			FlowGenMode.InsertConversionNodes
1699		);
1700
1701		simplifyParamSemantics(graph, id);
1702		
1703		scope stack2 = new StackBuffer;
1704		final toRemove = LocalDynArray!(ConFlow)(stack2);
1705
1706		foreach (con; graph.flow.iterOutgoingConnections(id)) {
1707			foreach (fl; graph.flow.iterDataFlow(id, con)) {
1708				if (doManualFlow(
1709					graph,
1710					id,
1711					con, fl,
1712					ctx
1713				)) {
1714					toRemove.pushBack(ConFlow(
1715						id, fl.from,
1716						con, fl.to
1717					));
1718				}
1719			}
1720		}
1721
1722		foreach (rem; toRemove) {
1723			graph.flow.removeDataFlow(rem.fromNode, rem.fromParam, rem.toNode, rem.toParam);
1724		}
1725	}
1726
1727	// NOTE: this was removed, m'kay?
1728	//graph.flow.removeAllAutoFlow();
1729}
1730
1731
1732
1733void convertGraphDataFlowExceptOutput(
1734	KernelGraph graph,
1735	ConvCtx ctx
1736) {
1737	scope stack = new StackBuffer;
1738
1739	final topological = stack.allocArray!(GraphNodeId)(graph.numNodes);
1740	findTopologicalOrder(graph.backend_readOnly, topological);
1741
1742	return convertGraphDataFlowExceptOutput(graph, ctx, topological);
1743}
1744
1745
1746void convertGraphDataFlowExceptOutput(
1747	KernelGraph graph,
1748	ConvCtx ctx,
1749	GraphNodeId[] topological
1750) {
1751	return convertGraphDataFlowExceptOutput(
1752		graph,
1753		ctx,
1754		(int delegate(ref GraphNodeId) sink) {
1755			foreach (id; topological) {
1756				if (int r = sink(id)) {
1757					return r;
1758				}
1759			}
1760			return 0;
1761		}
1762	);
1763}
1764
1765void convertGraphDataFlowExceptOutput(
1766	KernelGraph graph,
1767	ConvCtx ctx,
1768	int delegate(int delegate(ref GraphNodeId)) topological
1769) {
1770	bool isOutputNode(GraphNodeId id) {
1771		return KernelGraph.NodeType.Output == graph.getNode(id).type;
1772	}
1773	
1774	// Do auto flow for the first graph, but not generating conversions
1775	// for the output node
1776	foreach (id; topological) {
1777		doAutoFlow(
1778			graph,
1779			id,
1780			ctx,
1781			isOutputNode(id)
1782				? FlowGenMode.DirectConnection
1783				: FlowGenMode.InsertConversionNodes
1784		);
1785		
1786		simplifyParamSemantics(graph, id);
1787
1788		scope stack2 = new StackBuffer;
1789		final toRemove = LocalDynArray!(ConFlow)(stack2);
1790
1791		foreach (con; graph.flow.iterOutgoingConnections(id)) {
1792			bool markedForRemoval = false;
1793			
1794			if (!isOutputNode(con)) {
1795				foreach (fl; graph.flow.iterDataFlow(id, con)) {
1796					if (doManualFlow(
1797						graph,
1798						id,
1799						con, fl,
1800						ctx
1801					)) {
1802						toRemove.pushBack(ConFlow(
1803							id, fl.from,
1804							con, fl.to
1805						));
1806					}
1807				}
1808			}
1809		}
1810
1811		foreach (rem; toRemove) {
1812			graph.flow.removeDataFlow(rem.fromNode, rem.fromParam, rem.toNode, rem.toParam);
1813		}
1814	}
1815
1816	// NOTE: this was removed, m'kay?
1817	//graph.flow.removeAllAutoFlow();
1818}
1819
1820
1821void reduceGraphData(
1822	KernelGraph kg,
1823	void delegate(void delegate(
1824		GraphNodeId	nid,
1825		cstring		pname
1826	)) iterNodes,
1827	Function		reductionFunc,
1828	GraphNodeId*	outputNid,
1829	cstring*		outputPName
1830) {
1831	GraphNodeId	prevNid;
1832	cstring		prevPName;
1833	bool		gotAny = false;
1834	cstring		reductionPName;
1835
1836	foreach (i, p; reductionFunc.params) {
1837		if (i > 3 || (i <= 1 && p.isOutput) || (2 == i && p.isInput)) {
1838			error(
1839				"reduceGraphData: '{}' is not a valid reduction func.",
1840				reductionFunc.name
1841			);
1842		}
1843		
1844		if (p.isOutput) {
1845			if (reductionPName is null) {
1846				reductionPName = p.name;
1847			} else {
1848				error(
1849					"reduceGraphData: The reduction func must only have"
1850					" one output param. The passed '{}' func has more.",
1851					reductionFunc.name
1852				);
1853			}
1854		}
1855	}
1856
1857	iterNodes((
1858			GraphNodeId	nid,
1859			cstring		pname
1860		) {
1861			if (gotAny) {
1862				final rnid = kg.addFuncNode(reductionFunc);
1863				final rnode = kg.getNode(rnid);
1864
1865				kg.flow.addDataFlow(
1866					prevNid, prevPName,
1867					rnid, reductionFunc.params[0].name
1868				);
1869
1870				kg.flow.addDataFlow(
1871					nid, pname,
1872					rnid, reductionFunc.params[1].name
1873				);
1874
1875				prevNid = rnid;
1876				prevPName = reductionPName;
1877			} else {
1878				prevNid = nid;
1879				prevPName = pname;
1880				gotAny = true;
1881			}
1882		}
1883	);
1884
1885	*outputNid = prevNid;
1886	*outputPName = prevPName;
1887}