diff --git a/src/backend/catalog/Catalog.pm b/src/backend/catalog/Catalog.pm
index 6ffd5f904a18871a6d04d044c54fd406eb1f3911..fa8de04af385bd48b92a69a79459ee257d55ffd3 100644
--- a/src/backend/catalog/Catalog.pm
+++ b/src/backend/catalog/Catalog.pm
@@ -19,7 +19,7 @@ use warnings;
 require Exporter;
 our @ISA       = qw(Exporter);
 our @EXPORT    = ();
-our @EXPORT_OK = qw(Catalogs RenameTempFile);
+our @EXPORT_OK = qw(Catalogs SplitDataLine RenameTempFile);
 
 # Call this function with an array of names of header files to parse.
 # Returns a nested data structure describing the data in the headers.
@@ -216,6 +216,28 @@ sub Catalogs
 	return \%catalogs;
 }
 
+# Split a DATA line into fields.
+# Call this on the bki_values element of a DATA item returned by Catalogs();
+# it returns a list of field values.  We don't strip quoting from the fields.
+# Note: it should be safe to assign the result to a list of length equal to
+# the nominal number of catalog fields, because check_natts already checked
+# the number of fields.
+sub SplitDataLine
+{
+	my $bki_values = shift;
+
+	# This handling of quoted strings might look too simplistic, but it
+	# matches what bootscanner.l does: that has no provision for quote marks
+	# inside quoted strings, either.  If we don't have a quoted string, just
+	# snarf everything till next whitespace.  That will accept some things
+	# that bootscanner.l will see as erroneous tokens; but it seems wiser
+	# to do that and let bootscanner.l complain than to silently drop
+	# non-whitespace characters.
+	my @result = $bki_values =~ /"[^"]*"|\S+/g;
+
+	return @result;
+}
+
 # Rename temporary files to final names.
 # Call this function with the final file name and the .tmp extension
 # Note: recommended extension is ".tmp$$", so that parallel make steps
@@ -229,21 +251,20 @@ sub RenameTempFile
 	rename($temp_name, $final_name) || die "rename: $temp_name: $!";
 }
 
-# verify the number of fields in the passed-in bki structure
+# verify the number of fields in the passed-in DATA line
 sub check_natts
 {
 	my ($catname, $natts, $bki_val, $file, $line) = @_;
+
 	die "Could not find definition for Natts_${catname} before start of DATA() in $file\n"
 		unless defined $natts;
 
-	# we're working with a copy and need to count the fields only, so collapse
-	$bki_val =~ s/"[^"]*?"/xxx/g;
-	my @atts = split /\s+/, $bki_val;
+	my $nfields = scalar(SplitDataLine($bki_val));
 
 	die sprintf
 		"Wrong number of attributes in DATA() entry at %s:%d (expected %d but got %d)\n",
-		$file, $line, $natts, scalar @atts
-	  unless $natts == @atts;
+		$file, $line, $natts, $nfields
+	  unless $natts == $nfields;
 }
 
 1;
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 6e9d57aa8d4da04e6e312c8fa6bf2d1d2c6c9fc5..198e0724e208cb7fb557d6deb7b20160654a49f8 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -102,6 +102,7 @@ print $bki "# PostgreSQL $major_version\n";
 # vars to hold data needed for schemapg.h
 my %schemapg_entries;
 my @tables_needing_macros;
+my %regprocoids;
 our @types;
 
 # produce output, one catalog at a time
@@ -160,24 +161,57 @@ foreach my $catname (@{ $catalogs->{names} })
 		foreach my $row (@{ $catalog->{data} })
 		{
 
-			# substitute constant values we acquired above
-			$row->{bki_values} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
-			$row->{bki_values} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+			# Split line into tokens without interpreting their meaning.
+			my %bki_values;
+			@bki_values{@attnames} = Catalog::SplitDataLine($row->{bki_values});
+
+			# Perform required substitutions on fields
+			foreach my $att (keys %bki_values)
+			{
+				# Substitute constant values we acquired above.
+				# (It's intentional that this can apply to parts of a field).
+				$bki_values{$att} =~ s/\bPGUID\b/$BOOTSTRAP_SUPERUSERID/g;
+				$bki_values{$att} =~ s/\bPGNSP\b/$PG_CATALOG_NAMESPACE/g;
+
+				# Replace regproc columns' values with OIDs.
+				# If we don't have a unique value to substitute,
+				# just do nothing (regprocin will complain).
+				if ($bki_attr{$att}->{type} eq 'regproc')
+				{
+					my $procoid = $regprocoids{$bki_values{$att}};
+					$bki_values{$att} = $procoid
+						if defined($procoid) && $procoid ne 'MULTIPLE';
+				}
+			}
+
+			# Save pg_proc oids for use in later regproc substitutions.
+			# This relies on the order we process the files in!
+			if ($catname eq 'pg_proc')
+			{
+				if (defined($regprocoids{$bki_values{proname}}))
+				{
+					$regprocoids{$bki_values{proname}} = 'MULTIPLE';
+				}
+				else
+				{
+					$regprocoids{$bki_values{proname}} = $row->{oid};
+				}
+			}
 
 			# Save pg_type info for pg_attribute processing below
 			if ($catname eq 'pg_type')
 			{
-				my %type;
+				my %type = %bki_values;
 				$type{oid} = $row->{oid};
-				@type{@attnames} = split /\s+/, $row->{bki_values};
 				push @types, \%type;
 			}
 
 			# Write to postgres.bki
 			my $oid = $row->{oid} ? "OID = $row->{oid} " : '';
-			printf $bki "insert %s( %s)\n", $oid, $row->{bki_values};
+			printf $bki "insert %s( %s )\n", $oid,
+			  join(' ', @bki_values{@attnames});
 
-		   # Write comments to postgres.description and postgres.shdescription
+			# Write comments to postgres.description and postgres.shdescription
 			if (defined $row->{descr})
 			{
 				printf $descr "%s\t%s\t0\t%s\n", $row->{oid}, $catname,
@@ -426,7 +460,7 @@ sub bki_insert
 	my @attnames   = @_;
 	my $oid        = $row->{oid} ? "OID = $row->{oid} " : '';
 	my $bki_values = join ' ', map { $_ eq '' ? '""' : $_ } map $row->{$_}, @attnames;
-	printf $bki "insert %s( %s)\n", $oid, $bki_values;
+	printf $bki "insert %s( %s )\n", $oid, $bki_values;
 }
 
 # The field values of a Schema_pg_xxx declaration are similar, but not
diff --git a/src/backend/utils/Gen_fmgrtab.pl b/src/backend/utils/Gen_fmgrtab.pl
index 2af9b355e75f89c4721bbfd3da3346cd587bf2bf..76bdf5ca0d7c34ff97c240477d84da9902fe9506 100644
--- a/src/backend/utils/Gen_fmgrtab.pl
+++ b/src/backend/utils/Gen_fmgrtab.pl
@@ -58,30 +58,20 @@ foreach my $column (@{ $catalogs->{pg_proc}->{columns} })
 my $data = $catalogs->{pg_proc}->{data};
 foreach my $row (@$data)
 {
-
-	# To construct fmgroids.h and fmgrtab.c, we need to inspect some
-	# of the individual data fields.  Just splitting on whitespace
-	# won't work, because some quoted fields might contain internal
-	# whitespace.  We handle this by folding them all to a simple
-	# "xxx". Fortunately, this script doesn't need to look at any
-	# fields that might need quoting, so this simple hack is
-	# sufficient.
-	$row->{bki_values} =~ s/"[^"]*"/"xxx"/g;
-	@{$row}{@attnames} = split /\s+/, $row->{bki_values};
+	# Split line into tokens without interpreting their meaning.
+	my %bki_values;
+	@bki_values{@attnames} = Catalog::SplitDataLine($row->{bki_values});
 
 	# Select out just the rows for internal-language procedures.
 	# Note assumption here that INTERNALlanguageId is 12.
-	next if $row->{prolang} ne '12';
+	next if $bki_values{prolang} ne '12';
 
 	push @fmgr,
 	  { oid    => $row->{oid},
-		strict => $row->{proisstrict},
-		retset => $row->{proretset},
-		nargs  => $row->{pronargs},
-		prosrc => $row->{prosrc}, };
-
-	# Hack to work around memory leak in some versions of Perl
-	$row = undef;
+		strict => $bki_values{proisstrict},
+		retset => $bki_values{proretset},
+		nargs  => $bki_values{pronargs},
+		prosrc => $bki_values{prosrc}, };
 }
 
 # Emit headers for both files
diff --git a/src/backend/utils/adt/regproc.c b/src/backend/utils/adt/regproc.c
index 702924a9586dd911caefb9815c0cc3fd3ab7cd05..a90452c701188d861b58951bed6aaa133a2cbb81 100644
--- a/src/backend/utils/adt/regproc.c
+++ b/src/backend/utils/adt/regproc.c
@@ -21,10 +21,7 @@
 
 #include <ctype.h>
 
-#include "access/genam.h"
-#include "access/heapam.h"
 #include "access/htup_details.h"
-#include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_class.h"
 #include "catalog/pg_operator.h"
@@ -36,10 +33,8 @@
 #include "miscadmin.h"
 #include "parser/parse_type.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
-#include "utils/tqual.h"
 #include "utils/acl.h"
 #include "utils/regproc.h"
 #include "utils/varlena.h"
@@ -87,51 +82,11 @@ regprocin(PG_FUNCTION_ARGS)
 	/* Else it's a name, possibly schema-qualified */
 
 	/*
-	 * In bootstrap mode we assume the given name is not schema-qualified, and
-	 * just search pg_proc for a unique match.  This is needed for
-	 * initializing other system catalogs (pg_namespace may not exist yet, and
-	 * certainly there are no schemas other than pg_catalog).
+	 * We should never get here in bootstrap mode, as all references should
+	 * have been resolved by genbki.pl.
 	 */
 	if (IsBootstrapProcessingMode())
-	{
-		int			matches = 0;
-		Relation	hdesc;
-		ScanKeyData skey[1];
-		SysScanDesc sysscan;
-		HeapTuple	tuple;
-
-		ScanKeyInit(&skey[0],
-					Anum_pg_proc_proname,
-					BTEqualStrategyNumber, F_NAMEEQ,
-					CStringGetDatum(pro_name_or_oid));
-
-		hdesc = heap_open(ProcedureRelationId, AccessShareLock);
-		sysscan = systable_beginscan(hdesc, ProcedureNameArgsNspIndexId, true,
-									 NULL, 1, skey);
-
-		while (HeapTupleIsValid(tuple = systable_getnext(sysscan)))
-		{
-			result = (RegProcedure) HeapTupleGetOid(tuple);
-			if (++matches > 1)
-				break;
-		}
-
-		systable_endscan(sysscan);
-		heap_close(hdesc, AccessShareLock);
-
-		if (matches == 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
-				 errmsg("function \"%s\" does not exist", pro_name_or_oid)));
-
-		else if (matches > 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
-					 errmsg("more than one function named \"%s\"",
-							pro_name_or_oid)));
-
-		PG_RETURN_OID(result);
-	}
+		elog(ERROR, "regproc values must be OIDs in bootstrap mode");
 
 	/*
 	 * Normal case: parse the name into components and see if it matches any
@@ -295,15 +250,15 @@ regprocedurein(PG_FUNCTION_ARGS)
 		PG_RETURN_OID(result);
 	}
 
+	/* The rest of this wouldn't work in bootstrap mode */
+	if (IsBootstrapProcessingMode())
+		elog(ERROR, "regprocedure values must be OIDs in bootstrap mode");
+
 	/*
 	 * Else it's a name and arguments.  Parse the name and arguments, look up
 	 * potential matches in the current namespace search list, and scan to see
 	 * which one exactly matches the given argument types.  (There will not be
 	 * more than one match.)
-	 *
-	 * XXX at present, this code will not work in bootstrap mode, hence this
-	 * datatype cannot be used for any system column that needs to receive
-	 * data during bootstrap.
 	 */
 	parseNameAndArgTypes(pro_name_or_oid, false, &names, &nargs, argtypes);
 
@@ -400,6 +355,7 @@ format_procedure_internal(Oid procedure_oid, bool force_qualify)
 		StringInfoData buf;
 
 		/* XXX no support here for bootstrap mode */
+		Assert(!IsBootstrapProcessingMode());
 
 		initStringInfo(&buf);
 
@@ -546,51 +502,9 @@ regoperin(PG_FUNCTION_ARGS)
 
 	/* Else it's a name, possibly schema-qualified */
 
-	/*
-	 * In bootstrap mode we assume the given name is not schema-qualified, and
-	 * just search pg_operator for a unique match.  This is needed for
-	 * initializing other system catalogs (pg_namespace may not exist yet, and
-	 * certainly there are no schemas other than pg_catalog).
-	 */
+	/* The rest of this wouldn't work in bootstrap mode */
 	if (IsBootstrapProcessingMode())
-	{
-		int			matches = 0;
-		Relation	hdesc;
-		ScanKeyData skey[1];
-		SysScanDesc sysscan;
-		HeapTuple	tuple;
-
-		ScanKeyInit(&skey[0],
-					Anum_pg_operator_oprname,
-					BTEqualStrategyNumber, F_NAMEEQ,
-					CStringGetDatum(opr_name_or_oid));
-
-		hdesc = heap_open(OperatorRelationId, AccessShareLock);
-		sysscan = systable_beginscan(hdesc, OperatorNameNspIndexId, true,
-									 NULL, 1, skey);
-
-		while (HeapTupleIsValid(tuple = systable_getnext(sysscan)))
-		{
-			result = HeapTupleGetOid(tuple);
-			if (++matches > 1)
-				break;
-		}
-
-		systable_endscan(sysscan);
-		heap_close(hdesc, AccessShareLock);
-
-		if (matches == 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_FUNCTION),
-					 errmsg("operator does not exist: %s", opr_name_or_oid)));
-		else if (matches > 1)
-			ereport(ERROR,
-					(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
-					 errmsg("more than one operator named %s",
-							opr_name_or_oid)));
-
-		PG_RETURN_OID(result);
-	}
+		elog(ERROR, "regoper values must be OIDs in bootstrap mode");
 
 	/*
 	 * Normal case: parse the name into components and see if it matches any
@@ -759,15 +673,15 @@ regoperatorin(PG_FUNCTION_ARGS)
 		PG_RETURN_OID(result);
 	}
 
+	/* The rest of this wouldn't work in bootstrap mode */
+	if (IsBootstrapProcessingMode())
+		elog(ERROR, "regoperator values must be OIDs in bootstrap mode");
+
 	/*
 	 * Else it's a name and arguments.  Parse the name and arguments, look up
 	 * potential matches in the current namespace search list, and scan to see
 	 * which one exactly matches the given argument types.  (There will not be
 	 * more than one match.)
-	 *
-	 * XXX at present, this code will not work in bootstrap mode, hence this
-	 * datatype cannot be used for any system column that needs to receive
-	 * data during bootstrap.
 	 */
 	parseNameAndArgTypes(opr_name_or_oid, true, &names, &nargs, argtypes);
 	if (nargs == 1)
@@ -852,6 +766,7 @@ format_operator_internal(Oid operator_oid, bool force_qualify)
 		StringInfoData buf;
 
 		/* XXX no support here for bootstrap mode */
+		Assert(!IsBootstrapProcessingMode());
 
 		initStringInfo(&buf);
 
@@ -1006,42 +921,9 @@ regclassin(PG_FUNCTION_ARGS)
 
 	/* Else it's a name, possibly schema-qualified */
 
-	/*
-	 * In bootstrap mode we assume the given name is not schema-qualified, and
-	 * just search pg_class for a match.  This is needed for initializing
-	 * other system catalogs (pg_namespace may not exist yet, and certainly
-	 * there are no schemas other than pg_catalog).
-	 */
+	/* The rest of this wouldn't work in bootstrap mode */
 	if (IsBootstrapProcessingMode())
-	{
-		Relation	hdesc;
-		ScanKeyData skey[1];
-		SysScanDesc sysscan;
-		HeapTuple	tuple;
-
-		ScanKeyInit(&skey[0],
-					Anum_pg_class_relname,
-					BTEqualStrategyNumber, F_NAMEEQ,
-					CStringGetDatum(class_name_or_oid));
-
-		hdesc = heap_open(RelationRelationId, AccessShareLock);
-		sysscan = systable_beginscan(hdesc, ClassNameNspIndexId, true,
-									 NULL, 1, skey);
-
-		if (HeapTupleIsValid(tuple = systable_getnext(sysscan)))
-			result = HeapTupleGetOid(tuple);
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_TABLE),
-			   errmsg("relation \"%s\" does not exist", class_name_or_oid)));
-
-		/* We assume there can be only one match */
-
-		systable_endscan(sysscan);
-		heap_close(hdesc, AccessShareLock);
-
-		PG_RETURN_OID(result);
-	}
+		elog(ERROR, "regclass values must be OIDs in bootstrap mode");
 
 	/*
 	 * Normal case: parse the name into components and see if it matches any
@@ -1163,16 +1045,16 @@ regclasssend(PG_FUNCTION_ARGS)
 /*
  * regtypein		- converts "typename" to type OID
  *
- * We also accept a numeric OID, for symmetry with the output routine.
+ * The type name can be specified using the full type syntax recognized by
+ * the parser; for example, DOUBLE PRECISION and INTEGER[] will work and be
+ * translated to the correct type names.  (We ignore any typmod info
+ * generated by the parser, however.)
+ *
+ * We also accept a numeric OID, for symmetry with the output routine,
+ * and for possible use in bootstrap mode.
  *
  * '-' signifies unknown (OID 0).  In all other cases, the input must
  * match an existing pg_type entry.
- *
- * In bootstrap mode the name must just equal some existing name in pg_type.
- * In normal mode the type name can be specified using the full type syntax
- * recognized by the parser; for example, DOUBLE PRECISION and INTEGER[] will
- * work and be translated to the correct type names.  (We ignore any typmod
- * info generated by the parser, however.)
  */
 Datum
 regtypein(PG_FUNCTION_ARGS)
@@ -1197,42 +1079,9 @@ regtypein(PG_FUNCTION_ARGS)
 
 	/* Else it's a type name, possibly schema-qualified or decorated */
 
-	/*
-	 * In bootstrap mode we assume the given name is not schema-qualified, and
-	 * just search pg_type for a match.  This is needed for initializing other
-	 * system catalogs (pg_namespace may not exist yet, and certainly there
-	 * are no schemas other than pg_catalog).
-	 */
+	/* The rest of this wouldn't work in bootstrap mode */
 	if (IsBootstrapProcessingMode())
-	{
-		Relation	hdesc;
-		ScanKeyData skey[1];
-		SysScanDesc sysscan;
-		HeapTuple	tuple;
-
-		ScanKeyInit(&skey[0],
-					Anum_pg_type_typname,
-					BTEqualStrategyNumber, F_NAMEEQ,
-					CStringGetDatum(typ_name_or_oid));
-
-		hdesc = heap_open(TypeRelationId, AccessShareLock);
-		sysscan = systable_beginscan(hdesc, TypeNameNspIndexId, true,
-									 NULL, 1, skey);
-
-		if (HeapTupleIsValid(tuple = systable_getnext(sysscan)))
-			result = HeapTupleGetOid(tuple);
-		else
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_OBJECT),
-					 errmsg("type \"%s\" does not exist", typ_name_or_oid)));
-
-		/* We assume there can be only one match */
-
-		systable_endscan(sysscan);
-		heap_close(hdesc, AccessShareLock);
-
-		PG_RETURN_OID(result);
-	}
+		elog(ERROR, "regtype values must be OIDs in bootstrap mode");
 
 	/*
 	 * Normal case: invoke the full parser to deal with special cases such as
@@ -1342,9 +1191,6 @@ regtypesend(PG_FUNCTION_ARGS)
  *
  * '-' signifies unknown (OID 0).  In all other cases, the input must
  * match an existing pg_ts_config entry.
- *
- * This function is not needed in bootstrap mode, so we don't worry about
- * making it work then.
  */
 Datum
 regconfigin(PG_FUNCTION_ARGS)
@@ -1367,6 +1213,10 @@ regconfigin(PG_FUNCTION_ARGS)
 		PG_RETURN_OID(result);
 	}
 
+	/* The rest of this wouldn't work in bootstrap mode */
+	if (IsBootstrapProcessingMode())
+		elog(ERROR, "regconfig values must be OIDs in bootstrap mode");
+
 	/*
 	 * Normal case: parse the name into components and see if it matches any
 	 * pg_ts_config entries in the current search path.
@@ -1452,9 +1302,6 @@ regconfigsend(PG_FUNCTION_ARGS)
  *
  * '-' signifies unknown (OID 0).  In all other cases, the input must
  * match an existing pg_ts_dict entry.
- *
- * This function is not needed in bootstrap mode, so we don't worry about
- * making it work then.
  */
 Datum
 regdictionaryin(PG_FUNCTION_ARGS)
@@ -1477,6 +1324,10 @@ regdictionaryin(PG_FUNCTION_ARGS)
 		PG_RETURN_OID(result);
 	}
 
+	/* The rest of this wouldn't work in bootstrap mode */
+	if (IsBootstrapProcessingMode())
+		elog(ERROR, "regdictionary values must be OIDs in bootstrap mode");
+
 	/*
 	 * Normal case: parse the name into components and see if it matches any
 	 * pg_ts_dict entries in the current search path.
@@ -1562,9 +1413,6 @@ regdictionarysend(PG_FUNCTION_ARGS)
  *
  * '-' signifies unknown (OID 0).  In all other cases, the input must
  * match an existing pg_authid entry.
- *
- * This function is not needed in bootstrap mode, so we don't worry about
- * making it work then.
  */
 Datum
 regrolein(PG_FUNCTION_ARGS)
@@ -1587,6 +1435,10 @@ regrolein(PG_FUNCTION_ARGS)
 		PG_RETURN_OID(result);
 	}
 
+	/* The rest of this wouldn't work in bootstrap mode */
+	if (IsBootstrapProcessingMode())
+		elog(ERROR, "regrole values must be OIDs in bootstrap mode");
+
 	/* Normal case: see if the name matches any pg_authid entry. */
 	names = stringToQualifiedNameList(role_name_or_oid);
 
@@ -1708,6 +1560,10 @@ regnamespacein(PG_FUNCTION_ARGS)
 		PG_RETURN_OID(result);
 	}
 
+	/* The rest of this wouldn't work in bootstrap mode */
+	if (IsBootstrapProcessingMode())
+		elog(ERROR, "regnamespace values must be OIDs in bootstrap mode");
+
 	/* Normal case: see if the name matches any pg_namespace entry. */
 	names = stringToQualifiedNameList(nsp_name_or_oid);