Implement rr::Print support for Subzero

Factored out most of the rr::Print code in LLVMReactor.cpp to
Reactor.cpp, and rewritten in terms of Nucleus. Added a couple of new
Nucleus functions to support this.

Bug: b/149477527
Change-Id: I0a28626f1aa6133a37f9e75abc08544f3de15a45
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/41188
Presubmit-Ready: Antonio Maiorano <amaiorano@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aaf2be0..4451f44 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -543,9 +543,8 @@
     endif()
 endif()
 
-if((${REACTOR_BACKEND} STREQUAL "Subzero") AND (REACTOR_ENABLE_PRINT OR REACTOR_EMIT_PRINT_LOCATION OR REACTOR_EMIT_DEBUG_INFO))
+if((${REACTOR_BACKEND} STREQUAL "Subzero") AND (REACTOR_EMIT_PRINT_LOCATION OR REACTOR_EMIT_DEBUG_INFO))
     message(WARNING "REACTOR_ENABLE_PRINT, REACTOR_EMIT_PRINT_LOCATION, and REACTOR_EMIT_DEBUG_INFO are not supported by Subzero, disabling.")
-    set(REACTOR_ENABLE_PRINT "Off")
     set(REACTOR_EMIT_PRINT_LOCATION "Off")
     set(REACTOR_EMIT_DEBUG_INFO "Off")
 endif()
diff --git a/src/Reactor/LLVMReactor.cpp b/src/Reactor/LLVMReactor.cpp
index c14012d..7f780a6 100644
--- a/src/Reactor/LLVMReactor.cpp
+++ b/src/Reactor/LLVMReactor.cpp
@@ -77,19 +77,6 @@
 	return config;
 }
 
-#ifdef ENABLE_RR_PRINT
-std::string replace(std::string str, const std::string &substr, const std::string &replacement)
-{
-	size_t pos = 0;
-	while((pos = str.find(substr, pos)) != std::string::npos)
-	{
-		str.replace(pos, substr.length(), replacement);
-		pos += replacement.length();
-	}
-	return str;
-}
-#endif  // ENABLE_RR_PRINT
-
 llvm::Value *lowerPAVG(llvm::Value *x, llvm::Value *y)
 {
 	llvm::VectorType *ty = llvm::cast<llvm::VectorType>(x->getType());
@@ -1567,11 +1554,42 @@
 	jit->builder->CreateUnreachable();
 }
 
+Type *Nucleus::getType(Value *value)
+{
+	return T(V(value)->getType());
+}
+
+Type *Nucleus::getContainedType(Type *vectorType)
+{
+	return T(T(vectorType)->getContainedType(0));
+}
+
 Type *Nucleus::getPointerType(Type *ElementType)
 {
 	return T(llvm::PointerType::get(T(ElementType), 0));
 }
 
+static ::llvm::Type *getNaturalIntType()
+{
+	return ::llvm::Type::getIntNTy(jit->context, sizeof(int) * 8);
+}
+
+Type *Nucleus::getPrintfStorageType(Type *valueType)
+{
+	llvm::Type *valueTy = T(valueType);
+	if(valueTy->isIntegerTy())
+	{
+		return T(getNaturalIntType());
+	}
+	if(valueTy->isFloatTy())
+	{
+		return T(llvm::Type::getDoubleTy(jit->context));
+	}
+
+	UNIMPLEMENTED_NO_BUG("getPrintfStorageType: add more cases as needed");
+	return {};
+}
+
 Value *Nucleus::createNullValue(Type *Ty)
 {
 	RR_DEBUG_INFO_UPDATE_LOC();
@@ -1670,6 +1688,12 @@
 	return V(llvm::ConstantVector::get(llvm::ArrayRef<llvm::Constant *>(constantVector, numElements)));
 }
 
+Value *Nucleus::createConstantString(const char *v)
+{
+	auto ptr = jit->builder->CreateGlobalStringPtr(v);
+	return V(ptr);
+}
+
 Type *Void::getType()
 {
 	return T(llvm::Type::getVoidTy(jit->context));
@@ -3827,189 +3851,13 @@
 #endif  // defined(__i386__) || defined(__x86_64__)
 
 #ifdef ENABLE_RR_PRINT
-// extractAll returns a vector containing the extracted n scalar value of
-// the vector vec.
-static std::vector<Value *> extractAll(Value *vec, int n)
+void VPrintf(const std::vector<Value *> &vals)
 {
-	std::vector<Value *> elements;
-	elements.reserve(n);
-	for(int i = 0; i < n; i++)
-	{
-		auto el = V(jit->builder->CreateExtractElement(V(vec), i));
-		elements.push_back(el);
-	}
-	return elements;
-}
-
-// toInt returns all the integer values in vals extended to a native width
-// integer.
-static std::vector<Value *> toInt(const std::vector<Value *> &vals, bool isSigned)
-{
-	auto intTy = ::llvm::Type::getIntNTy(jit->context, sizeof(int) * 8);  // Natural integer width.
-	std::vector<Value *> elements;
-	elements.reserve(vals.size());
-	for(auto v : vals)
-	{
-		if(isSigned)
-		{
-			elements.push_back(V(jit->builder->CreateSExt(V(v), intTy)));
-		}
-		else
-		{
-			elements.push_back(V(jit->builder->CreateZExt(V(v), intTy)));
-		}
-	}
-	return elements;
-}
-
-// toDouble returns all the float values in vals extended to doubles.
-static std::vector<Value *> toDouble(const std::vector<Value *> &vals)
-{
-	auto doubleTy = ::llvm::Type::getDoubleTy(jit->context);
-	std::vector<Value *> elements;
-	elements.reserve(vals.size());
-	for(auto v : vals)
-	{
-		elements.push_back(V(jit->builder->CreateFPExt(V(v), doubleTy)));
-	}
-	return elements;
-}
-
-std::vector<Value *> PrintValue::Ty<Bool>::val(const RValue<Bool> &v)
-{
-	auto t = jit->builder->CreateGlobalStringPtr("true");
-	auto f = jit->builder->CreateGlobalStringPtr("false");
-	return { V(jit->builder->CreateSelect(V(v.value), t, f)) };
-}
-
-std::vector<Value *> PrintValue::Ty<Byte>::val(const RValue<Byte> &v)
-{
-	return toInt({ v.value }, false);
-}
-
-std::vector<Value *> PrintValue::Ty<Byte4>::val(const RValue<Byte4> &v)
-{
-	return toInt(extractAll(v.value, 4), false);
-}
-
-std::vector<Value *> PrintValue::Ty<Int>::val(const RValue<Int> &v)
-{
-	return toInt({ v.value }, true);
-}
-
-std::vector<Value *> PrintValue::Ty<Int2>::val(const RValue<Int2> &v)
-{
-	return toInt(extractAll(v.value, 2), true);
-}
-
-std::vector<Value *> PrintValue::Ty<Int4>::val(const RValue<Int4> &v)
-{
-	return toInt(extractAll(v.value, 4), true);
-}
-
-std::vector<Value *> PrintValue::Ty<UInt>::val(const RValue<UInt> &v)
-{
-	return toInt({ v.value }, false);
-}
-
-std::vector<Value *> PrintValue::Ty<UInt2>::val(const RValue<UInt2> &v)
-{
-	return toInt(extractAll(v.value, 2), false);
-}
-
-std::vector<Value *> PrintValue::Ty<UInt4>::val(const RValue<UInt4> &v)
-{
-	return toInt(extractAll(v.value, 4), false);
-}
-
-std::vector<Value *> PrintValue::Ty<Short>::val(const RValue<Short> &v)
-{
-	return toInt({ v.value }, true);
-}
-
-std::vector<Value *> PrintValue::Ty<Short4>::val(const RValue<Short4> &v)
-{
-	return toInt(extractAll(v.value, 4), true);
-}
-
-std::vector<Value *> PrintValue::Ty<UShort>::val(const RValue<UShort> &v)
-{
-	return toInt({ v.value }, false);
-}
-
-std::vector<Value *> PrintValue::Ty<UShort4>::val(const RValue<UShort4> &v)
-{
-	return toInt(extractAll(v.value, 4), false);
-}
-
-std::vector<Value *> PrintValue::Ty<Float>::val(const RValue<Float> &v)
-{
-	return toDouble({ v.value });
-}
-
-std::vector<Value *> PrintValue::Ty<Float4>::val(const RValue<Float4> &v)
-{
-	return toDouble(extractAll(v.value, 4));
-}
-
-std::vector<Value *> PrintValue::Ty<const char *>::val(const char *v)
-{
-	return { V(jit->builder->CreateGlobalStringPtr(v)) };
-}
-
-void Printv(const char *function, const char *file, int line, const char *fmt, std::initializer_list<PrintValue> args)
-{
-	// LLVM types used below.
 	auto i32Ty = ::llvm::Type::getInt32Ty(jit->context);
-	auto intTy = ::llvm::Type::getIntNTy(jit->context, sizeof(int) * 8);  // Natural integer width.
 	auto i8PtrTy = ::llvm::Type::getInt8PtrTy(jit->context);
 	auto funcTy = ::llvm::FunctionType::get(i32Ty, { i8PtrTy }, true);
-
 	auto func = jit->module->getOrInsertFunction("printf", funcTy);
-
-	// Build the printf format message string.
-	std::string str;
-	if(file != nullptr) { str += (line > 0) ? "%s:%d " : "%s "; }
-	if(function != nullptr) { str += "%s "; }
-	str += fmt;
-
-	// Perform substitution on all '{n}' bracketed indices in the format
-	// message.
-	int i = 0;
-	for(const PrintValue &arg : args)
-	{
-		str = replace(str, "{" + std::to_string(i++) + "}", arg.format);
-	}
-
-	::llvm::SmallVector<::llvm::Value *, 8> vals;
-
-	// The format message is always the first argument.
-	vals.push_back(jit->builder->CreateGlobalStringPtr(str));
-
-	// Add optional file, line and function info if provided.
-	if(file != nullptr)
-	{
-		vals.push_back(jit->builder->CreateGlobalStringPtr(file));
-		if(line > 0)
-		{
-			vals.push_back(::llvm::ConstantInt::get(intTy, line));
-		}
-	}
-	if(function != nullptr)
-	{
-		vals.push_back(jit->builder->CreateGlobalStringPtr(function));
-	}
-
-	// Add all format arguments.
-	for(const PrintValue &arg : args)
-	{
-		for(auto val : arg.values)
-		{
-			vals.push_back(V(val));
-		}
-	}
-
-	jit->builder->CreateCall(func, vals);
+	jit->builder->CreateCall(func, V(vals));
 }
 #endif  // ENABLE_RR_PRINT
 
diff --git a/src/Reactor/LLVMReactor.hpp b/src/Reactor/LLVMReactor.hpp
index db7df10..fc3f1a2 100644
--- a/src/Reactor/LLVMReactor.hpp
+++ b/src/Reactor/LLVMReactor.hpp
@@ -65,6 +65,17 @@
 	return reinterpret_cast<Value *>(t);
 }
 
+inline std::vector<llvm::Value *> V(const std::vector<Value *> &values)
+{
+	std::vector<llvm::Value *> result;
+	result.reserve(values.size());
+	for(auto &v : values)
+	{
+		result.push_back(V(v));
+	}
+	return result;
+}
+
 // Emits a no-op instruction that will not be optimized away.
 // Useful for emitting something that can have a source location without
 // effect.
diff --git a/src/Reactor/Nucleus.hpp b/src/Reactor/Nucleus.hpp
index 513f4fb..4c08ddd 100644
--- a/src/Reactor/Nucleus.hpp
+++ b/src/Reactor/Nucleus.hpp
@@ -21,6 +21,7 @@
 #include <cstdint>
 #include <functional>
 #include <memory>
+#include <string>
 #include <vector>
 
 #ifdef None
@@ -326,8 +327,13 @@
 	static Value *createNullPointer(Type *type);
 	static Value *createConstantVector(const int64_t *constants, Type *type);
 	static Value *createConstantVector(const double *constants, Type *type);
+	static Value *createConstantString(const char *v);
+	static Value *createConstantString(const std::string &v) { return createConstantString(v.c_str()); }
 
+	static Type *getType(Value *value);
+	static Type *getContainedType(Type *vectorType);
 	static Type *getPointerType(Type *elementType);
+	static Type *getPrintfStorageType(Type *valueType);
 };
 
 }  // namespace rr
diff --git a/src/Reactor/Print.hpp b/src/Reactor/Print.hpp
index a5bde3e..ca70465 100644
--- a/src/Reactor/Print.hpp
+++ b/src/Reactor/Print.hpp
@@ -337,6 +337,10 @@
 	static std::vector<Value *> val(const RValue<T> &v) { return PrintValue::Ty<T>::val(v); }
 };
 
+// VPrintf emits a call to printf() using vals[0] as the format string,
+// and vals[1..n] as the args.
+void VPrintf(const std::vector<Value *> &vals);
+
 // Printv emits a call to printf() using the function, file and line,
 // message and optional values.
 // See Printv below.
diff --git a/src/Reactor/Reactor.cpp b/src/Reactor/Reactor.cpp
index 8801f2a..abe4882 100644
--- a/src/Reactor/Reactor.cpp
+++ b/src/Reactor/Reactor.cpp
@@ -14,6 +14,7 @@
 
 #include "Reactor.hpp"
 #include "Debug.hpp"
+#include "Print.hpp"
 
 #include <cmath>
 
@@ -4596,4 +4597,197 @@
 // TODO: Long has no constructor that takes a uint64_t
 // Long          CToReactor<uint64_t>::cast(uint64_t v)       { return type(v); }
 
+#ifdef ENABLE_RR_PRINT
+static std::string replaceAll(std::string str, const std::string &substr, const std::string &replacement)
+{
+	size_t pos = 0;
+	while((pos = str.find(substr, pos)) != std::string::npos)
+	{
+		str.replace(pos, substr.length(), replacement);
+		pos += replacement.length();
+	}
+	return str;
+}
+
+// extractAll returns a vector containing the extracted n scalar value of
+// the vector vec.
+// TODO: Move to Reactor.cpp (LLVMReactor can use this too)
+static std::vector<Value *> extractAll(Value *vec, int n)
+{
+	Type *elemTy = Nucleus::getContainedType(Nucleus::getType(vec));
+	std::vector<Value *> elements;
+	elements.reserve(n);
+	for(int i = 0; i < n; i++)
+	{
+		auto el = Nucleus::createExtractElement(vec, elemTy, i);
+		elements.push_back(el);
+	}
+	return elements;
+}
+
+// toInt returns all the integer values in vals extended to a printf-required storage value
+static std::vector<Value *> toInt(const std::vector<Value *> &vals, bool isSigned)
+{
+	auto storageTy = Nucleus::getPrintfStorageType(Int::getType());
+	std::vector<Value *> elements;
+	elements.reserve(vals.size());
+	for(auto v : vals)
+	{
+		if(isSigned)
+		{
+			elements.push_back(Nucleus::createSExt(v, storageTy));
+		}
+		else
+		{
+			elements.push_back(Nucleus::createZExt(v, storageTy));
+		}
+	}
+	return elements;
+}
+
+// toFloat returns all the float values in vals extended to extended to a printf-required storage value
+static std::vector<Value *> toFloat(const std::vector<Value *> &vals)
+{
+	auto storageTy = Nucleus::getPrintfStorageType(Float::getType());
+	std::vector<Value *> elements;
+	elements.reserve(vals.size());
+	for(auto v : vals)
+	{
+		elements.push_back(Nucleus::createFPExt(v, storageTy));
+	}
+	return elements;
+}
+
+std::vector<Value *> PrintValue::Ty<Bool>::val(const RValue<Bool> &v)
+{
+	auto t = Nucleus::createConstantString("true");
+	auto f = Nucleus::createConstantString("false");
+	return { Nucleus::createSelect(v.value, t, f) };
+}
+
+std::vector<Value *> PrintValue::Ty<Byte>::val(const RValue<Byte> &v)
+{
+	return toInt({ v.value }, false);
+}
+
+std::vector<Value *> PrintValue::Ty<Byte4>::val(const RValue<Byte4> &v)
+{
+	return toInt(extractAll(v.value, 4), false);
+}
+
+std::vector<Value *> PrintValue::Ty<Int>::val(const RValue<Int> &v)
+{
+	return toInt({ v.value }, true);
+}
+
+std::vector<Value *> PrintValue::Ty<Int2>::val(const RValue<Int2> &v)
+{
+	return toInt(extractAll(v.value, 2), true);
+}
+
+std::vector<Value *> PrintValue::Ty<Int4>::val(const RValue<Int4> &v)
+{
+	return toInt(extractAll(v.value, 4), true);
+}
+
+std::vector<Value *> PrintValue::Ty<UInt>::val(const RValue<UInt> &v)
+{
+	return toInt({ v.value }, false);
+}
+
+std::vector<Value *> PrintValue::Ty<UInt2>::val(const RValue<UInt2> &v)
+{
+	return toInt(extractAll(v.value, 2), false);
+}
+
+std::vector<Value *> PrintValue::Ty<UInt4>::val(const RValue<UInt4> &v)
+{
+	return toInt(extractAll(v.value, 4), false);
+}
+
+std::vector<Value *> PrintValue::Ty<Short>::val(const RValue<Short> &v)
+{
+	return toInt({ v.value }, true);
+}
+
+std::vector<Value *> PrintValue::Ty<Short4>::val(const RValue<Short4> &v)
+{
+	return toInt(extractAll(v.value, 4), true);
+}
+
+std::vector<Value *> PrintValue::Ty<UShort>::val(const RValue<UShort> &v)
+{
+	return toInt({ v.value }, false);
+}
+
+std::vector<Value *> PrintValue::Ty<UShort4>::val(const RValue<UShort4> &v)
+{
+	return toInt(extractAll(v.value, 4), false);
+}
+
+std::vector<Value *> PrintValue::Ty<Float>::val(const RValue<Float> &v)
+{
+	return toFloat({ v.value });
+}
+
+std::vector<Value *> PrintValue::Ty<Float4>::val(const RValue<Float4> &v)
+{
+	return toFloat(extractAll(v.value, 4));
+}
+
+std::vector<Value *> PrintValue::Ty<const char *>::val(const char *v)
+{
+	return { Nucleus::createConstantString(v) };
+}
+
+void Printv(const char *function, const char *file, int line, const char *fmt, std::initializer_list<PrintValue> args)
+{
+	// Build the printf format message string.
+	std::string str;
+	if(file != nullptr) { str += (line > 0) ? "%s:%d " : "%s "; }
+	if(function != nullptr) { str += "%s "; }
+	str += fmt;
+
+	// Perform substitution on all '{n}' bracketed indices in the format
+	// message.
+	int i = 0;
+	for(const PrintValue &arg : args)
+	{
+		str = replaceAll(str, "{" + std::to_string(i++) + "}", arg.format);
+	}
+
+	std::vector<Value *> vals;
+	vals.reserve(8);
+
+	// The format message is always the first argument.
+	vals.push_back(Nucleus::createConstantString(str));
+
+	// Add optional file, line and function info if provided.
+	if(file != nullptr)
+	{
+		vals.push_back(Nucleus::createConstantString(file));
+		if(line > 0)
+		{
+			vals.push_back(Nucleus::createConstantInt(line));
+		}
+	}
+	if(function != nullptr)
+	{
+		vals.push_back(Nucleus::createConstantString(function));
+	}
+
+	// Add all format arguments.
+	for(const PrintValue &arg : args)
+	{
+		for(auto val : arg.values)
+		{
+			vals.push_back(val);
+		}
+	}
+
+	// This call is implemented by each backend
+	VPrintf(vals);
+}
+#endif  // ENABLE_RR_PRINT
+
 }  // namespace rr
diff --git a/src/Reactor/SubzeroReactor.cpp b/src/Reactor/SubzeroReactor.cpp
index f2dbdef..29aa1c8 100644
--- a/src/Reactor/SubzeroReactor.cpp
+++ b/src/Reactor/SubzeroReactor.cpp
@@ -14,6 +14,7 @@
 
 #include "Debug.hpp"
 #include "EmulatedReactor.hpp"
+#include "Print.hpp"
 #include "Reactor.hpp"
 
 #include "ExecutableMemory.hpp"
@@ -143,11 +144,8 @@
 }
 
 // Wrapper for calls on C functions with Ice types
-template<typename Return, typename... CArgs, typename... RArgs>
-Ice::Variable *Call(Ice::Cfg *function, Ice::CfgNode *basicBlock, Return(fptr)(CArgs...), RArgs &&... args)
+Ice::Variable *Call(Ice::Cfg *function, Ice::CfgNode *basicBlock, Ice::Type retTy, void const *fptr, const std::vector<Ice::Operand *> &iceArgs)
 {
-	Ice::Type retTy = T(rr::CToReactorT<Return>::getType());
-
 	// Subzero doesn't support boolean return values. Replace with an i32.
 	if(retTy == Ice::IceType_i1)
 	{
@@ -160,9 +158,7 @@
 		ret = function->makeVariable(retTy);
 	}
 
-	std::array<Ice::Variable *, sizeof...(args)> iceArgs{ { std::forward<RArgs>(args)... } };
-
-	auto call = Ice::InstCall::create(function, iceArgs.size(), ret, getConstantPointer(function->getContext(), reinterpret_cast<void const *>(fptr)), false);
+	auto call = Ice::InstCall::create(function, iceArgs.size(), ret, getConstantPointer(function->getContext(), fptr), false);
 	for(auto arg : iceArgs)
 	{
 		call->addArg(arg);
@@ -172,6 +168,15 @@
 	return ret;
 }
 
+// Wrapper for calls on C functions with Ice types
+template<typename Return, typename... CArgs, typename... RArgs>
+Ice::Variable *Call(Ice::Cfg *function, Ice::CfgNode *basicBlock, Return(fptr)(CArgs...), RArgs &&... args)
+{
+	Ice::Type retTy = T(rr::CToReactorT<Return>::getType());
+	std::vector<Ice::Operand *> iceArgs{ std::forward<RArgs>(args)... };
+	return Call(function, basicBlock, retTy, reinterpret_cast<void const *>(fptr), iceArgs);
+}
+
 // Returns a non-const variable copy of const v
 Ice::Variable *createUnconstCast(Ice::Cfg *function, Ice::CfgNode *basicBlock, Ice::Constant *v)
 {
@@ -414,6 +419,17 @@
 	return reinterpret_cast<Ice::Operand *>(v);
 }
 
+std::vector<Ice::Operand *> V(const std::vector<Value *> &values)
+{
+	std::vector<Ice::Operand *> result;
+	result.reserve(values.size());
+	for(auto &v : values)
+	{
+		result.push_back(V(v));
+	}
+	return result;
+}
+
 BasicBlock *B(Ice::CfgNode *b)
 {
 	return reinterpret_cast<BasicBlock *>(b);
@@ -798,6 +814,13 @@
 	std::vector<std::unique_ptr<uint8_t[]>> constantData;
 };
 
+#ifdef ENABLE_RR_PRINT
+void VPrintf(const std::vector<Value *> &vals)
+{
+	sz::Call(::function, ::basicBlock, Ice::IceType_i32, reinterpret_cast<const void *>(::printf), V(vals));
+}
+#endif  // ENABLE_RR_PRINT
+
 Nucleus::Nucleus()
 {
 	::codegenMutex.lock();  // Reactor is currently not thread safe
@@ -1701,7 +1724,7 @@
 Value *Nucleus::createExtractElement(Value *vector, Type *type, int index)
 {
 	auto result = ::function->makeVariable(T(type));
-	auto extract = Ice::InstExtractElement::create(::function, result, vector, ::context->getConstantInt32(index));
+	auto extract = Ice::InstExtractElement::create(::function, result, V(vector), ::context->getConstantInt32(index));
 	::basicBlock->appendInst(extract);
 
 	return V(result);
@@ -1764,11 +1787,58 @@
 	::basicBlock->appendInst(unreachable);
 }
 
+Type *Nucleus::getType(Value *value)
+{
+	return T(V(value)->getType());
+}
+
+Type *Nucleus::getContainedType(Type *vectorType)
+{
+	Ice::Type vecTy = T(vectorType);
+	switch(vecTy)
+	{
+		case Ice::IceType_v4i1: return T(Ice::IceType_i1);
+		case Ice::IceType_v8i1: return T(Ice::IceType_i1);
+		case Ice::IceType_v16i1: return T(Ice::IceType_i1);
+		case Ice::IceType_v16i8: return T(Ice::IceType_i8);
+		case Ice::IceType_v8i16: return T(Ice::IceType_i16);
+		case Ice::IceType_v4i32: return T(Ice::IceType_i32);
+		case Ice::IceType_v4f32: return T(Ice::IceType_f32);
+		default:
+			ASSERT_MSG(false, "getContainedType: input type is not a vector type");
+			return {};
+	}
+}
+
 Type *Nucleus::getPointerType(Type *ElementType)
 {
 	return T(sz::getPointerType(T(ElementType)));
 }
 
+static constexpr Ice::Type getNaturalIntType()
+{
+	constexpr size_t intSize = sizeof(int);
+	static_assert(intSize == 4 || intSize == 8, "");
+	return intSize == 4 ? Ice::IceType_i32 : Ice::IceType_i64;
+}
+
+Type *Nucleus::getPrintfStorageType(Type *valueType)
+{
+	Ice::Type valueTy = T(valueType);
+	switch(valueTy)
+	{
+		case Ice::IceType_i32:
+			return T(getNaturalIntType());
+
+		case Ice::IceType_f32:
+			return T(Ice::IceType_f64);
+
+		default:
+			UNIMPLEMENTED_NO_BUG("getPrintfStorageType: add more cases as needed");
+			return {};
+	}
+}
+
 Value *Nucleus::createNullValue(Type *Ty)
 {
 	if(Ice::isVectorType(T(Ty)))
@@ -1933,6 +2003,11 @@
 	return createConstantVector((const int64_t *)constants, type);
 }
 
+Value *Nucleus::createConstantString(const char *v)
+{
+	return V(IceConstantData(v, strlen(v) + 1));
+}
+
 Type *Void::getType()
 {
 	return T(Ice::IceType_void);