// 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& ScriptBuffer; public: FScriptBytecodeWriter( TArray& InScriptBuffer ) : ScriptBuffer( InScriptBuffer ) { } void Serialize( void* V, int64 Length ) override { int32 iStart = ScriptBuffer.AddUninitialized(IntCastChecked(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(E); Serialize(&B, 1); return *this; } FArchive& operator<<(ECastToken E) { uint8 B = static_cast(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(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& Script; FSkipOffsetEmitter(TArray& 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 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 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& 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::Get(); Vector3fStruct = TVariantStructure::Get(); RotatorStruct = TBaseStructure::Get(); TransformStruct = TBaseStructure::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(*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(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Boolean); } static bool IsBit(const FProperty* Property) { if (Property && Property->GetOwnerStruct() && !CastFieldChecked(Property)->IsNativeBool()) { return true; } return false; } static bool IsString(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_String); } static bool IsText(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Text); } static bool IsFloat(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } 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(); } 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(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Int); } static bool IsInt64(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Int64); } static bool IsUInt64(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return false; } static bool IsByte(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA() || (Property->IsA() && static_cast(Property)->GetUnderlyingProperty()->IsA()); } 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(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Name); } static bool IsStruct(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Struct); } static bool IsDelegate(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Delegate); } static bool IsSoftObject(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } 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(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Object); } static bool IsClass(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Class); } static bool IsInterface(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } return Type && (Type->PinCategory == UEdGraphSchema_K2::PC_Interface); } static bool IsFieldPath(const FEdGraphPinType* Type, const FProperty* Property) { if (Property) { return Property->IsA(); } 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(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(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(CoerceProperty); UScriptStruct* Struct = StructProperty ? ToRawPtr(StructProperty->Struct) : ToRawPtr(Cast(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(Prop)->GetPropertyValue_InContainer(StructData, ArrayIter); NewTerm.Name = NewTerm.TextLiteral.ToString(); } else if (Prop->IsA(FObjectProperty::StaticClass())) { NewTerm.ObjectLiteral = CastField(Prop)->GetObjectPropertyValue(Prop->ContainerPtrToValuePtr(StructData)); } EmitTermExpr(&NewTerm, Prop, true, true); } } Struct->DestroyStruct(StructData, ArrayDim); Writer << EX_EndStructConst; } } else if (const FArrayProperty* ArrayPropr = CastField(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(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(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(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(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(InnerProp)->GetPropertyValue(RawElemPtr); NewTerm.Name = NewTerm.TextLiteral.ToString(); } else if (InnerProp->IsA(FObjectPropertyBase::StaticClass())) { NewTerm.ObjectLiteral = CastField(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(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 PropIt(FunctionToCall); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt) { FProperty* Param = *PropIt; check(Param); const bool bShouldParameterBeCleared = Param->IsA() && 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 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 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 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(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(InterfaceExpression->ObjectLiteral); check(ClassPtr); Writer << ClassPtr; EmitTerm(TargetExpression, (FProperty*)(GetDefault())); } 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(InterfaceExpression->ObjectLiteral); check(ClassPtr); Writer << ClassPtr; EmitTerm(TargetExpression, (FProperty*)(GetDefault())); } 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(ResultObjClassExpression->ObjectLiteral); check(ClassPtr != nullptr); Writer << ClassPtr; EmitTerm(TargetInterfaceExpression, (FProperty*)(GetDefault())); } 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(ResultClassExpression->ObjectLiteral); Writer << ClassPtr; EmitTerm(TargetExpression, (FProperty*)(GetDefault())); } 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(InterfaceExpression->ObjectLiteral); Writer << ClassPtr; EmitTerm(TargetExpression, (FProperty*)(GetDefault())); } void EmitObjectToBoolStatement(FBlueprintCompiledStatement& Statement) { FBPTerminal* DestinationExpression = Statement.LHS; FBPTerminal* TargetExpression = Statement.RHS[0]; UClass* PSCObjClass = Cast(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(CastToken); Writer << CastType; FProperty* TargetProperty = !bIsInterfaceCast ? ((FProperty*)(GetDefault())) : ((FProperty*)(GetDefault())); 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())); } 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(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(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(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())); } 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())); } 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())); } 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())); } 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((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 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(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> 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(FunctionContext.MessageLog.FindSourceObject(OuterTunnelInstance))) { ExpansionSourceNodes.Add(MakeWeakObjectPtr(const_cast(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())); } 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 CompilerContextGuard(CurrentCompilerContext, &CompilerContext); TGuardValue 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::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& 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(SourceClass); TSet> 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& 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* 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