2401 lines
77 KiB
C++
2401 lines
77 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
KismetCompilerVMBackend.cpp
|
|
=============================================================================*/
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Misc/CoreMisc.h"
|
|
#include "UObject/Script.h"
|
|
#include "UObject/ObjectMacros.h"
|
|
#include "Serialization/ArchiveUObject.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/SoftObjectPtr.h"
|
|
#include "UObject/Interface.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/TextProperty.h"
|
|
#include "UObject/FieldPathProperty.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
#include "Engine/LatentActionManager.h"
|
|
#include "StructUtils/UserDefinedStruct.h"
|
|
#include "BPTerminal.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node_MacroInstance.h"
|
|
#include "BlueprintCompiledStatement.h"
|
|
#include "Engine/BlueprintGeneratedClass.h"
|
|
#include "KismetCompiledFunctionContext.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
|
|
#include "KismetCompilerMisc.h"
|
|
#include "KismetCompilerBackend.h"
|
|
|
|
#include "Misc/DefaultValueHelper.h"
|
|
#include "ProjectUtilities/BuildTargetSet.h"
|
|
|
|
#include "Kismet2/StructureEditorUtils.h"
|
|
#include "Kismet2/KismetDebugUtilities.h"
|
|
|
|
#include "Internationalization/TextNamespaceUtil.h"
|
|
#include "Internationalization/TextPackageNamespaceUtil.h"
|
|
#include "Internationalization/StringTableRegistry.h"
|
|
#include "Internationalization/StringTableCore.h"
|
|
#include "Internationalization/StringTable.h"
|
|
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "KismetCompilerVMBackend"
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FScriptBytecodeWriter
|
|
|
|
//
|
|
// Little class for writing to scripts.
|
|
//
|
|
class FScriptBytecodeWriter : public FArchiveUObject
|
|
{
|
|
public:
|
|
TArray<uint8>& ScriptBuffer;
|
|
public:
|
|
FScriptBytecodeWriter( TArray<uint8>& InScriptBuffer )
|
|
: ScriptBuffer( InScriptBuffer )
|
|
{
|
|
}
|
|
|
|
void Serialize( void* V, int64 Length ) override
|
|
{
|
|
int32 iStart = ScriptBuffer.AddUninitialized(IntCastChecked<int32, int64>(Length));
|
|
FMemory::Memcpy( &(ScriptBuffer[iStart]), V, Length );
|
|
}
|
|
|
|
using FArchiveUObject::operator<<; // For visibility of the overloads we don't override
|
|
|
|
FArchive& operator<<(FName& Name) override
|
|
{
|
|
FArchive& Ar = *this;
|
|
|
|
// This must match the format and endianness expected by XFERNAME
|
|
FNameEntryId ComparisonIndex = Name.GetComparisonIndex(), DisplayIndex = Name.GetDisplayIndex();
|
|
uint32 Number = Name.GetNumber();
|
|
Ar << ComparisonIndex;
|
|
Ar << DisplayIndex;
|
|
Ar << Number;
|
|
|
|
return Ar;
|
|
}
|
|
|
|
FArchive& operator<<(UObject*& Res) override
|
|
{
|
|
ScriptPointerType D = (ScriptPointerType)Res;
|
|
FArchive& Ar = *this;
|
|
|
|
Ar << D;
|
|
return Ar;
|
|
}
|
|
|
|
FArchive& operator<<(FObjectPtr& Res) override
|
|
{
|
|
#if UE_WITH_OBJECT_HANDLE_LATE_RESOLVE
|
|
ScriptPointerType D = (ScriptPointerType)Res.GetHandle().PointerOrRef;
|
|
#elif UE_WITH_REMOTE_OBJECT_HANDLE
|
|
ScriptPointerType D = (ScriptPointerType)Res.GetHandle().PointerOrHandle;
|
|
#else
|
|
ScriptPointerType D = (ScriptPointerType)Res.GetHandle();
|
|
#endif
|
|
FArchive& Ar = *this;
|
|
|
|
Ar << D;
|
|
return Ar;
|
|
}
|
|
FArchive& operator<<(FField*& Res) override
|
|
{
|
|
ScriptPointerType D = (ScriptPointerType)Res;
|
|
FArchive& Ar = *this;
|
|
Ar << D;
|
|
return Ar;
|
|
}
|
|
|
|
FArchive& operator<<(FLazyObjectPtr& LazyObjectPtr) override
|
|
{
|
|
return FArchive::operator<<(LazyObjectPtr);
|
|
}
|
|
|
|
FArchive& operator<<(FSoftObjectPtr& Value) override
|
|
{
|
|
return FArchive::operator<<(Value);
|
|
}
|
|
|
|
FArchive& operator<<(FSoftObjectPath& Value) override
|
|
{
|
|
return FArchiveUObject::operator<<(Value);
|
|
}
|
|
|
|
FArchive& operator<<(TCHAR* S)
|
|
{
|
|
Serialize(S, FCString::Strlen(S) + 1);
|
|
return *this;
|
|
}
|
|
|
|
FArchive& operator<<(EExprToken E)
|
|
{
|
|
checkSlow(E < 0xFF);
|
|
|
|
uint8 B = static_cast<uint8>(E);
|
|
Serialize(&B, 1);
|
|
return *this;
|
|
}
|
|
|
|
FArchive& operator<<(ECastToken E)
|
|
{
|
|
uint8 B = static_cast<uint8>(E);
|
|
Serialize(&B, 1);
|
|
return *this;
|
|
}
|
|
|
|
FArchive& operator<<(EBlueprintTextLiteralType E)
|
|
{
|
|
static_assert(sizeof(__underlying_type(EBlueprintTextLiteralType)) == sizeof(uint8), "EBlueprintTextLiteralType is expected to be a uint8");
|
|
|
|
uint8 B = (uint8)E;
|
|
Serialize(&B, 1);
|
|
return *this;
|
|
}
|
|
|
|
FArchive& operator<<(EPropertyType E)
|
|
{
|
|
uint8 B = static_cast<uint8>(E);
|
|
Serialize(&B, 1);
|
|
return *this;
|
|
}
|
|
|
|
CodeSkipSizeType EmitPlaceholderSkip()
|
|
{
|
|
CodeSkipSizeType Result = ScriptBuffer.Num();
|
|
|
|
CodeSkipSizeType Placeholder = -1;
|
|
(*this) << Placeholder;
|
|
|
|
return Result;
|
|
}
|
|
|
|
void CommitSkip(CodeSkipSizeType WriteOffset, CodeSkipSizeType NewValue)
|
|
{
|
|
//@TODO: Any endian issues?
|
|
#if SCRIPT_LIMIT_BYTECODE_TO_64KB
|
|
static_assert(sizeof(CodeSkipSizeType) == 2, "Update this code as size changed.");
|
|
ScriptBuffer[WriteOffset] = NewValue & 0xFF;
|
|
ScriptBuffer[WriteOffset+1] = (NewValue >> 8) & 0xFF;
|
|
#else
|
|
static_assert(sizeof(CodeSkipSizeType) == 4, "Update this code as size changed.");
|
|
ScriptBuffer[WriteOffset] = NewValue & 0xFF;
|
|
ScriptBuffer[WriteOffset+1] = (NewValue >> 8) & 0xFF;
|
|
ScriptBuffer[WriteOffset+2] = (NewValue >> 16) & 0xFF;
|
|
ScriptBuffer[WriteOffset+3] = (NewValue >> 24) & 0xFF;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FSkipOffsetEmitter
|
|
|
|
struct FSkipOffsetEmitter
|
|
{
|
|
CodeSkipSizeType SkipWriteIndex;
|
|
CodeSkipSizeType StartIndex;
|
|
TArray<uint8>& Script;
|
|
|
|
FSkipOffsetEmitter(TArray<uint8>& InScript)
|
|
: SkipWriteIndex(-1)
|
|
, StartIndex(-1)
|
|
, Script(InScript)
|
|
{
|
|
}
|
|
|
|
void Emit()
|
|
{
|
|
SkipWriteIndex = (CodeSkipSizeType)Script.Num();
|
|
StartIndex = SkipWriteIndex;
|
|
|
|
// Reserve space
|
|
for (int32 i = 0; i < sizeof(CodeSkipSizeType); ++i)
|
|
{
|
|
Script.Add(0);
|
|
}
|
|
}
|
|
|
|
void BeginCounting()
|
|
{
|
|
StartIndex = Script.Num();
|
|
}
|
|
|
|
void Commit()
|
|
{
|
|
check(SkipWriteIndex != -1);
|
|
CodeSkipSizeType BytesToSkip = Script.Num() - StartIndex;
|
|
|
|
//@TODO: Any endian issues?
|
|
#if SCRIPT_LIMIT_BYTECODE_TO_64KB
|
|
static_assert(sizeof(CodeSkipSizeType) == 2, "Update this code as size changed.");
|
|
Script[SkipWriteIndex] = BytesToSkip & 0xFF;
|
|
Script[SkipWriteIndex+1] = (BytesToSkip >> 8) & 0xFF;
|
|
#else
|
|
static_assert(sizeof(CodeSkipSizeType) == 4, "Update this code as size changed.");
|
|
Script[SkipWriteIndex] = BytesToSkip & 0xFF;
|
|
Script[SkipWriteIndex+1] = (BytesToSkip >> 8) & 0xFF;
|
|
Script[SkipWriteIndex+2] = (BytesToSkip >> 16) & 0xFF;
|
|
Script[SkipWriteIndex+3] = (BytesToSkip >> 24) & 0xFF;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FCodeSkipInfo
|
|
|
|
class FCodeSkipInfo
|
|
{
|
|
public:
|
|
enum ECodeSkipType
|
|
{
|
|
Fixup = 0,
|
|
InstrumentedDelegateFixup
|
|
};
|
|
FCodeSkipInfo(ECodeSkipType TypeIn, FBlueprintCompiledStatement* TargetLabelIn = nullptr, FBlueprintCompiledStatement* SourceLabelIn = nullptr)
|
|
: Type(TypeIn)
|
|
, SourceLabel(SourceLabelIn)
|
|
, TargetLabel(TargetLabelIn)
|
|
{
|
|
}
|
|
|
|
ECodeSkipType Type;
|
|
FBlueprintCompiledStatement* SourceLabel;
|
|
FBlueprintCompiledStatement* TargetLabel;
|
|
FName DelegateName;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FScriptBuilderBase
|
|
|
|
class FScriptBuilderBase
|
|
{
|
|
private:
|
|
FScriptBytecodeWriter Writer;
|
|
UBlueprintGeneratedClass* ClassBeingBuilt;
|
|
UEdGraphSchema_K2* Schema;
|
|
friend class FContextEmitter;
|
|
|
|
// Pointers to commonly used structures (found in constructor)
|
|
UScriptStruct* VectorStruct;
|
|
UScriptStruct* Vector3fStruct;
|
|
UScriptStruct* RotatorStruct;
|
|
UScriptStruct* TransformStruct;
|
|
UScriptStruct* LatentInfoStruct;
|
|
UScriptStruct* ProfileStruct;
|
|
|
|
FKismetCompilerVMBackend::TStatementToSkipSizeMap StatementLabelMap;
|
|
FKismetCompilerVMBackend::TStatementToSkipSizeMap& UbergraphStatementLabelMap;
|
|
|
|
// Fixup list for jump targets (location to overwrite; jump target)
|
|
TMap<CodeSkipSizeType, FCodeSkipInfo> JumpTargetFixupMap;
|
|
|
|
// Is this compiling the ubergraph?
|
|
bool bIsUbergraph;
|
|
|
|
FBlueprintCompiledStatement& ReturnStatement;
|
|
|
|
FKismetCompilerContext* CurrentCompilerContext;
|
|
FKismetFunctionContext* CurrentFunctionContext;
|
|
|
|
// Pure node count/starting offset (used for instrumentation)
|
|
int32 PureNodeEntryCount;
|
|
int32 PureNodeEntryStart;
|
|
|
|
protected:
|
|
/**
|
|
* This class is designed to be used like so to emit a bytecode context expression:
|
|
*
|
|
* {
|
|
* FContextEmitter ContextHandler;
|
|
* if (Needs Context)
|
|
* {
|
|
* ContextHandler.StartContext(context);
|
|
* }
|
|
* Do stuff predicated on context
|
|
* // Emitter closes when it falls out of scope
|
|
* }
|
|
*/
|
|
struct FContextEmitter
|
|
{
|
|
private:
|
|
FScriptBuilderBase& ScriptBuilder;
|
|
FScriptBytecodeWriter& Writer;
|
|
TArray<FSkipOffsetEmitter> SkipperStack;
|
|
bool bInContext;
|
|
public:
|
|
FContextEmitter(FScriptBuilderBase& InScriptBuilder)
|
|
: ScriptBuilder(InScriptBuilder)
|
|
, Writer(ScriptBuilder.Writer)
|
|
, bInContext(false)
|
|
{
|
|
}
|
|
|
|
/** Starts a context if the Term isn't NULL */
|
|
void TryStartContext(FBPTerminal* Term, bool bUnsafeToSkip = false, bool bIsInterfaceContext = false, FProperty* RValueProperty = nullptr)
|
|
{
|
|
if (Term != NULL)
|
|
{
|
|
StartContext(Term, bUnsafeToSkip, bIsInterfaceContext, RValueProperty);
|
|
}
|
|
}
|
|
|
|
void StartContext(FBPTerminal* Term, bool bUnsafeToSkip = false, bool bIsInterfaceContext = false, FProperty* RValueProperty = nullptr)
|
|
{
|
|
bInContext = true;
|
|
|
|
if(Term->IsClassContextType())
|
|
{
|
|
Writer << EX_ClassContext;
|
|
}
|
|
else
|
|
{
|
|
static const FBoolConfigValueHelper CanSuppressAccessViolation(TEXT("Kismet"), TEXT("bCanSuppressAccessViolation"), GEngineIni);
|
|
if (bUnsafeToSkip || !CanSuppressAccessViolation)
|
|
{
|
|
Writer << EX_Context;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_Context_FailSilent;
|
|
}
|
|
|
|
if (bIsInterfaceContext)
|
|
{
|
|
Writer << EX_InterfaceContext;
|
|
}
|
|
}
|
|
|
|
ScriptBuilder.EmitTerm(Term);
|
|
|
|
// Skip offset if the expression evaluates to null (counting from later on)
|
|
FSkipOffsetEmitter Skipper(ScriptBuilder.Writer.ScriptBuffer);
|
|
Skipper.Emit();
|
|
|
|
// R-Value property, see ReadVariableSize in UObject::ProcessContextOpcode() for usage
|
|
Writer << RValueProperty;
|
|
|
|
// Context expression (this is the part that gets skipped if the object turns out NULL)
|
|
Skipper.BeginCounting();
|
|
|
|
SkipperStack.Push( Skipper );
|
|
}
|
|
|
|
void CloseContext()
|
|
{
|
|
// Point to skip to (end of sequence)
|
|
for (int32 i = 0; i < SkipperStack.Num(); ++i)
|
|
{
|
|
SkipperStack[i].Commit();
|
|
}
|
|
|
|
bInContext = false;
|
|
}
|
|
|
|
~FContextEmitter()
|
|
{
|
|
if (bInContext)
|
|
{
|
|
CloseContext();
|
|
}
|
|
}
|
|
};
|
|
public:
|
|
FScriptBuilderBase(TArray<uint8>& InScript, UBlueprintGeneratedClass* InClass, UEdGraphSchema_K2* InSchema, FKismetCompilerVMBackend::TStatementToSkipSizeMap& InUbergraphStatementLabelMap, bool bInIsUbergraph, FBlueprintCompiledStatement& InReturnStatement)
|
|
: Writer(InScript)
|
|
, ClassBeingBuilt(InClass)
|
|
, Schema(InSchema)
|
|
, UbergraphStatementLabelMap(InUbergraphStatementLabelMap)
|
|
, bIsUbergraph(bInIsUbergraph)
|
|
, ReturnStatement(InReturnStatement)
|
|
, CurrentCompilerContext(nullptr)
|
|
, CurrentFunctionContext(nullptr)
|
|
, PureNodeEntryCount(0)
|
|
, PureNodeEntryStart(0)
|
|
{
|
|
VectorStruct = TBaseStructure<FVector>::Get();
|
|
Vector3fStruct = TVariantStructure<FVector3f>::Get();
|
|
RotatorStruct = TBaseStructure<FRotator>::Get();
|
|
TransformStruct = TBaseStructure<FTransform>::Get();
|
|
LatentInfoStruct = FLatentActionInfo::StaticStruct();
|
|
}
|
|
|
|
void CopyStatementMapToUbergraphMap()
|
|
{
|
|
UbergraphStatementLabelMap = StatementLabelMap;
|
|
}
|
|
|
|
void EmitStringLiteral(const FString& String)
|
|
{
|
|
if (FCString::IsPureAnsi(*String))
|
|
{
|
|
Writer << EX_StringConst;
|
|
uint8 OutCh;
|
|
for (const TCHAR* Ch = *String; *Ch; ++Ch)
|
|
{
|
|
OutCh = CharCast<ANSICHAR>(*Ch);
|
|
Writer << OutCh;
|
|
}
|
|
|
|
OutCh = 0;
|
|
Writer << OutCh;
|
|
}
|
|
else
|
|
{
|
|
// Note: This is a no-op on platforms that are using a 16-bit TCHAR
|
|
FTCHARToUTF16 UTF16String(*String, String.Len() + 1); // include the null terminator
|
|
|
|
Writer << EX_UnicodeStringConst;
|
|
uint16 OutCh;
|
|
for (const UTF16CHAR* Ch = UTF16String.Get(); *Ch; ++Ch)
|
|
{
|
|
OutCh = *Ch;
|
|
Writer << OutCh;
|
|
}
|
|
|
|
OutCh = 0;
|
|
Writer << OutCh;
|
|
}
|
|
}
|
|
|
|
struct FLiteralTypeHelper
|
|
{
|
|
static bool IsBoolean(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FBoolProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Boolean);
|
|
}
|
|
|
|
static bool IsBit(const FProperty* Property)
|
|
{
|
|
if (Property && Property->GetOwnerStruct() && !CastFieldChecked<FBoolProperty>(Property)->IsNativeBool())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsString(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FStrProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_String);
|
|
}
|
|
|
|
static bool IsText(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FTextProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Text);
|
|
}
|
|
|
|
static bool IsFloat(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FFloatProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Real) && (Type->PinSubCategory == UEdGraphSchema_K2::PC_Float);
|
|
}
|
|
|
|
static bool IsDouble(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FDoubleProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Real) && (Type->PinSubCategory == UEdGraphSchema_K2::PC_Double);
|
|
}
|
|
|
|
static bool IsInt(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FIntProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Int);
|
|
}
|
|
|
|
static bool IsInt64(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FInt64Property>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Int64);
|
|
}
|
|
|
|
static bool IsUInt64(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FUInt64Property>();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool IsByte(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FByteProperty>() || (Property->IsA<FEnumProperty>() && static_cast<const FEnumProperty*>(Property)->GetUnderlyingProperty()->IsA<FByteProperty>());
|
|
}
|
|
return Type && ((Type->PinCategory == UEdGraphSchema_K2::PC_Byte) || (Type->PinCategory == UEdGraphSchema_K2::PC_Enum));
|
|
}
|
|
|
|
static bool IsName(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FNameProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Name);
|
|
}
|
|
|
|
static bool IsStruct(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FStructProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Struct);
|
|
}
|
|
|
|
static bool IsDelegate(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FDelegateProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Delegate);
|
|
}
|
|
|
|
static bool IsSoftObject(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FSoftObjectProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_SoftObject);
|
|
}
|
|
|
|
// Will handle Class properties as well
|
|
static bool IsObject(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FObjectPropertyBase>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Object);
|
|
}
|
|
|
|
static bool IsClass(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FClassProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Class);
|
|
}
|
|
|
|
static bool IsInterface(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FInterfaceProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Interface);
|
|
}
|
|
|
|
static bool IsFieldPath(const FEdGraphPinType* Type, const FProperty* Property)
|
|
{
|
|
if (Property)
|
|
{
|
|
return Property->IsA<FFieldPathProperty>();
|
|
}
|
|
return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_FieldPath);
|
|
}
|
|
};
|
|
|
|
void EmitTermExpr(FBPTerminal* Term, const FProperty* CoerceProperty = NULL, bool bAllowStaticArray = false, bool bCallerRequiresBit = false)
|
|
{
|
|
constexpr EPropertyPortFlags ImportTextPortFlags = EPropertyPortFlags::PPF_SerializedAsImportText;
|
|
if (Term->bIsLiteral)
|
|
{
|
|
check(!Term->Type.IsContainer() || CoerceProperty);
|
|
|
|
// Additional Validation, since we cannot trust custom k2nodes
|
|
if (CoerceProperty && ensure(Schema) && ensure(CurrentCompilerContext))
|
|
{
|
|
const bool bSpecialCaseSelf = (Term->Type.PinSubCategory == UEdGraphSchema_K2::PN_Self);
|
|
if(!bSpecialCaseSelf)
|
|
{
|
|
FEdGraphPinType TrueType;
|
|
const bool bValidProperty = Schema->ConvertPropertyToPinType(CoerceProperty, TrueType);
|
|
|
|
auto AreTypesBinaryCompatible = [](const FEdGraphPinType& TypeA, const FEdGraphPinType& TypeB) -> bool
|
|
{
|
|
if (TypeA.PinCategory != TypeB.PinCategory)
|
|
{
|
|
return false;
|
|
}
|
|
if ((TypeA.ContainerType != TypeB.ContainerType)
|
|
|| (TypeA.bIsWeakPointer != TypeB.bIsWeakPointer))
|
|
{
|
|
return false;
|
|
}
|
|
if (TypeA.PinCategory == UEdGraphSchema_K2::PC_Struct)
|
|
{
|
|
if (TypeA.PinSubCategoryObject != TypeB.PinSubCategoryObject)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (bValidProperty && !AreTypesBinaryCompatible(Term->Type, TrueType))
|
|
{
|
|
const FString ErrorMessage = FString::Printf(TEXT("ICE: The type of property %s doesn't match the terminal type for pin @@."), *CoerceProperty->GetPathName());
|
|
CurrentCompilerContext->MessageLog.Error(*ErrorMessage, Term->SourcePin);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FLiteralTypeHelper::IsString(&Term->Type, CoerceProperty))
|
|
{
|
|
EmitStringLiteral(Term->Name);
|
|
}
|
|
else if (FLiteralTypeHelper::IsText(&Term->Type, CoerceProperty))
|
|
{
|
|
Writer << EX_TextConst;
|
|
|
|
const FString& StringValue = FTextInspector::GetDisplayString(Term->TextLiteral);
|
|
|
|
// What kind of text are we dealing with?
|
|
if (Term->TextLiteral.IsEmpty())
|
|
{
|
|
Writer << EBlueprintTextLiteralType::Empty;
|
|
}
|
|
else if (Term->TextLiteral.IsFromStringTable())
|
|
{
|
|
FName TableId;
|
|
FString Key;
|
|
FTextInspector::GetTableIdAndKey(Term->TextLiteral, TableId, Key);
|
|
|
|
UStringTable* StringTableAsset = FStringTableRegistry::Get().FindStringTableAsset(TableId);
|
|
|
|
Writer << EBlueprintTextLiteralType::StringTableEntry;
|
|
Writer << StringTableAsset; // Not used at runtime, but exists for asset dependency gathering
|
|
EmitStringLiteral(TableId.ToString());
|
|
EmitStringLiteral(Key);
|
|
}
|
|
else if (Term->TextLiteral.IsCultureInvariant())
|
|
{
|
|
Writer << EBlueprintTextLiteralType::InvariantText;
|
|
EmitStringLiteral(StringValue);
|
|
}
|
|
else
|
|
{
|
|
FTextId TextId;
|
|
const FString* SourceString = FTextInspector::GetSourceString(Term->TextLiteral);
|
|
|
|
if (SourceString && Term->TextLiteral.ShouldGatherForLocalization())
|
|
{
|
|
TextId = FTextInspector::GetTextId(Term->TextLiteral);
|
|
}
|
|
|
|
if (!TextId.IsEmpty())
|
|
{
|
|
// BP bytecode always removes the package localization ID to match how text works at runtime
|
|
// If we're gathering editor-only text then we'll pick up the version with the package localization ID from the property/pin rather than the bytecode
|
|
const FString Namespace = TextNamespaceUtil::StripPackageNamespace(TextId.GetNamespace().ToString());
|
|
const FString Key = TextId.GetKey().ToString();
|
|
|
|
Writer << EBlueprintTextLiteralType::LocalizedText;
|
|
EmitStringLiteral(*SourceString);
|
|
EmitStringLiteral(Key);
|
|
EmitStringLiteral(Namespace);
|
|
}
|
|
else
|
|
{
|
|
Writer << EBlueprintTextLiteralType::LiteralString;
|
|
EmitStringLiteral(StringValue);
|
|
}
|
|
}
|
|
}
|
|
else if (FLiteralTypeHelper::IsFloat(&Term->Type, CoerceProperty))
|
|
{
|
|
float Value = FCString::Atof(*(Term->Name));
|
|
Writer << EX_FloatConst;
|
|
Writer << Value;
|
|
}
|
|
else if (FLiteralTypeHelper::IsDouble(&Term->Type, CoerceProperty))
|
|
{
|
|
double Value = 0.0;
|
|
if (Term->Type.bSerializeAsSinglePrecisionFloat)
|
|
{
|
|
Value = FCString::Atof(*(Term->Name));
|
|
}
|
|
else
|
|
{
|
|
Value = FCString::Atod(*(Term->Name));
|
|
}
|
|
|
|
Writer << EX_DoubleConst;
|
|
Writer << Value;
|
|
}
|
|
else if (FLiteralTypeHelper::IsInt(&Term->Type, CoerceProperty))
|
|
{
|
|
// In certain cases (like UKismetArrayLibrary functions), we have
|
|
// polymorphic functions that provide their own "custom thunk"
|
|
// (custom execution code). The actual function acts as a
|
|
// template, where the parameter types can be changed out for
|
|
// other types (much like c++ template functions, the "custom
|
|
// thunk" is generic). Traditionally, we use integer refs as the
|
|
// place holder type (that's why this is nested in a FIntProperty
|
|
// check)... Complications arise here, when we try to emit
|
|
// literal values fed into the function when they don't match
|
|
// the template's (int) type. For most types, this here is
|
|
// circumvented with AutoCreateRefTerm, but when it is a self
|
|
// (literal) node we still end up here. So, we try to detect and
|
|
// handle that case here.
|
|
if ((Term->Type.PinSubCategory == UEdGraphSchema_K2::PN_Self) && CoerceProperty && CoerceProperty->HasAnyPropertyFlags(CPF_ReferenceParm))
|
|
{
|
|
Writer << EX_Self;
|
|
}
|
|
else
|
|
{
|
|
//@TODO: There are smaller encodings EX_IntZero, EX_IntOne, EX_IntConstByte available which could be used instead when the value fits
|
|
int32 Value = FCString::Atoi(*(Term->Name));
|
|
Writer << EX_IntConst;
|
|
Writer << Value;
|
|
}
|
|
}
|
|
else if (FLiteralTypeHelper::IsInt64(&Term->Type, CoerceProperty))
|
|
{
|
|
int64 Value = 0;
|
|
LexFromString(Value, *(Term->Name));
|
|
Writer << EX_Int64Const;
|
|
Writer << Value;
|
|
}
|
|
else if (FLiteralTypeHelper::IsUInt64(&Term->Type, CoerceProperty))
|
|
{
|
|
uint64 Value = 0;
|
|
LexFromString(Value, *(Term->Name));
|
|
Writer << EX_UInt64Const;
|
|
Writer << Value;
|
|
}
|
|
else if (FLiteralTypeHelper::IsByte(&Term->Type, CoerceProperty))
|
|
{
|
|
uint8 Value = 0;
|
|
|
|
UEnum* EnumPtr = nullptr;
|
|
|
|
if (const FByteProperty* ByteProp = CastField< FByteProperty >(CoerceProperty))
|
|
{
|
|
EnumPtr = ByteProp->Enum;
|
|
}
|
|
else if (const FEnumProperty* EnumProp = CastField< FEnumProperty >(CoerceProperty))
|
|
{
|
|
EnumPtr = EnumProp->GetEnum();
|
|
}
|
|
|
|
//Parameter property can represent a generic byte. we need the actual type to parse the value.
|
|
if (!EnumPtr)
|
|
{
|
|
EnumPtr = Cast<UEnum>(Term->Type.PinSubCategoryObject.Get());
|
|
}
|
|
|
|
//Check for valid enum object reference
|
|
if (EnumPtr)
|
|
{
|
|
//Get value from enum string
|
|
Value = EnumPtr->GetValueByName(*(Term->Name));
|
|
}
|
|
else
|
|
{
|
|
Value = FCString::Atoi(*(Term->Name));
|
|
}
|
|
|
|
Writer << EX_ByteConst;
|
|
Writer << Value;
|
|
}
|
|
else if (FLiteralTypeHelper::IsBoolean(&Term->Type, CoerceProperty))
|
|
{
|
|
// Bitfields in struct literals were being treated as full bytes, but instructions like
|
|
// EX_LetBool provide the destination as the CoerceProperty, even though they are going
|
|
// to allocate a full byte for us to write to. To disambiguate I have added bCallerRequiresBit
|
|
// when a calling expressing allocates only a single bit for us to write to:
|
|
bool bValue = Term->Name.ToBool();
|
|
const bool bIsBit = bCallerRequiresBit && FLiteralTypeHelper::IsBit(CoerceProperty);
|
|
|
|
if(bIsBit)
|
|
{
|
|
check(CoerceProperty);
|
|
// FArchive const correctness workaround:
|
|
FProperty* BitProperty = const_cast<FProperty*>(CoerceProperty);
|
|
uint8 ValueAsByte = bValue;
|
|
|
|
// Emit the literal, with enough information to safely write to CoerceProperty:
|
|
Writer << EX_BitFieldConst;
|
|
Writer << BitProperty;
|
|
Writer << ValueAsByte;
|
|
}
|
|
else
|
|
{
|
|
Writer << (bValue ? EX_True : EX_False);
|
|
}
|
|
}
|
|
else if (FLiteralTypeHelper::IsName(&Term->Type, CoerceProperty))
|
|
{
|
|
FName LiteralName(*(Term->Name));
|
|
Writer << EX_NameConst;
|
|
Writer << LiteralName;
|
|
}
|
|
else if (FLiteralTypeHelper::IsStruct(&Term->Type, CoerceProperty))
|
|
{
|
|
const FStructProperty* StructProperty = CastField<FStructProperty>(CoerceProperty);
|
|
UScriptStruct* Struct = StructProperty ? ToRawPtr(StructProperty->Struct) : ToRawPtr(Cast<UScriptStruct>(Term->Type.PinSubCategoryObject.Get()));
|
|
check(Struct);
|
|
|
|
if (Struct == VectorStruct)
|
|
{
|
|
FVector V = FVector::ZeroVector;
|
|
if (!Term->Name.IsEmpty())
|
|
{
|
|
const bool bParsedUsingCustomFormat = FDefaultValueHelper::ParseVector(Term->Name, /*out*/ V);
|
|
if (!bParsedUsingCustomFormat)
|
|
{
|
|
Struct->ImportText(*Term->Name, &V, nullptr, ImportTextPortFlags, GWarn, GetPathNameSafe(StructProperty));
|
|
}
|
|
}
|
|
Writer << EX_VectorConst;
|
|
Writer << V;
|
|
}
|
|
else if (Struct == Vector3fStruct)
|
|
{
|
|
FVector3f V = FVector3f::ZeroVector;
|
|
if (!Term->Name.IsEmpty())
|
|
{
|
|
const bool bParsedUsingCustomFormat = FDefaultValueHelper::ParseVector(Term->Name, /*out*/ V);
|
|
if (!bParsedUsingCustomFormat)
|
|
{
|
|
Struct->ImportText(*Term->Name, &V, nullptr, ImportTextPortFlags, GWarn, GetPathNameSafe(StructProperty));
|
|
}
|
|
}
|
|
Writer << EX_Vector3fConst;
|
|
Writer << V;
|
|
}
|
|
else if (Struct == RotatorStruct)
|
|
{
|
|
FRotator R = FRotator::ZeroRotator;
|
|
if (!Term->Name.IsEmpty())
|
|
{
|
|
const bool bParsedUsingCustomFormat = FDefaultValueHelper::ParseRotator(Term->Name, /*out*/ R);
|
|
if (!bParsedUsingCustomFormat)
|
|
{
|
|
Struct->ImportText(*Term->Name, &R, nullptr, ImportTextPortFlags, GWarn, GetPathNameSafe(StructProperty));
|
|
}
|
|
}
|
|
Writer << EX_RotationConst;
|
|
Writer << R;
|
|
}
|
|
else if (Struct == TransformStruct)
|
|
{
|
|
FTransform T = FTransform::Identity;
|
|
if (!Term->Name.IsEmpty())
|
|
{
|
|
const bool bParsedUsingCustomFormat = T.InitFromString(Term->Name);
|
|
if (!bParsedUsingCustomFormat)
|
|
{
|
|
Struct->ImportText(*Term->Name, &T, nullptr, ImportTextPortFlags, GWarn, GetPathNameSafe(StructProperty));
|
|
}
|
|
}
|
|
Writer << EX_TransformConst;
|
|
Writer << T;
|
|
}
|
|
else
|
|
{
|
|
const int32 ArrayDim = StructProperty ? StructProperty->ArrayDim : 1; //@TODO: is it safe when StructProperty == nullptr?
|
|
int32 StructSize = Struct->GetStructureSize() * ArrayDim;
|
|
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
|
|
Struct->InitializeStruct(StructData, ArrayDim);
|
|
if (!ensure(bAllowStaticArray || 1 == ArrayDim))
|
|
{
|
|
UE_LOG(LogK2Compiler, Error, TEXT("Unsupported static array. Property: %s, Struct: %s"), *GetPathNameSafe(StructProperty), *Struct->GetName());
|
|
}
|
|
|
|
// Assume that any errors on the import of the name string have been caught in the function call generation
|
|
Struct->ImportText(Term->Name.IsEmpty() ? TEXT("()") : *Term->Name, StructData, nullptr, ImportTextPortFlags, GLog, GetPathNameSafe(StructProperty));
|
|
|
|
Writer << EX_StructConst;
|
|
Writer << Struct;
|
|
Writer << StructSize;
|
|
|
|
// TODO: Change this once structs/classes can be declared as explicitly editor only
|
|
bool bIsEditorOnlyStruct = false;
|
|
|
|
checkSlow(Schema);
|
|
for( FProperty* Prop = Struct->PropertyLink; Prop; Prop = Prop->PropertyLinkNext )
|
|
{
|
|
// Skip transient and editor only properties, this needs to be synched with ScriptCore
|
|
if (Prop->PropertyFlags & CPF_Transient || (!bIsEditorOnlyStruct && Prop->PropertyFlags & CPF_EditorOnly))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Create a new term for each property, and serialize it out
|
|
for (int32 ArrayIter = 0; ArrayIter < Prop->ArrayDim; ++ArrayIter)
|
|
{
|
|
FBPTerminal NewTerm;
|
|
if(!Schema->ConvertPropertyToPinType(Prop, NewTerm.Type))
|
|
{
|
|
// Do nothing for unsupported/unhandled property types. This will leave the value unchanged from its constructed default.
|
|
Writer << EX_Nothing;
|
|
continue;
|
|
}
|
|
|
|
NewTerm.bIsLiteral = true;
|
|
NewTerm.Source = Term->Source;
|
|
NewTerm.SourcePin = Term->SourcePin;
|
|
Prop->ExportText_InContainer(ArrayIter, NewTerm.Name, StructData, StructData, NULL, PPF_None);
|
|
if (Prop->IsA(FTextProperty::StaticClass()))
|
|
{
|
|
NewTerm.TextLiteral = CastField<FTextProperty>(Prop)->GetPropertyValue_InContainer(StructData, ArrayIter);
|
|
NewTerm.Name = NewTerm.TextLiteral.ToString();
|
|
}
|
|
else if (Prop->IsA(FObjectProperty::StaticClass()))
|
|
{
|
|
NewTerm.ObjectLiteral = CastField<FObjectProperty>(Prop)->GetObjectPropertyValue(Prop->ContainerPtrToValuePtr<void>(StructData));
|
|
}
|
|
|
|
EmitTermExpr(&NewTerm, Prop, true, true);
|
|
}
|
|
}
|
|
Struct->DestroyStruct(StructData, ArrayDim);
|
|
Writer << EX_EndStructConst;
|
|
}
|
|
}
|
|
else if (const FArrayProperty* ArrayPropr = CastField<FArrayProperty>(CoerceProperty))
|
|
{
|
|
FProperty* InnerProp = ArrayPropr->Inner;
|
|
ensure(InnerProp);
|
|
FScriptArray ScriptArray;
|
|
ArrayPropr->ImportText_Direct(*Term->Name, &ScriptArray, NULL, ImportTextPortFlags, GLog);
|
|
|
|
FScriptArrayHelper ScriptArrayHelper(ArrayPropr, &ScriptArray);
|
|
int32 ElementNum = ScriptArrayHelper.Num();
|
|
|
|
Writer << EX_ArrayConst;
|
|
Writer << InnerProp;
|
|
Writer << ElementNum;
|
|
for (int32 ElemIdx = 0; ElemIdx < ElementNum; ++ElemIdx)
|
|
{
|
|
uint8* RawElemData = ScriptArrayHelper.GetRawPtr(ElemIdx);
|
|
EmitInnerElementExpr(Term, InnerProp, RawElemData);
|
|
}
|
|
Writer << EX_EndArrayConst;
|
|
}
|
|
else if (const FSetProperty* SetPropr = CastField<FSetProperty>(CoerceProperty))
|
|
{
|
|
FProperty* InnerProp = SetPropr->ElementProp;
|
|
ensure(InnerProp);
|
|
|
|
FScriptSet ScriptSet;
|
|
SetPropr->ImportText_Direct(*Term->Name, &ScriptSet, NULL, ImportTextPortFlags, GLog);
|
|
int32 ElementNum = ScriptSet.Num();
|
|
|
|
FScriptSetHelper ScriptSetHelper(SetPropr, &ScriptSet);
|
|
|
|
Writer << EX_SetConst;
|
|
Writer << InnerProp;
|
|
Writer << ElementNum;
|
|
|
|
for (FScriptSetHelper::FIterator It(ScriptSetHelper); It; ++It)
|
|
{
|
|
uint8* RawElemData = ScriptSetHelper.GetElementPtr(It);
|
|
EmitInnerElementExpr(Term, InnerProp, RawElemData);
|
|
}
|
|
Writer << EX_EndSetConst;
|
|
}
|
|
else if (const FMapProperty* MapPropr = CastField<FMapProperty>(CoerceProperty))
|
|
{
|
|
FProperty* KeyProp = MapPropr->KeyProp;
|
|
FProperty* ValProp = MapPropr->ValueProp;
|
|
ensure(KeyProp && ValProp);
|
|
|
|
FScriptMap ScriptMap;
|
|
MapPropr->ImportText_Direct(*Term->Name, &ScriptMap, NULL, ImportTextPortFlags, GLog);
|
|
int32 ElementNum = ScriptMap.Num();
|
|
|
|
FScriptMapHelper ScriptMapHelper(MapPropr, &ScriptMap);
|
|
|
|
Writer << EX_MapConst;
|
|
Writer << KeyProp;
|
|
Writer << ValProp;
|
|
Writer << ElementNum;
|
|
|
|
for (FScriptMapHelper::FIterator It(ScriptMapHelper); It; ++It)
|
|
{
|
|
EmitInnerElementExpr(Term, KeyProp, ScriptMapHelper.GetKeyPtr(It));
|
|
EmitInnerElementExpr(Term, ValProp, ScriptMapHelper.GetValuePtr(It));
|
|
}
|
|
Writer << EX_EndMapConst;
|
|
}
|
|
else if (FLiteralTypeHelper::IsDelegate(&Term->Type, CoerceProperty))
|
|
{
|
|
FName FunctionName;
|
|
|
|
// Deliberately null delegates are allowed, using empty string or the ExportText format
|
|
if (Term->Name != TEXT("") && Term->Name != TEXT("(null).None"))
|
|
{
|
|
FunctionName = *Term->Name; //@TODO: K2 Delegate Support: Need to verify this function actually exists and has the right signature?
|
|
}
|
|
|
|
Writer << EX_InstanceDelegate;
|
|
Writer << FunctionName;
|
|
}
|
|
else if (FLiteralTypeHelper::IsSoftObject(&Term->Type, CoerceProperty))
|
|
{
|
|
Writer << EX_SoftObjectConst;
|
|
EmitStringLiteral(Term->Name);
|
|
}
|
|
else if (FLiteralTypeHelper::IsFieldPath(&Term->Type, CoerceProperty))
|
|
{
|
|
Writer << EX_FieldPathConst;
|
|
EmitStringLiteral(Term->Name);
|
|
}
|
|
else if (FLiteralTypeHelper::IsObject(&Term->Type, CoerceProperty) || FLiteralTypeHelper::IsClass(&Term->Type, CoerceProperty))
|
|
{
|
|
// Note: This case handles both FObjectProperty and FClassProperty
|
|
if (Term->Type.PinSubCategory == UEdGraphSchema_K2::PN_Self)
|
|
{
|
|
Writer << EX_Self;
|
|
}
|
|
else if (!Term->ObjectLiteral)
|
|
{
|
|
Writer << EX_NoObject;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_ObjectConst;
|
|
Writer << Term->ObjectLiteral;
|
|
}
|
|
}
|
|
else if (FLiteralTypeHelper::IsInterface(&Term->Type, CoerceProperty))
|
|
{
|
|
if (Term->Type.PinSubCategory == UEdGraphSchema_K2::PN_Self)
|
|
{
|
|
Writer << EX_ObjToInterfaceCast;
|
|
UClass* InterfaceClass = CastChecked<UClass>(Term->Type.PinSubCategoryObject.Get());
|
|
Writer << InterfaceClass;
|
|
Writer << EX_Self;
|
|
}
|
|
else if (Term->ObjectLiteral == nullptr)
|
|
{
|
|
Writer << EX_NoInterface;
|
|
}
|
|
else
|
|
{
|
|
// emit cast
|
|
Writer << EX_ObjToInterfaceCast;
|
|
UClass* InterfaceClass = CastChecked<UClass>(Term->Type.PinSubCategoryObject.Get());
|
|
Writer << InterfaceClass;
|
|
// from the object:
|
|
Writer << EX_ObjectConst;
|
|
Writer << Term->ObjectLiteral;
|
|
}
|
|
}
|
|
else if (!CoerceProperty && Term->Type.PinCategory.IsNone() && (Term->Type.PinSubCategory == UEdGraphSchema_K2::PN_Self))
|
|
{
|
|
Writer << EX_Self;
|
|
}
|
|
// else if (CoerceProperty->IsA(FMulticastDelegateProperty::StaticClass()))
|
|
// Cannot assign a literal to a multicast delegate; it should be added instead of assigned
|
|
else
|
|
{
|
|
if (ensure(CurrentCompilerContext))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("PropertyType"), CoerceProperty ? CoerceProperty->GetClass()->GetDisplayNameText() : FText());
|
|
Args.Add(TEXT("PropertyName"), CoerceProperty ? CoerceProperty->GetDisplayNameText() : FText());
|
|
CurrentCompilerContext->MessageLog.Error(*FText::Format(LOCTEXT("InvalidProperty", "It is not possible to express this type ({PropertyType}) as a literal value for the property {PropertyName} on pin @@! If it is inside a struct, you can add a Make struct node to resolve this issue!"), Args).ToString(), Term->SourcePin);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
check(Term->AssociatedVarProperty);
|
|
if (Term->IsDefaultVarTerm())
|
|
{
|
|
Writer << EX_DefaultVariable;
|
|
}
|
|
else if (Term->IsLocalVarTerm())
|
|
{
|
|
Writer << (Term->AssociatedVarProperty->HasAnyPropertyFlags(CPF_OutParm) ? EX_LocalOutVariable : EX_LocalVariable);
|
|
}
|
|
else if (Term->IsSparseClassDataVarTerm())
|
|
{
|
|
Writer << EX_ClassSparseDataVariable;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_InstanceVariable;
|
|
}
|
|
Writer << Term->AssociatedVarProperty;
|
|
}
|
|
}
|
|
|
|
void EmitInnerElementExpr(FBPTerminal* OuterTerm, FProperty* InnerProp, uint8* RawElemPtr)
|
|
{
|
|
FBPTerminal NewTerm;
|
|
Schema->ConvertPropertyToPinType(InnerProp, NewTerm.Type);
|
|
NewTerm.bIsLiteral = true;
|
|
NewTerm.Source = OuterTerm->Source;
|
|
NewTerm.SourcePin = OuterTerm->SourcePin;
|
|
|
|
InnerProp->ExportText_Direct(NewTerm.Name, RawElemPtr, RawElemPtr, NULL, PPF_None);
|
|
if (InnerProp->IsA(FTextProperty::StaticClass()))
|
|
{
|
|
NewTerm.TextLiteral = CastField<FTextProperty>(InnerProp)->GetPropertyValue(RawElemPtr);
|
|
NewTerm.Name = NewTerm.TextLiteral.ToString();
|
|
}
|
|
else if (InnerProp->IsA(FObjectPropertyBase::StaticClass()))
|
|
{
|
|
NewTerm.ObjectLiteral = CastField<FObjectPropertyBase>(InnerProp)->GetObjectPropertyValue(RawElemPtr);
|
|
}
|
|
|
|
EmitTermExpr(&NewTerm, InnerProp);
|
|
}
|
|
|
|
void EmitLatentInfoTerm(FBPTerminal* Term, FProperty* LatentInfoProperty, FBlueprintCompiledStatement* TargetLabel)
|
|
{
|
|
// Special case of the struct property emitter. Needs to emit a linkage property for fixup
|
|
FStructProperty* StructProperty = CastFieldChecked<FStructProperty>(LatentInfoProperty);
|
|
check(StructProperty->Struct == LatentInfoStruct);
|
|
|
|
int32 StructSize = LatentInfoStruct->GetStructureSize();
|
|
uint8* StructData = (uint8*)FMemory_Alloca(StructSize);
|
|
StructProperty->InitializeValue(StructData);
|
|
|
|
// Assume that any errors on the import of the name string have been caught in the function call generation
|
|
StructProperty->ImportText_Direct(*Term->Name, StructData, NULL, 0, GLog);
|
|
|
|
Writer << EX_StructConst;
|
|
Writer << LatentInfoStruct;
|
|
Writer << StructSize;
|
|
|
|
checkSlow(Schema);
|
|
for (FProperty* Prop = LatentInfoStruct->PropertyLink; Prop; Prop = Prop->PropertyLinkNext)
|
|
{
|
|
if (TargetLabel && Prop->GetBoolMetaData(FBlueprintMetadata::MD_NeedsLatentFixup))
|
|
{
|
|
// Emit the literal and queue a fixup to correct it once the address is known
|
|
Writer << EX_SkipOffsetConst;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, TargetLabel));
|
|
}
|
|
else if (Prop->GetBoolMetaData(FBlueprintMetadata::MD_LatentCallbackTarget))
|
|
{
|
|
FBPTerminal CallbackTargetTerm;
|
|
CallbackTargetTerm.bIsLiteral = true;
|
|
CallbackTargetTerm.Type.PinSubCategory = UEdGraphSchema_K2::PN_Self;
|
|
EmitTermExpr(&CallbackTargetTerm, Prop);
|
|
}
|
|
else
|
|
{
|
|
// Create a new term for each property, and serialize it out
|
|
FBPTerminal NewTerm;
|
|
if(Schema->ConvertPropertyToPinType(Prop, NewTerm.Type))
|
|
{
|
|
NewTerm.bIsLiteral = true;
|
|
Prop->ExportText_InContainer(0, NewTerm.Name, StructData, StructData, NULL, PPF_None);
|
|
|
|
EmitTermExpr(&NewTerm, Prop);
|
|
}
|
|
else
|
|
{
|
|
// Do nothing for unsupported/unhandled property types. This will leave the value unchanged from its constructed default.
|
|
Writer << EX_Nothing;
|
|
}
|
|
}
|
|
}
|
|
|
|
Writer << EX_EndStructConst;
|
|
}
|
|
|
|
void EmitFunctionCall(FKismetCompilerContext& CompilerContext, FKismetFunctionContext& FunctionContext, FBlueprintCompiledStatement& Statement, UEdGraphNode* SourceNode)
|
|
{
|
|
UFunction* FunctionToCall = Statement.FunctionToCall;
|
|
check(FunctionToCall);
|
|
|
|
ClassBeingBuilt->CalledFunctions.Emplace(FunctionToCall);
|
|
|
|
if (FunctionToCall->HasAllFunctionFlags(FUNC_Native))
|
|
{
|
|
// Array output parameters are cleared, in case the native function doesn't clear them before filling.
|
|
int32 NumParams = 0;
|
|
for (TFieldIterator<FProperty> PropIt(FunctionToCall); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
|
|
{
|
|
FProperty* Param = *PropIt;
|
|
check(Param);
|
|
const bool bShouldParameterBeCleared = Param->IsA<FArrayProperty>()
|
|
&& Param->HasAllPropertyFlags(CPF_Parm | CPF_OutParm)
|
|
&& !Param->HasAnyPropertyFlags(CPF_ReferenceParm | CPF_ConstParm | CPF_ReturnParm);
|
|
if (bShouldParameterBeCleared)
|
|
{
|
|
// SetArray instruction will be called with empty parameter list.
|
|
Writer << EX_SetArray;
|
|
FBPTerminal* ArrayTerm = Statement.RHS[NumParams];
|
|
ensure(ArrayTerm && !ArrayTerm->bIsLiteral);
|
|
EmitTerm(ArrayTerm, Param);
|
|
Writer << EX_EndArray;
|
|
}
|
|
NumParams += Param->HasAnyPropertyFlags(CPF_ReturnParm) ? 0 : 1;
|
|
}
|
|
}
|
|
|
|
// The target label will only ever be set on a call function when calling into the Ubergraph, which requires a patchup
|
|
// or when re-entering from a latent function which requires a different kind of patchup
|
|
if ((Statement.TargetLabel != NULL) && !bIsUbergraph)
|
|
{
|
|
CodeSkipSizeType OffsetWithinUbergraph = UbergraphStatementLabelMap.FindChecked(Statement.TargetLabel);
|
|
|
|
// Overwrite RHS(0) text with the state index to kick off
|
|
check(Statement.RHS[Statement.UbergraphCallIndex]->bIsLiteral);
|
|
Statement.RHS[Statement.UbergraphCallIndex]->Name = FString::FromInt(OffsetWithinUbergraph);
|
|
|
|
#if UE_BLUEPRINT_EVENTGRAPH_FASTCALLS
|
|
// Store optimization data if this is a simple call into the ubergraph
|
|
if (FunctionContext.bIsSimpleStubGraphWithNoParams && CompilerContext.NewClass->UberGraphFunction)
|
|
{
|
|
check(FunctionToCall == CompilerContext.NewClass->UberGraphFunction);
|
|
check(FunctionToCall->ParmsSize == sizeof(int32));
|
|
|
|
if ((FunctionToCall->FirstPropertyToInit == nullptr) && (FunctionToCall->PostConstructLink == nullptr))
|
|
{
|
|
FunctionContext.Function->EventGraphFunction = FunctionToCall;
|
|
FunctionContext.Function->EventGraphCallOffset = OffsetWithinUbergraph;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// Handle the return value assignment if present
|
|
bool bHasOutputValue = false;
|
|
for (TFieldIterator<FProperty> PropIt(FunctionToCall); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
|
|
{
|
|
FProperty* FuncParamProperty = *PropIt;
|
|
if (FuncParamProperty->HasAnyPropertyFlags(CPF_ReturnParm))
|
|
{
|
|
if (Statement.LHS)
|
|
{
|
|
EmitDestinationExpression(Statement.LHS);
|
|
}
|
|
bHasOutputValue = true;
|
|
}
|
|
else if (FuncParamProperty->HasAnyPropertyFlags(CPF_OutParm) && !FuncParamProperty->HasAnyPropertyFlags(CPF_ConstParm))
|
|
{
|
|
// Non const values passed by ref are also an output
|
|
bHasOutputValue = true;
|
|
}
|
|
}
|
|
|
|
const bool bFinalFunction = FunctionToCall->HasAnyFunctionFlags(FUNC_Final) || Statement.bIsParentContext;
|
|
using namespace UE::ProjectUtilities;
|
|
const bool bMathCall = bFinalFunction
|
|
&& FunctionToCall->HasAllFunctionFlags(FUNC_Static|FUNC_Final|FUNC_Native)
|
|
&& !FunctionToCall->HasAnyFunctionFlags(FUNC_NetFuncFlags|FUNC_BlueprintAuthorityOnly|FUNC_BlueprintCosmetic|FUNC_NetRequest|FUNC_NetResponse)
|
|
&& !FunctionToCall->GetOuterUClass()->IsChildOf(UInterface::StaticClass())
|
|
// If the function has wildcard parameters (e.g. UKismetArrayLibrary) we assume that it
|
|
// is doing things with reflection data at runtime and will therefore benefit from
|
|
// the safety of a 'context' based function call (e.g. if context is invalid or
|
|
// 'bArrayContextFailed' we can skip the function call):
|
|
&& !UEdGraphSchema_K2::HasWildcardParams(FunctionToCall)
|
|
// Don't emit math calls if the target function may disapear on us at loadtime/runtime:
|
|
&& FBuildTargetSet::GetCallerTargetsUnsupportedByCallee(ClassBeingBuilt, FunctionToCall) == FBuildTargetSet();
|
|
|
|
const bool bLocalScriptFunction =
|
|
!FunctionToCall->HasAnyFunctionFlags(FUNC_Native|FUNC_NetFuncFlags|FUNC_BlueprintAuthorityOnly|FUNC_BlueprintCosmetic|FUNC_NetRequest|FUNC_NetResponse);
|
|
|
|
// Handle the function calling context if needed
|
|
FContextEmitter CallContextWriter(*this);
|
|
|
|
if (!bMathCall) // math call doesn't need context
|
|
{
|
|
// RValue property is used to clear value after Access Violation. See UObject::ProcessContextOpcod
|
|
// If the property from LHS is used, then the retured property (with CPF_ReturnParm) is cleared. But properties returned by ref are not cleared.
|
|
FProperty* RValueProperty = Statement.LHS ? Statement.LHS->AssociatedVarProperty : nullptr;
|
|
CallContextWriter.TryStartContext(Statement.FunctionContext, /*bUnsafeToSkip=*/ bHasOutputValue, Statement.bIsInterfaceContext, RValueProperty);
|
|
}
|
|
|
|
// Emit the call type
|
|
if (FunctionToCall->HasAnyFunctionFlags(FUNC_Delegate))
|
|
{
|
|
// @todo: Default delegate functions are no longer callable (and also now have mangled names.) FindField will fail.
|
|
check(false);
|
|
}
|
|
else if (bFinalFunction)
|
|
{
|
|
if (bMathCall)
|
|
{
|
|
Writer << EX_CallMath;
|
|
}
|
|
else if(bLocalScriptFunction)
|
|
{
|
|
Writer << EX_LocalFinalFunction;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_FinalFunction;
|
|
}
|
|
// The function to call doesn't have a native index
|
|
Writer << FunctionToCall;
|
|
}
|
|
else
|
|
{
|
|
FName FunctionName(FunctionToCall->GetFName());
|
|
if(bLocalScriptFunction)
|
|
{
|
|
Writer << EX_LocalVirtualFunction;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_VirtualFunction;
|
|
}
|
|
Writer << FunctionName;
|
|
}
|
|
|
|
const bool bIsCustomThunk = FunctionToCall->HasMetaData(FBlueprintMetadata::MD_CustomThunk);
|
|
// Emit function parameters
|
|
int32 NumParams = 0;
|
|
for (TFieldIterator<FProperty> PropIt(FunctionToCall); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
|
|
{
|
|
FProperty* FuncParamProperty = *PropIt;
|
|
|
|
if (!FuncParamProperty->HasAnyPropertyFlags(CPF_ReturnParm))
|
|
{
|
|
FBPTerminal* Term = Statement.RHS[NumParams];
|
|
check(Term != NULL);
|
|
|
|
// Latent function handling: Need to emit a fixup request into the FLatentInfo struct
|
|
if (bIsUbergraph && FuncParamProperty->GetName() == FunctionToCall->GetMetaData(FBlueprintMetadata::MD_LatentInfo))
|
|
{
|
|
EmitLatentInfoTerm(Term, FuncParamProperty, Statement.TargetLabel);
|
|
}
|
|
else
|
|
{
|
|
// Native type of a wildcard parameter should be ignored.
|
|
const bool bBadCoerceProperty = bIsCustomThunk && !Term->Type.IsContainer() && UEdGraphSchema_K2::IsWildcardProperty(FuncParamProperty);
|
|
// When no coerce property is passed, a type of literal will be retrieved from the term.
|
|
EmitTerm(Term, bBadCoerceProperty ? nullptr : FuncParamProperty);
|
|
}
|
|
NumParams++;
|
|
}
|
|
}
|
|
|
|
const bool bIsVariadic = FunctionToCall->HasMetaData(FBlueprintMetadata::MD_Variadic);
|
|
if (bIsVariadic)
|
|
{
|
|
// Variadic functions may have extra terms they need to emit after the main set of function arguments
|
|
// These are all considered wildcards so no type checking will be performed on them
|
|
for (; NumParams < Statement.RHS.Num(); ++NumParams)
|
|
{
|
|
FBPTerminal* Term = Statement.RHS[NumParams];
|
|
check(Term);
|
|
EmitTerm(Term, nullptr);
|
|
}
|
|
}
|
|
|
|
// End of parameter list
|
|
Writer << EX_EndFunctionParms;
|
|
}
|
|
|
|
void EmitCallDelegate(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
UFunction* FunctionToCall = Statement.FunctionToCall;
|
|
check(NULL != FunctionToCall);
|
|
check(NULL != Statement.FunctionContext);
|
|
check(FunctionToCall->HasAnyFunctionFlags(FUNC_Delegate));
|
|
|
|
ClassBeingBuilt->CalledFunctions.Emplace(FunctionToCall);
|
|
|
|
// The function to call doesn't have a native index
|
|
Writer << EX_CallMulticastDelegate;
|
|
Writer << FunctionToCall;
|
|
EmitTerm(Statement.FunctionContext);
|
|
|
|
// Emit function parameters
|
|
int32 NumParams = 0;
|
|
for (TFieldIterator<FProperty> PropIt(FunctionToCall); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
|
|
{
|
|
FProperty* FuncParamProperty = *PropIt;
|
|
|
|
FBPTerminal* Term = Statement.RHS[NumParams];
|
|
check(Term != NULL);
|
|
|
|
// Emit parameter term normally
|
|
EmitTerm(Term, FuncParamProperty);
|
|
|
|
NumParams++;
|
|
}
|
|
|
|
// End of parameter list
|
|
Writer << EX_EndFunctionParms;
|
|
}
|
|
|
|
void EmitTerm(FBPTerminal* Term, const FProperty* CoerceProperty = NULL, FBPTerminal* RValueTerm = NULL)
|
|
{
|
|
if (Term->InlineGeneratedParameter)
|
|
{
|
|
ensure(!Term->InlineGeneratedParameter->bIsJumpTarget);
|
|
auto TermSourceAsNode = Cast<UEdGraphNode>(Term->Source);
|
|
auto TermSourceAsPin = Term->SourcePin;
|
|
UEdGraphNode* SourceNode = TermSourceAsNode ? TermSourceAsNode
|
|
: (TermSourceAsPin ? TermSourceAsPin->GetOwningNodeUnchecked() : nullptr);
|
|
if (ensure(CurrentCompilerContext && CurrentFunctionContext))
|
|
{
|
|
GenerateCodeForStatement(*CurrentCompilerContext, *CurrentFunctionContext, *Term->InlineGeneratedParameter, SourceNode);
|
|
}
|
|
}
|
|
else if (Term->Context == NULL)
|
|
{
|
|
EmitTermExpr(Term, CoerceProperty);
|
|
}
|
|
else
|
|
{
|
|
if (Term->Context->IsStructContextType())
|
|
{
|
|
check(Term->AssociatedVarProperty);
|
|
|
|
Writer << EX_StructMemberContext;
|
|
Writer << Term->AssociatedVarProperty;
|
|
|
|
// Now run the context expression
|
|
EmitTerm(Term->Context, NULL);
|
|
}
|
|
else
|
|
{
|
|
// If this is the top of the chain this context, then save it off the r-value and pass it down the chain so we can safely handle runtime null contexts
|
|
if( RValueTerm == NULL )
|
|
{
|
|
RValueTerm = Term;
|
|
}
|
|
|
|
FContextEmitter CallContextWriter(*this);
|
|
FProperty* RValueProperty = RValueTerm->AssociatedVarProperty;
|
|
CallContextWriter.TryStartContext(Term->Context, /*@TODO: bUnsafeToSkip*/ true, /*bIsInterfaceContext*/ false, RValueProperty);
|
|
|
|
EmitTermExpr(Term, CoerceProperty);
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmitDestinationExpression(FBPTerminal* DestinationExpression)
|
|
{
|
|
check(Schema && DestinationExpression && !DestinationExpression->Type.PinCategory.IsNone());
|
|
|
|
const bool bIsContainer = DestinationExpression->Type.IsContainer();
|
|
const bool bIsDelegate = UEdGraphSchema_K2::PC_Delegate == DestinationExpression->Type.PinCategory;
|
|
const bool bIsMulticastDelegate = UEdGraphSchema_K2::PC_MCDelegate == DestinationExpression->Type.PinCategory;
|
|
const bool bIsBoolean = UEdGraphSchema_K2::PC_Boolean == DestinationExpression->Type.PinCategory;
|
|
const bool bIsObj = (UEdGraphSchema_K2::PC_Object == DestinationExpression->Type.PinCategory) || (UEdGraphSchema_K2::PC_Class == DestinationExpression->Type.PinCategory);
|
|
const bool bIsSoftObject = UEdGraphSchema_K2::PC_SoftObject == DestinationExpression->Type.PinCategory;
|
|
const bool bIsWeakObjPtr = DestinationExpression->Type.bIsWeakPointer;
|
|
|
|
if (bIsContainer)
|
|
{
|
|
Writer << EX_Let;
|
|
ensure(DestinationExpression->AssociatedVarProperty);
|
|
Writer << DestinationExpression->AssociatedVarProperty;
|
|
}
|
|
else if (bIsMulticastDelegate)
|
|
{
|
|
Writer << EX_LetMulticastDelegate;
|
|
}
|
|
else if (bIsDelegate)
|
|
{
|
|
Writer << EX_LetDelegate;
|
|
}
|
|
else if (bIsBoolean)
|
|
{
|
|
Writer << EX_LetBool;
|
|
}
|
|
else if (bIsObj && !bIsSoftObject)
|
|
{
|
|
if( !bIsWeakObjPtr )
|
|
{
|
|
Writer << EX_LetObj;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_LetWeakObjPtr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_Let;
|
|
Writer << DestinationExpression->AssociatedVarProperty;
|
|
}
|
|
EmitTerm(DestinationExpression);
|
|
}
|
|
|
|
void EmitAssignmentStatment(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* SourceExpression = Statement.RHS[0];
|
|
|
|
EmitDestinationExpression(DestinationExpression);
|
|
|
|
EmitTerm(SourceExpression, DestinationExpression->AssociatedVarProperty);
|
|
}
|
|
|
|
void EmitAssignmentOnPersistentFrameStatment(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* SourceExpression = Statement.RHS[0];
|
|
|
|
Writer << EX_LetValueOnPersistentFrame;
|
|
check(ClassBeingBuilt && ClassBeingBuilt->UberGraphFunction);
|
|
Writer << DestinationExpression->AssociatedVarProperty;
|
|
|
|
EmitTerm(SourceExpression, DestinationExpression->AssociatedVarProperty);
|
|
}
|
|
|
|
void EmitCastObjToInterfaceStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* InterfaceExpression = Statement.RHS[0];
|
|
FBPTerminal* TargetExpression = Statement.RHS[1];
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_ObjToInterfaceCast;
|
|
UClass* ClassPtr = CastChecked<UClass>(InterfaceExpression->ObjectLiteral);
|
|
check(ClassPtr);
|
|
Writer << ClassPtr;
|
|
EmitTerm(TargetExpression, (FProperty*)(GetDefault<FObjectProperty>()));
|
|
}
|
|
|
|
void EmitCastBetweenInterfacesStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* InterfaceExpression = Statement.RHS[0];
|
|
FBPTerminal* TargetExpression = Statement.RHS[1];
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_CrossInterfaceCast;
|
|
UClass* ClassPtr = CastChecked<UClass>(InterfaceExpression->ObjectLiteral);
|
|
check(ClassPtr);
|
|
Writer << ClassPtr;
|
|
EmitTerm(TargetExpression, (FProperty*)(GetDefault<FInterfaceProperty>()));
|
|
}
|
|
|
|
void EmitCastInterfaceToObjStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* ResultObjClassExpression = Statement.RHS[0];
|
|
FBPTerminal* TargetInterfaceExpression = Statement.RHS[1];
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_InterfaceToObjCast;
|
|
UClass* ClassPtr = CastChecked<UClass>(ResultObjClassExpression->ObjectLiteral);
|
|
check(ClassPtr != nullptr);
|
|
Writer << ClassPtr;
|
|
EmitTerm(TargetInterfaceExpression, (FProperty*)(GetDefault<FObjectProperty>()));
|
|
}
|
|
|
|
void EmitDynamicCastStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* ResultClassExpression = Statement.RHS[0];
|
|
FBPTerminal* TargetExpression = Statement.RHS[1];
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_DynamicCast;
|
|
UClass* ClassPtr = CastChecked<UClass>(ResultClassExpression->ObjectLiteral);
|
|
Writer << ClassPtr;
|
|
EmitTerm(TargetExpression, (FProperty*)(GetDefault<FObjectProperty>()));
|
|
}
|
|
|
|
void EmitMetaCastStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* InterfaceExpression = Statement.RHS[0];
|
|
FBPTerminal* TargetExpression = Statement.RHS[1];
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_MetaCast;
|
|
UClass* ClassPtr = CastChecked<UClass>(InterfaceExpression->ObjectLiteral);
|
|
Writer << ClassPtr;
|
|
EmitTerm(TargetExpression, (FProperty*)(GetDefault<FClassProperty>()));
|
|
}
|
|
|
|
void EmitObjectToBoolStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* TargetExpression = Statement.RHS[0];
|
|
|
|
UClass* PSCObjClass = Cast<UClass>(TargetExpression->Type.PinSubCategoryObject.Get());
|
|
const bool bIsInterfaceCast = (PSCObjClass && PSCObjClass->HasAnyClassFlags(CLASS_Interface));
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_Cast;
|
|
ECastToken CastToken = !bIsInterfaceCast ? CST_ObjectToBool : CST_InterfaceToBool;
|
|
uint8 CastType = static_cast<uint8>(CastToken);
|
|
Writer << CastType;
|
|
|
|
FProperty* TargetProperty = !bIsInterfaceCast ? ((FProperty*)(GetDefault<FObjectProperty>())) : ((FProperty*)(GetDefault<FInterfaceProperty>()));
|
|
EmitTerm(TargetExpression, TargetProperty);
|
|
}
|
|
|
|
void EmitAddMulticastDelegateStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* Delegate = Statement.LHS;
|
|
FBPTerminal* DelegateToAdd = Statement.RHS[0];
|
|
|
|
Writer << EX_AddMulticastDelegate;
|
|
EmitTerm(Delegate);
|
|
EmitTerm(DelegateToAdd);
|
|
}
|
|
|
|
void EmitRemoveMulticastDelegateStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* Delegate = Statement.LHS;
|
|
FBPTerminal* DelegateToAdd = Statement.RHS[0];
|
|
|
|
Writer << EX_RemoveMulticastDelegate;
|
|
EmitTerm(Delegate);
|
|
EmitTerm(DelegateToAdd);
|
|
}
|
|
|
|
void EmitBindDelegateStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
check(2 == Statement.RHS.Num());
|
|
FBPTerminal* Delegate = Statement.LHS;
|
|
FBPTerminal* NameTerm = Statement.RHS[0];
|
|
FBPTerminal* ObjectTerm = Statement.RHS[1];
|
|
check(Delegate && ObjectTerm);
|
|
check(NameTerm && NameTerm->bIsLiteral);
|
|
check(!NameTerm->Name.IsEmpty());
|
|
|
|
FName FunctionName(*(NameTerm->Name));
|
|
Writer << EX_BindDelegate;
|
|
Writer << FunctionName;
|
|
|
|
EmitTerm(Delegate);
|
|
EmitTerm(ObjectTerm, (FProperty*)(GetDefault<FObjectProperty>()));
|
|
}
|
|
|
|
void EmitClearMulticastDelegateStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* Delegate = Statement.LHS;
|
|
|
|
Writer << EX_ClearMulticastDelegate;
|
|
EmitTerm(Delegate);
|
|
}
|
|
|
|
void EmitCreateArrayStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
Writer << EX_SetArray;
|
|
|
|
FBPTerminal* ArrayTerm = Statement.LHS;
|
|
EmitTerm(ArrayTerm);
|
|
|
|
FArrayProperty* ArrayProperty = CastFieldChecked<FArrayProperty>(ArrayTerm->AssociatedVarProperty);
|
|
FProperty* InnerProperty = ArrayProperty->Inner;
|
|
|
|
for(auto ArrayItemIt = Statement.RHS.CreateIterator(); ArrayItemIt; ++ArrayItemIt)
|
|
{
|
|
FBPTerminal* Item = *ArrayItemIt;
|
|
EmitTerm(Item, (Item->bIsLiteral ? InnerProperty : NULL));
|
|
}
|
|
|
|
Writer << EX_EndArray;
|
|
}
|
|
|
|
void EmitCreateSetStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
Writer << EX_SetSet;
|
|
|
|
FBPTerminal* SetTerm = Statement.LHS;
|
|
EmitTerm(SetTerm);
|
|
int32 ElementNum = Statement.RHS.Num();
|
|
|
|
Writer << ElementNum; // number of elements in the set, used for reserve call
|
|
|
|
FSetProperty* SetProperty = CastFieldChecked<FSetProperty>(SetTerm->AssociatedVarProperty);
|
|
FProperty* InnerProperty = SetProperty->ElementProp;
|
|
|
|
for(FBPTerminal* Item : Statement.RHS)
|
|
{
|
|
EmitTerm(Item, (Item->bIsLiteral ? InnerProperty : NULL));
|
|
}
|
|
|
|
Writer << EX_EndSet;
|
|
}
|
|
|
|
void EmitCreateMapStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
Writer << EX_SetMap;
|
|
|
|
FBPTerminal* MapTerm = Statement.LHS;
|
|
EmitTerm(MapTerm);
|
|
|
|
ensureMsgf(Statement.RHS.Num() % 2 == 0, TEXT("Expected even number of key/values whe emitting map statement"));
|
|
int32 ElementNum = Statement.RHS.Num() / 2;
|
|
|
|
Writer << ElementNum;
|
|
|
|
FMapProperty* MapProperty = CastFieldChecked<FMapProperty>(MapTerm->AssociatedVarProperty);
|
|
|
|
for(auto MapItemIt = Statement.RHS.CreateIterator(); MapItemIt; ++MapItemIt)
|
|
{
|
|
FBPTerminal* Item = *MapItemIt;
|
|
EmitTerm(Item, (Item->bIsLiteral ? MapProperty->KeyProp : NULL));
|
|
++MapItemIt;
|
|
Item = *MapItemIt;
|
|
EmitTerm(Item, (Item->bIsLiteral ? MapProperty->ValueProp : NULL));
|
|
}
|
|
|
|
Writer << EX_EndMap;
|
|
}
|
|
|
|
void EmitGoto(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
if (Statement.Type == KCST_ComputedGoto)
|
|
{
|
|
// Emit the computed jump operation
|
|
Writer << EX_ComputedJump;
|
|
|
|
// Now include the integer offset expression
|
|
EmitTerm(Statement.LHS, (FProperty*)(GetDefault<FIntProperty>()));
|
|
}
|
|
else if (Statement.Type == KCST_GotoIfNot)
|
|
{
|
|
// Emit the jump with a dummy address
|
|
Writer << EX_JumpIfNot;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
// Queue up a fixup to be done once all label offsets are known
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, Statement.TargetLabel));
|
|
|
|
// Now include the boolean expression
|
|
EmitTerm(Statement.LHS, (FProperty*)(GetDefault<FBoolProperty>()));
|
|
}
|
|
else if (Statement.Type == KCST_EndOfThreadIfNot)
|
|
{
|
|
// Emit the pop if not opcode
|
|
Writer << EX_PopExecutionFlowIfNot;
|
|
|
|
// Now include the boolean expression
|
|
EmitTerm(Statement.LHS, (FProperty*)(GetDefault<FBoolProperty>()));
|
|
}
|
|
else if (Statement.Type == KCST_UnconditionalGoto)
|
|
{
|
|
// Emit the jump with a dummy address
|
|
Writer << EX_Jump;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
// Queue up a fixup to be done once all label offsets are known
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, Statement.TargetLabel));
|
|
}
|
|
else if (Statement.Type == KCST_GotoReturn)
|
|
{
|
|
// Emit the jump with a dummy address
|
|
Writer << EX_Jump;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
// Queue up a fixup to be done once all label offsets are known
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, &ReturnStatement));
|
|
}
|
|
else if (Statement.Type == KCST_GotoReturnIfNot)
|
|
{
|
|
// Emit the jump with a dummy address
|
|
Writer << EX_JumpIfNot;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
// Queue up a fixup to be done once all label offsets are known
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, &ReturnStatement));
|
|
|
|
// Now include the boolean expression
|
|
EmitTerm(Statement.LHS, (FProperty*)(GetDefault<FBoolProperty>()));
|
|
}
|
|
else
|
|
{
|
|
ensureMsgf(false, TEXT("FScriptBuilderBase::EmitGoto unknown type"));
|
|
}
|
|
}
|
|
|
|
void EmitPushExecState(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
// Push the address onto the flow stack
|
|
Writer << EX_PushExecutionFlow;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
// Mark the target for fixup once the addresses have been resolved
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, Statement.TargetLabel));
|
|
}
|
|
|
|
void EmitPopExecState(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
// Pop the state off the flow stack
|
|
Writer << EX_PopExecutionFlow;
|
|
}
|
|
|
|
void EmitReturn(FKismetFunctionContext& Context)
|
|
{
|
|
FProperty* ReturnProperty = Context.Function->GetReturnProperty();
|
|
|
|
Writer << EX_Return;
|
|
|
|
if (ReturnProperty == NULL)
|
|
{
|
|
Writer << EX_Nothing;
|
|
}
|
|
else
|
|
{
|
|
Writer << EX_LocalOutVariable;
|
|
Writer << ReturnProperty;
|
|
}
|
|
}
|
|
|
|
void EmitSwitchValue(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
const int32 TermsBeforeCases = 1;
|
|
const int32 TermsPerCase = 2;
|
|
|
|
if ((Statement.RHS.Num() < 4) || (1 == (Statement.RHS.Num() % 2)))
|
|
{
|
|
// Error
|
|
ensure(false);
|
|
}
|
|
|
|
Writer << EX_SwitchValue;
|
|
// number of cases (without default)
|
|
uint16 NumCases = IntCastChecked<uint16, int32>((Statement.RHS.Num() - 2) / TermsPerCase);
|
|
Writer << NumCases;
|
|
// end goto index
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
// index term
|
|
auto IndexTerm = Statement.RHS[0];
|
|
check(IndexTerm);
|
|
EmitTerm(IndexTerm);
|
|
FProperty* VirtualIndexProperty = IndexTerm->AssociatedVarProperty;
|
|
check(VirtualIndexProperty);
|
|
|
|
auto DefaultTerm = Statement.RHS[TermsBeforeCases + NumCases*TermsPerCase];
|
|
check(DefaultTerm);
|
|
FProperty* VirtualValueProperty = DefaultTerm->AssociatedVarProperty;
|
|
check(VirtualValueProperty);
|
|
|
|
for (uint16 TermIndex = TermsBeforeCases; TermIndex < (NumCases * TermsPerCase); ++TermIndex)
|
|
{
|
|
EmitTerm(Statement.RHS[TermIndex], VirtualIndexProperty); // it's a literal value
|
|
++TermIndex;
|
|
CodeSkipSizeType PatchOffsetToNextCase = Writer.EmitPlaceholderSkip();
|
|
EmitTerm(Statement.RHS[TermIndex], VirtualValueProperty); // it could be literal for 'self'
|
|
Writer.CommitSkip(PatchOffsetToNextCase, Writer.ScriptBuffer.Num());
|
|
}
|
|
|
|
// default term
|
|
EmitTerm(DefaultTerm);
|
|
|
|
Writer.CommitSkip(PatchUpNeededAtOffset, Writer.ScriptBuffer.Num());
|
|
}
|
|
|
|
void EmitInstrumentation(FKismetCompilerContext& CompilerContext, FKismetFunctionContext& FunctionContext, FBlueprintCompiledStatement& Statement, UEdGraphNode* SourceNode)
|
|
{
|
|
// Allows us to turn off instrumentation to clean up the disassembly of functions for comparing the output of different USVM bytecode compilers
|
|
static const FBoolConfigValueHelper bShouldSupressInstrumentation(TEXT("Kismet"), TEXT("bSuppressInstrumentation"), GEngineIni);
|
|
if (bShouldSupressInstrumentation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int32 Offset = Writer.ScriptBuffer.Num();
|
|
|
|
if (Statement.Type == KCST_DebugSite)
|
|
{
|
|
Writer << EX_Tracepoint;
|
|
}
|
|
else if (Statement.Type == KCST_WireTraceSite)
|
|
{
|
|
Writer << EX_WireTracepoint;
|
|
}
|
|
else
|
|
{
|
|
uint8 EventType = 0;
|
|
switch (Statement.Type)
|
|
{
|
|
case KCST_InstrumentedEvent: EventType = EScriptInstrumentation::InlineEvent; break;
|
|
case KCST_InstrumentedEventStop: EventType = EScriptInstrumentation::Stop; break;
|
|
case KCST_InstrumentedWireExit: EventType = EScriptInstrumentation::NodeExit; break;
|
|
case KCST_InstrumentedWireEntry: EventType = EScriptInstrumentation::NodeEntry; break;
|
|
case KCST_InstrumentedPureNodeEntry: EventType = EScriptInstrumentation::PureNodeEntry; break;
|
|
case KCST_InstrumentedStatePush: EventType = EScriptInstrumentation::PushState; break;
|
|
case KCST_InstrumentedStateRestore: EventType = EScriptInstrumentation::RestoreState; break;
|
|
case KCST_InstrumentedStateReset: EventType = EScriptInstrumentation::ResetState; break;
|
|
case KCST_InstrumentedStateSuspend: EventType = EScriptInstrumentation::SuspendState; break;
|
|
case KCST_InstrumentedStatePop: EventType = EScriptInstrumentation::PopState; break;
|
|
case KCST_InstrumentedTunnelEndOfThread: EventType = EScriptInstrumentation::TunnelEndOfThread; break;
|
|
}
|
|
Writer << EX_InstrumentationEvent;
|
|
Writer << EventType;
|
|
if (EventType == EScriptInstrumentation::InlineEvent)
|
|
{
|
|
FName EventName(*Statement.Comment);
|
|
Writer << EventName;
|
|
}
|
|
else if (EventType == EScriptInstrumentation::SuspendState)
|
|
{
|
|
if (Statement.TargetLabel->TargetLabel)
|
|
{
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
FCodeSkipInfo CodeSkipInfo(FCodeSkipInfo::InstrumentedDelegateFixup, Statement.TargetLabel->TargetLabel, &Statement);
|
|
if (Statement.TargetLabel->FunctionToCall)
|
|
{
|
|
CodeSkipInfo.DelegateName = Statement.TargetLabel->FunctionToCall->GetFName();
|
|
}
|
|
// Queue up a fixup to be done once all label offsets are known
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, CodeSkipInfo);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<UEdGraphPin*> PinContextArray(Statement.PureOutputContextArray);
|
|
if (Statement.ExecContext != nullptr)
|
|
{
|
|
PinContextArray.Add(Statement.ExecContext);
|
|
}
|
|
|
|
for (auto PinContext : PinContextArray)
|
|
{
|
|
UEdGraphPin const* TrueSourcePin = FunctionContext.MessageLog.FindSourcePin(PinContext);
|
|
// Source pin can be marked as pending kill if it was a generated pin that node logic decided to disown, e.g.
|
|
// logic in UK2Node_CallFunction to handle bWantsEnumToExecExpansion:
|
|
if (TrueSourcePin && !TrueSourcePin->IsPendingKill())
|
|
{
|
|
ClassBeingBuilt->GetDebugData().RegisterPinToCodeAssociation(TrueSourcePin, FunctionContext.Function, Offset);
|
|
}
|
|
}
|
|
|
|
if (SourceNode != NULL)
|
|
{
|
|
// Record where this NOP is
|
|
UEdGraphNode* TrueSourceNode = Cast<UEdGraphNode>(FunctionContext.MessageLog.FindSourceObject(SourceNode));
|
|
if (TrueSourceNode)
|
|
{
|
|
const bool bInstrumentedBreakpoint = Statement.Type == KCST_InstrumentedWireEntry;
|
|
const bool bBreakpointSite = Statement.Type == KCST_DebugSite || bInstrumentedBreakpoint;
|
|
|
|
// If the intermediate node was the result of a tunnel expansion (e.g. macro/composite), gather the chain of tunnel instance
|
|
// source nodes that were expanded to eventually include the node. This information is used to construct a lookup table that's
|
|
// used to help determine whether or not the current instruction pointer maps back to a tunnel instance node in a source graph.
|
|
// For example:
|
|
//
|
|
// A (Macro instance node in top-level source graph)
|
|
// |
|
|
// +- [...expansion of A...] + B (Composite node in A's macro source graph)
|
|
// |
|
|
// +- [...expansion of B...] + C (Macro instance node in B's collapsed child subgraph)
|
|
// |
|
|
// +- [...expansion of C...]
|
|
//
|
|
// The intermediate exec nodes in each expansion set will map back to C through MessageLog.GetIntermediateTunnelInstance().
|
|
// Thus, if SourceNode was created as the result of a tunnel instance expansion, it will map back to an outer Tunnel Instance
|
|
// node. If the Tunnel Instance node itself was created as the result of a tunnel instance expansion, it will map back to
|
|
// another outer Tunnel Instance node as well. This will continue until we run out of Tunnel Instance nodes. The set of Tunnel
|
|
// Instance nodes that we find constitutes the full expansion hierarchy. We then map the hierarchy back to their matching
|
|
// source nodes and register the set as the Tunnel Instance node chain that's associated with the current instruction offset.
|
|
//
|
|
TArray<TWeakObjectPtr<UEdGraphNode>> ExpansionSourceNodes;
|
|
if (const UEdGraphNode* OuterTunnelInstance = FunctionContext.MessageLog.GetIntermediateTunnelInstance(SourceNode))
|
|
{
|
|
do
|
|
{
|
|
// Map the intermediate tunnel instance node back to its original source and add it to the array.
|
|
if (const UEdGraphNode* ExpansionSourceNode = Cast<UEdGraphNode>(FunctionContext.MessageLog.FindSourceObject(OuterTunnelInstance)))
|
|
{
|
|
ExpansionSourceNodes.Add(MakeWeakObjectPtr(const_cast<UEdGraphNode*>(ExpansionSourceNode)));
|
|
}
|
|
|
|
// Continue back up the chain until we run out of expansion source nodes (this ensures that we include any nested expansions).
|
|
OuterTunnelInstance = FunctionContext.MessageLog.GetIntermediateTunnelInstance(OuterTunnelInstance);
|
|
|
|
} while (OuterTunnelInstance);
|
|
}
|
|
|
|
// Register the debug information for the node.
|
|
ClassBeingBuilt->GetDebugData().RegisterNodeToCodeAssociation(TrueSourceNode, ExpansionSourceNodes, FunctionContext.Function, Offset, bBreakpointSite);
|
|
|
|
// Track pure node script code range for the current impure (exec) node
|
|
if (Statement.Type == KCST_InstrumentedPureNodeEntry)
|
|
{
|
|
if (PureNodeEntryCount == 0)
|
|
{
|
|
// Indicates the starting offset for this pure node call chain.
|
|
PureNodeEntryStart = Offset;
|
|
}
|
|
|
|
++PureNodeEntryCount;
|
|
}
|
|
else if (Statement.Type == KCST_InstrumentedWireEntry && PureNodeEntryCount > 0)
|
|
{
|
|
// Map script code range for the full set of pure node inputs feeding in to the current impure (exec) node at the current offset
|
|
ClassBeingBuilt->GetDebugData().RegisterPureNodeScriptCodeRange(TrueSourceNode, FunctionContext.Function, FInt32Range(PureNodeEntryStart, Offset));
|
|
|
|
// Reset pure node code range tracking.
|
|
PureNodeEntryCount = 0;
|
|
PureNodeEntryStart = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmitArrayGetByRef(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
Writer << EX_ArrayGetByRef;
|
|
// The array variable
|
|
EmitTerm(Statement.RHS[0]);
|
|
// The index to access in the array
|
|
EmitTerm(Statement.RHS[1], (FProperty*)(GetDefault<FIntProperty>()));
|
|
}
|
|
|
|
void EmitCastStatement(FBlueprintCompiledStatement& Statement)
|
|
{
|
|
FBPTerminal* DestinationExpression = Statement.LHS;
|
|
FBPTerminal* TargetExpression = Statement.RHS[0];
|
|
|
|
Writer << EX_Let;
|
|
FProperty* PropertyToHandleComplexStruct = nullptr;
|
|
Writer << PropertyToHandleComplexStruct;
|
|
EmitTerm(DestinationExpression);
|
|
|
|
Writer << EX_Cast;
|
|
|
|
ECastToken CastType = CST_Max;
|
|
|
|
switch (Statement.Type)
|
|
{
|
|
case KCST_DoubleToFloatCast:
|
|
CastType = CST_DoubleToFloat;
|
|
break;
|
|
case KCST_FloatToDoubleCast:
|
|
CastType = CST_FloatToDouble;
|
|
break;
|
|
default:
|
|
check(false);
|
|
break;
|
|
}
|
|
|
|
Writer << CastType;
|
|
|
|
EmitTerm(TargetExpression);
|
|
}
|
|
|
|
void PushReturnAddress(FBlueprintCompiledStatement& ReturnTarget)
|
|
{
|
|
Writer << EX_PushExecutionFlow;
|
|
CodeSkipSizeType PatchUpNeededAtOffset = Writer.EmitPlaceholderSkip();
|
|
|
|
JumpTargetFixupMap.Add(PatchUpNeededAtOffset, FCodeSkipInfo(FCodeSkipInfo::Fixup, &ReturnTarget));
|
|
}
|
|
|
|
void CloseScript()
|
|
{
|
|
Writer << EX_EndOfScript;
|
|
}
|
|
|
|
virtual ~FScriptBuilderBase()
|
|
{
|
|
}
|
|
|
|
void GenerateCodeForStatement(FKismetCompilerContext& CompilerContext, FKismetFunctionContext& FunctionContext, FBlueprintCompiledStatement& Statement, UEdGraphNode* SourceNode)
|
|
{
|
|
TGuardValue<FKismetCompilerContext*> CompilerContextGuard(CurrentCompilerContext, &CompilerContext);
|
|
TGuardValue<FKismetFunctionContext*> FunctionContextGuard(CurrentFunctionContext, &FunctionContext);
|
|
|
|
// Record the start of this statement in the bytecode if it's needed as a target label
|
|
if (Statement.bIsJumpTarget)
|
|
{
|
|
StatementLabelMap.Add(&Statement, Writer.ScriptBuffer.Num());
|
|
}
|
|
|
|
// Generate bytecode for the statement
|
|
switch (Statement.Type)
|
|
{
|
|
case KCST_Nop:
|
|
Writer << EX_Nothing;
|
|
break;
|
|
case KCST_CallFunction:
|
|
EmitFunctionCall(CompilerContext, FunctionContext, Statement, SourceNode);
|
|
break;
|
|
case KCST_CallDelegate:
|
|
EmitCallDelegate(Statement);
|
|
break;
|
|
case KCST_Assignment:
|
|
EmitAssignmentStatment(Statement);
|
|
break;
|
|
case KCST_AssignmentOnPersistentFrame:
|
|
EmitAssignmentOnPersistentFrameStatment(Statement);
|
|
break;
|
|
case KCST_CastObjToInterface:
|
|
EmitCastObjToInterfaceStatement(Statement);
|
|
break;
|
|
case KCST_CrossInterfaceCast:
|
|
EmitCastBetweenInterfacesStatement(Statement);
|
|
break;
|
|
case KCST_CastInterfaceToObj:
|
|
EmitCastInterfaceToObjStatement(Statement);
|
|
break;
|
|
case KCST_DynamicCast:
|
|
EmitDynamicCastStatement(Statement);
|
|
break;
|
|
case KCST_MetaCast:
|
|
EmitMetaCastStatement(Statement);
|
|
break;
|
|
case KCST_ObjectToBool:
|
|
EmitObjectToBoolStatement(Statement);
|
|
break;
|
|
case KCST_AddMulticastDelegate:
|
|
EmitAddMulticastDelegateStatement(Statement);
|
|
break;
|
|
case KCST_RemoveMulticastDelegate:
|
|
EmitRemoveMulticastDelegateStatement(Statement);
|
|
break;
|
|
case KCST_BindDelegate:
|
|
EmitBindDelegateStatement(Statement);
|
|
break;
|
|
case KCST_ClearMulticastDelegate:
|
|
EmitClearMulticastDelegateStatement(Statement);
|
|
break;
|
|
case KCST_CreateArray:
|
|
EmitCreateArrayStatement(Statement);
|
|
break;
|
|
case KCST_ComputedGoto:
|
|
case KCST_UnconditionalGoto:
|
|
case KCST_GotoIfNot:
|
|
case KCST_EndOfThreadIfNot:
|
|
case KCST_GotoReturn:
|
|
case KCST_GotoReturnIfNot:
|
|
EmitGoto(Statement);
|
|
break;
|
|
case KCST_PushState:
|
|
EmitPushExecState(Statement);
|
|
break;
|
|
case KCST_EndOfThread:
|
|
EmitPopExecState(Statement);
|
|
break;
|
|
case KCST_Comment:
|
|
// VM ignores comments
|
|
break;
|
|
case KCST_Return:
|
|
EmitReturn(FunctionContext);
|
|
break;
|
|
case KCST_SwitchValue:
|
|
EmitSwitchValue(Statement);
|
|
break;
|
|
case KCST_DebugSite:
|
|
case KCST_WireTraceSite:
|
|
case KCST_InstrumentedEvent:
|
|
case KCST_InstrumentedEventStop:
|
|
case KCST_InstrumentedWireEntry:
|
|
case KCST_InstrumentedWireExit:
|
|
case KCST_InstrumentedStatePush:
|
|
case KCST_InstrumentedStateReset:
|
|
case KCST_InstrumentedStateSuspend:
|
|
case KCST_InstrumentedStatePop:
|
|
case KCST_InstrumentedStateRestore:
|
|
case KCST_InstrumentedPureNodeEntry:
|
|
case KCST_InstrumentedTunnelEndOfThread:
|
|
EmitInstrumentation(CompilerContext, FunctionContext, Statement, SourceNode);
|
|
break;
|
|
case KCST_ArrayGetByRef:
|
|
EmitArrayGetByRef(Statement);
|
|
break;
|
|
case KCST_CreateSet:
|
|
EmitCreateSetStatement(Statement);
|
|
break;
|
|
case KCST_CreateMap:
|
|
EmitCreateMapStatement(Statement);
|
|
break;
|
|
case KCST_DoubleToFloatCast:
|
|
case KCST_FloatToDoubleCast:
|
|
EmitCastStatement(Statement);
|
|
break;
|
|
default:
|
|
UE_LOG(LogK2Compiler, Warning, TEXT("VM backend encountered unsupported statement type %d"), (int32)Statement.Type);
|
|
}
|
|
}
|
|
|
|
// Fix up all jump targets
|
|
void PerformFixups()
|
|
{
|
|
for (TMap<CodeSkipSizeType, FCodeSkipInfo>::TIterator It(JumpTargetFixupMap); It; ++It)
|
|
{
|
|
CodeSkipSizeType OffsetToFix = It.Key();
|
|
FCodeSkipInfo& CodeSkipInfo = It.Value();
|
|
|
|
CodeSkipSizeType TargetStatementOffset = StatementLabelMap.FindChecked(CodeSkipInfo.TargetLabel);
|
|
|
|
Writer.CommitSkip(OffsetToFix, TargetStatementOffset);
|
|
|
|
if (CodeSkipInfo.Type == FCodeSkipInfo::InstrumentedDelegateFixup)
|
|
{
|
|
// Register delegate entrypoint offsets
|
|
ClassBeingBuilt->GetDebugData().RegisterEntryPoint(TargetStatementOffset, CodeSkipInfo.DelegateName);
|
|
}
|
|
}
|
|
|
|
JumpTargetFixupMap.Empty();
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FKismetCompilerVMBackend
|
|
|
|
void FKismetCompilerVMBackend::GenerateCodeFromClass(UClass* SourceClass, TIndirectArray<FKismetFunctionContext>& Functions, bool bGenerateStubsOnly)
|
|
{
|
|
// Generate script bytecode
|
|
for (int32 i = 0; i < Functions.Num(); ++i)
|
|
{
|
|
FKismetFunctionContext& Function = Functions[i];
|
|
if (Function.IsValid())
|
|
{
|
|
const bool bIsUbergraph = (i == 0);
|
|
ConstructFunction(Function, bIsUbergraph, bGenerateStubsOnly);
|
|
}
|
|
}
|
|
|
|
// Remove duplicates from CalledFunctions:
|
|
UBlueprintGeneratedClass* ClassBeingBuilt = CastChecked<UBlueprintGeneratedClass>(SourceClass);
|
|
TSet<TObjectPtr<UFunction>> Unique(ClassBeingBuilt->CalledFunctions);
|
|
ClassBeingBuilt->CalledFunctions = Unique.Array();
|
|
}
|
|
|
|
void FKismetCompilerVMBackend::ConstructFunction(FKismetFunctionContext& FunctionContext, bool bIsUbergraph, bool bGenerateStubOnly)
|
|
{
|
|
UFunction* Function = FunctionContext.Function;
|
|
UBlueprintGeneratedClass* Class = FunctionContext.NewClass;
|
|
|
|
FString FunctionName;
|
|
Function->GetName(FunctionName);
|
|
|
|
TArray<uint8>& ScriptArray = Function->Script;
|
|
|
|
// Return statement, to push on FlowStack or to use with _GotoReturn
|
|
FBlueprintCompiledStatement ReturnStatement;
|
|
ReturnStatement.Type = KCST_Return;
|
|
|
|
FScriptBuilderBase ScriptWriter(ScriptArray, Class, Schema, UbergraphStatementLabelMap, bIsUbergraph, ReturnStatement);
|
|
|
|
if (!bGenerateStubOnly)
|
|
{
|
|
ReturnStatement.bIsJumpTarget = true;
|
|
if (FunctionContext.bUseFlowStack)
|
|
{
|
|
ScriptWriter.PushReturnAddress(ReturnStatement);
|
|
}
|
|
|
|
// Emit code in the order specified by the linear execution list (the first node is always the entry point for the function)
|
|
for (int32 NodeIndex = 0; NodeIndex < FunctionContext.LinearExecutionList.Num(); ++NodeIndex)
|
|
{
|
|
UEdGraphNode* StatementNode = FunctionContext.LinearExecutionList[NodeIndex];
|
|
TArray<FBlueprintCompiledStatement*>* StatementList = FunctionContext.StatementsPerNode.Find(StatementNode);
|
|
|
|
if (StatementList != nullptr)
|
|
{
|
|
for (int32 StatementIndex = 0; StatementIndex < StatementList->Num(); ++StatementIndex)
|
|
{
|
|
FBlueprintCompiledStatement* Statement = (*StatementList)[StatementIndex];
|
|
|
|
ScriptWriter.GenerateCodeForStatement(CompilerContext, FunctionContext, *Statement, StatementNode);
|
|
|
|
// Abort code generation on error (no need to process additional statements).
|
|
if (FunctionContext.MessageLog.NumErrors > 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reduce to a stub if any errors were raised. This ensures the VM won't attempt to evaluate an incomplete expression.
|
|
if (FunctionContext.MessageLog.NumErrors > 0)
|
|
{
|
|
ScriptArray.Empty();
|
|
ReturnStatement.bIsJumpTarget = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle the function return value
|
|
ScriptWriter.GenerateCodeForStatement(CompilerContext, FunctionContext, ReturnStatement, nullptr);
|
|
|
|
// Fix up jump addresses
|
|
ScriptWriter.PerformFixups();
|
|
|
|
// Close out the script
|
|
ScriptWriter.CloseScript();
|
|
|
|
// Save off the offsets within the ubergraph, needed to patch up the stubs later on
|
|
if (bIsUbergraph)
|
|
{
|
|
ScriptWriter.CopyStatementMapToUbergraphMap();
|
|
}
|
|
|
|
// Make sure we didn't overflow the maximum bytecode size
|
|
#if SCRIPT_LIMIT_BYTECODE_TO_64KB
|
|
if (ScriptArray.Num() > 0xFFFF)
|
|
{
|
|
MessageLog.Error(TEXT("Script exceeded bytecode length limit of 64 KB"));
|
|
ScriptArray.Empty();
|
|
ScriptArray.Add(EX_EndOfScript);
|
|
}
|
|
#else
|
|
static_assert(sizeof(CodeSkipSizeType) == 4, "Update this code as size changed.");
|
|
#endif
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|