 src/cuda_control.c |   2 -
 src/gpujoin.c      |  12 +++--
 src/gpupreagg.c    |   8 +++-
 src/gpuscan.c      | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 src/gpusort.c      |  10 ++--
 5 files changed, 147 insertions(+), 16 deletions(-)

diff --git a/src/cuda_control.c b/src/cuda_control.c
index c524f4e..6ca25cf 100644
--- a/src/cuda_control.c
+++ b/src/cuda_control.c
@@ -1686,8 +1686,6 @@ pgstrom_fetch_gputask(GpuTaskState *gts)
 				if (!gtask)
 				{
 					pgstrom_deactivate_gputaskstate(gts);
-					elog(DEBUG1, "scan done (%s)",
-						 gts->css.methods->CustomName);
 					break;
 				}
 				dlist_push_tail(&gts->pending_tasks, &gtask->chain);
diff --git a/src/gpujoin.c b/src/gpujoin.c
index a4d3c88..d7a18cd 100644
--- a/src/gpujoin.c
+++ b/src/gpujoin.c
@@ -6360,15 +6360,20 @@ pgstrom_init_gpujoin(void)
 							 GUC_NOT_IN_SAMPLE,
 							 NULL, NULL, NULL);
 	/* setup path methods */
-	gpujoin_path_methods.CustomName				= "GpuJoin";
+	gpujoin_path_methods.xnode.extnodename		= "GpuJoinPath";
+	gpujoin_path_methods.xnode.node_size		= sizeof(GpuJoinPath);
 	gpujoin_path_methods.PlanCustomPath			= create_gpujoin_plan;
+	RegisterExtensibleNodeMethods(&gpujoin_path_methods.xnode);
 
 	/* setup plan methods */
-	gpujoin_plan_methods.CustomName				= "GpuJoin";
+	gpujoin_plan_methods.xnode.extnodename		= "GpuJoin";
+	gpujoin_path_methods.xnode.node_size		= sizeof(CustomScan);
 	gpujoin_plan_methods.CreateCustomScanState	= gpujoin_create_scan_state;
+	RegisterExtensibleNodeMethods(&gpujoin_plan_methods.xnode);
 
 	/* setup exec methods */
-	gpujoin_exec_methods.CustomName				= "GpuJoin";
+	gpujoin_exec_methods.xnode.extnodename		= "GpuJoinState";
+	gpujoin_exec_methods.xnode.node_size		= sizeof(GpuJoinState);
 	gpujoin_exec_methods.BeginCustomScan		= gpujoin_begin;
 	gpujoin_exec_methods.ExecCustomScan			= gpujoin_exec;
 	gpujoin_exec_methods.EndCustomScan			= gpujoin_end;
@@ -6376,6 +6381,7 @@ pgstrom_init_gpujoin(void)
 	gpujoin_exec_methods.MarkPosCustomScan		= NULL;
 	gpujoin_exec_methods.RestrPosCustomScan		= NULL;
 	gpujoin_exec_methods.ExplainCustomScan		= gpujoin_explain;
+	RegisterExtensibleNodeMethods(&gpujoin_exec_methods.xnode);
 
 	/* hook registration */
 	set_join_pathlist_next = set_join_pathlist_hook;
diff --git a/src/gpupreagg.c b/src/gpupreagg.c
index 4768159..1c3a1cc 100644
--- a/src/gpupreagg.c
+++ b/src/gpupreagg.c
@@ -5285,16 +5285,20 @@ pgstrom_init_gpupreagg(void)
 
 	/* initialization of plan method table */
 	memset(&gpupreagg_scan_methods, 0, sizeof(CustomScanMethods));
-	gpupreagg_scan_methods.CustomName          = "GpuPreAgg";
+	gpupreagg_scan_methods.xnode.extnodename	= "GpuPreAgg";
+	gpupreagg_scan_methods.xnode.node_size		= sizeof(CustomScan);
 	gpupreagg_scan_methods.CreateCustomScanState
 		= gpupreagg_create_scan_state;
+	RegisterExtensibleNodeMethods(&gpupreagg_scan_methods.xnode);
 
 	/* initialization of exec method table */
 	memset(&gpupreagg_exec_methods, 0, sizeof(CustomExecMethods));
-	gpupreagg_exec_methods.CustomName          = "GpuPreAgg";
+	gpupreagg_exec_methods.xnode.extnodename   = "GpuPreAggState";
+	gpupreagg_exec_methods.xnode.node_size     = sizeof(GpuPreAggState);
    	gpupreagg_exec_methods.BeginCustomScan     = gpupreagg_begin;
 	gpupreagg_exec_methods.ExecCustomScan      = gpupreagg_exec;
 	gpupreagg_exec_methods.EndCustomScan       = gpupreagg_end;
 	gpupreagg_exec_methods.ReScanCustomScan    = gpupreagg_rescan;
 	gpupreagg_exec_methods.ExplainCustomScan   = gpupreagg_explain;
+	RegisterExtensibleNodeMethods(&gpupreagg_exec_methods.xnode);
 }
diff --git a/src/gpuscan.c b/src/gpuscan.c
index 002bb60..39dc51e 100644
--- a/src/gpuscan.c
+++ b/src/gpuscan.c
@@ -24,6 +24,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/readfuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/cost.h"
 #include "optimizer/pathnode.h"
@@ -75,9 +76,24 @@ typedef struct {
     cl_int      proj_extra_width; /* width of extra buffer on projection */
 } GpuScanInfo;
 
+typedef struct
+{
+	CustomScan	cscan;
+	char	   *kern_source;	/* source of opencl kernel */
+	int32		extra_flags;	/* extra libraries to be included */
+	List	   *func_defs;		/* list of declared functions */
+	List	   *used_params;	/* list of Const/Param in use */
+	List	   *used_vars;		/* list of Var in use */
+	List	   *dev_quals;		/* qualifiers to be run on device */
+	cl_int      base_fixed_width; /* width of fixed fields on base rel */
+    cl_int      proj_fixed_width; /* width of fixed fields on projection */
+    cl_int      proj_extra_width; /* width of extra buffer on projection */
+} GpuScan;
+
 static inline void
 form_gpuscan_info(CustomScan *cscan, GpuScanInfo *gs_info)
 {
+	GpuScan	   *gscan = (GpuScan *) cscan;
 	List	   *privs = NIL;
 	List	   *exprs = NIL;
 
@@ -95,6 +111,16 @@ form_gpuscan_info(CustomScan *cscan, GpuScanInfo *gs_info)
 
 	cscan->custom_private = privs;
     cscan->custom_exprs = exprs;
+
+	gscan->kern_source = pstrdup(gs_info->kern_source);
+	gscan->extra_flags = gs_info->extra_flags;
+	gscan->func_defs = copyObject(gs_info->func_defs);
+	gscan->used_params = copyObject(gs_info->used_params);
+	gscan->used_vars = copyObject(gs_info->used_vars);
+	gscan->dev_quals = copyObject(gs_info->dev_quals);
+	gscan->base_fixed_width = gs_info->base_fixed_width;
+	gscan->proj_fixed_width = gs_info->proj_fixed_width;
+	gscan->proj_extra_width = gs_info->proj_extra_width;
 }
 
 static GpuScanInfo *
@@ -512,7 +538,7 @@ gpuscan_try_replace_seqscan(Relation baserel, SeqScan *seqscan)
 	 * OK, let's make a GpuScan that can replace SeqScan without
 	 * host-only expression in scan-qualifiers.
 	 */
-	cscan = makeNode(CustomScan);
+	cscan = (CustomScan *) newNode(sizeof(GpuScan), T_CustomScan);
 	cscan->scan.plan.startup_cost = startup_cost;
 	cscan->scan.plan.total_cost = total_cost;
 	cscan->scan.plan.plan_width = seqscan->plan.plan_width;
@@ -626,7 +652,7 @@ create_gpuscan_plan(PlannerInfo *root,
 	/*
 	 * Construction of GpuScanPlan node; on top of CustomPlan node
 	 */
-	cscan = makeNode(CustomScan);
+	cscan = (CustomScan *) newNode(sizeof(GpuScan), T_CustomScan);
 	cscan->scan.plan.targetlist = tlist;
 	cscan->scan.plan.qual = host_quals;
 	cscan->scan.plan.lefttree = NULL;
@@ -1649,6 +1675,88 @@ assign_gpuscan_session_info(StringInfo buf, GpuTaskState *gts)
 	}
 }
 
+static void
+gpuscan_plannode_copy(Node *__newnode, const Node *__oldnode)
+{
+	GpuScan		   *newnode = (GpuScan *) __newnode;
+	const GpuScan  *oldnode = (GpuScan *) __oldnode;
+
+	newnode->kern_source = pstrdup(oldnode->kern_source);
+	newnode->extra_flags = oldnode->extra_flags;
+	newnode->func_defs = copyObject(oldnode->func_defs);
+	newnode->used_params = copyObject(oldnode->used_params);
+	newnode->used_vars = copyObject(oldnode->used_vars);
+	newnode->dev_quals = copyObject(oldnode->dev_quals);
+	newnode->base_fixed_width = oldnode->base_fixed_width;
+	newnode->proj_fixed_width = oldnode->proj_fixed_width;
+	newnode->proj_extra_width = oldnode->proj_extra_width;
+}
+
+static void
+gpuscan_plannode_out(StringInfo str, const Node *node)
+{
+	GpuScan	   *gscan = (GpuScan *) node;
+
+	appendStringInfo(str, " :kern_source ");
+	outToken(str, gscan->kern_source);
+
+	appendStringInfo(str, " :extra_flags %u", gscan->extra_flags);
+	appendStringInfo(str, " :func_defs %s",
+					 nodeToString(gscan->func_defs));
+	appendStringInfo(str, " :used_params %s",
+					 nodeToString(gscan->used_params));
+	appendStringInfo(str, " :used_vars %s",
+					 nodeToString(gscan->used_vars));
+	appendStringInfo(str, " :dev_quals %s",
+					 nodeToString(gscan->dev_quals));
+	appendStringInfo(str, " :base_fixed_width %d",
+					 gscan->base_fixed_width);
+	appendStringInfo(str, " :proj_fixed_width %d",
+					 gscan->proj_fixed_width);
+	appendStringInfo(str, " :proj_extra_width %d",
+					 gscan->proj_extra_width);
+}
+
+static void
+gpuscan_plannode_read(Node *node)
+{
+	GpuScan	   *gscan = (GpuScan *) node;
+	char	   *token;
+	int			length;
+
+	token = pg_strtok(&length);		/* skip :kern_source */
+	token = pg_strtok(&length);
+	gscan->kern_source = (length > 0 ? debackslash(token, length) : NULL);
+
+	token = pg_strtok(&length);		/* skip: extra_flags */
+	token = pg_strtok(&length);
+	gscan->extra_flags = (unsigned int) strtoul(token, NULL, 10);
+
+	token = pg_strtok(&length);		/* skip: func_defs */
+	gscan->func_defs = nodeRead(NULL, 0);
+
+	token = pg_strtok(&length);		/* skip: used_params */
+	gscan->used_params = nodeRead(NULL, 0);
+
+	token = pg_strtok(&length);		/* skip: used_vars */
+	gscan->used_vars = nodeRead(NULL, 0);
+
+	token = pg_strtok(&length);		/* skip: dev_quals */
+	gscan->dev_quals = nodeRead(NULL, 0);
+
+	token = pg_strtok(&length);		/* skip: base_fixed_width */
+	token = pg_strtok(&length);
+	gscan->base_fixed_width = atoi(token);
+
+	token = pg_strtok(&length);		/* skip: proj_fixed_width */
+	token = pg_strtok(&length);
+	gscan->proj_fixed_width = atoi(token);
+
+	token = pg_strtok(&length);		/* skip: proj_extra_width */
+	token = pg_strtok(&length);
+	gscan->proj_extra_width = atoi(token);
+}
+
 /*
  * gpuscan_create_scan_state
  *
@@ -1678,11 +1786,15 @@ gpuscan_begin(CustomScanState *node, EState *estate, int eflags)
 	GpuScanState   *gss = (GpuScanState *) node;
 	CustomScan	   *cscan = (CustomScan *)node->ss.ps.plan;
 	GpuScanInfo	   *gs_info = deform_gpuscan_info(cscan);
+	GpuScan		   *gscan;
 
 	/* gpuscan should not have inner/outer plan right now */
 	Assert(outerPlan(node) == NULL);
 	Assert(innerPlan(node) == NULL);
 
+	gscan = (GpuScan *)stringToNode(nodeToString(copyObject(cscan)));
+	elog(INFO, "GpuScan: %s", nodeToString(gscan));
+
 	/* activate GpuContext for device execution */
 	if ((eflags & EXEC_FLAG_EXPLAIN_ONLY) == 0)
 		gcontext = pgstrom_get_gpucontext();
@@ -2112,22 +2224,31 @@ pgstrom_init_gpuscan(void)
 
 	/* setup path methods */
 	memset(&gpuscan_path_methods, 0, sizeof(gpuscan_path_methods));
-	gpuscan_path_methods.CustomName			= "GpuScan";
+	gpuscan_path_methods.xnode.extnodename  = "GpuScanPath";
+	gpuscan_path_methods.xnode.node_size    = sizeof(GpuScanPath);
 	gpuscan_path_methods.PlanCustomPath		= create_gpuscan_plan;
+	RegisterExtensibleNodeMethods(&gpuscan_path_methods.xnode);
 
 	/* setup plan methods */
 	memset(&gpuscan_plan_methods, 0, sizeof(gpuscan_plan_methods));
-	gpuscan_plan_methods.CustomName			= "GpuScan";
+	gpuscan_plan_methods.xnode.extnodename	= "GpuScan";
+	gpuscan_plan_methods.xnode.node_size	= sizeof(GpuScan);
+	gpuscan_plan_methods.xnode.nodeCopy		= gpuscan_plannode_copy;
+	gpuscan_plan_methods.xnode.nodeOut		= gpuscan_plannode_out;
+	gpuscan_plan_methods.xnode.nodeRead		= gpuscan_plannode_read;
 	gpuscan_plan_methods.CreateCustomScanState = gpuscan_create_scan_state;
+	RegisterExtensibleNodeMethods(&gpuscan_plan_methods.xnode);
 
 	/* setup exec methods */
 	memset(&gpuscan_exec_methods, 0, sizeof(gpuscan_exec_methods));
-	gpuscan_exec_methods.CustomName         = "GpuScan";
+	gpuscan_exec_methods.xnode.extnodename	= "GpuScanState";
+	gpuscan_exec_methods.xnode.node_size	= sizeof(GpuScanState);
 	gpuscan_exec_methods.BeginCustomScan    = gpuscan_begin;
 	gpuscan_exec_methods.ExecCustomScan     = gpuscan_exec;
 	gpuscan_exec_methods.EndCustomScan      = gpuscan_end;
 	gpuscan_exec_methods.ReScanCustomScan   = gpuscan_rescan;
 	gpuscan_exec_methods.ExplainCustomScan  = gpuscan_explain;
+	RegisterExtensibleNodeMethods(&gpuscan_exec_methods.xnode);
 
 	/* hook registration */
 	set_rel_pathlist_next = set_rel_pathlist_hook;
diff --git a/src/gpusort.c b/src/gpusort.c
index 37a50db..d19b506 100644
--- a/src/gpusort.c
+++ b/src/gpusort.c
@@ -1675,8 +1675,6 @@ gpusort_task_complete(GpuTask *gtask)
 			dlist_push_tail(&gss->gts.ready_tasks, &gpusort->task.chain);
 			gss->gts.num_ready_tasks++;
 
-			elog(DEBUG1, "sort done (%s)",
-				 gss->gts.css.methods->CustomName);
 			gss->sort_done = true;	/* congratulation! */
 		}
 		SpinLockRelease(&gss->gts.lock);
@@ -2665,12 +2663,15 @@ pgstrom_init_gpusort(void)
 
 	/* initialize the plan method table */
 	memset(&gpusort_scan_methods, 0, sizeof(CustomScanMethods));
-	gpusort_scan_methods.CustomName			= "GpuSort";
+	gpusort_scan_methods.xnode.extnodename	= "GpuSort";
+	gpusort_scan_methods.xnode.node_size    = sizeof(CustomScan);
 	gpusort_scan_methods.CreateCustomScanState = gpusort_create_scan_state;
+	RegisterExtensibleNodeMethods(&gpusort_scan_methods.xnode);
 
 	/* initialize the exec method table */
 	memset(&gpusort_exec_methods, 0, sizeof(CustomExecMethods));
-	gpusort_exec_methods.CustomName			= "GpuSort";
+	gpusort_exec_methods.xnode.extnodename  = "GpuSortState";
+	gpusort_exec_methods.xnode.node_size    = sizeof(GpuSortState);
 	gpusort_exec_methods.BeginCustomScan	= gpusort_begin;
 	gpusort_exec_methods.ExecCustomScan		= gpusort_exec;
 	gpusort_exec_methods.EndCustomScan		= gpusort_end;
@@ -2678,6 +2679,7 @@ pgstrom_init_gpusort(void)
 	gpusort_exec_methods.MarkPosCustomScan	= gpusort_mark_pos;
 	gpusort_exec_methods.RestrPosCustomScan	= gpusort_restore_pos;
 	gpusort_exec_methods.ExplainCustomScan	= gpusort_explain;
+	RegisterExtensibleNodeMethods(&gpusort_exec_methods.xnode);
 }
 
 /* ================================================================
