For Developers Working With Unreal Engine

Tiny Code Fixes – Part 1

by

Sometimes, you come across some nice little code fixes/improvements that are worth making but that are so tiny, they don’t really deserve blogging about… but… as we’re not using Git at Coconut Lizard and would still like to pass these fixes back to Epic and the community, we decided that we could just pass these through the blog regardless… so, without further ado…


Ei8ht

This bug’s been in the codebase since sometime around May 2015.

  • fixed issue with not being able to tell if a content-based project needs to be built as code within the editor UE-11636 (CL 2533916, Peter Sauerbrei)

With this change came, amongst other things, the following:-

bool GameProjectUtils::HasDefaultBuildSettings(const FName InPlatformInfoName)
{
  // first check default build settings for all platforms
  TArray<FString> BoolKeys, IntKeys, StringKeys, BuildKeys;
  BuildKeys.Add(TEXT("bCompileApex")); BuildKeys.Add(TEXT("bCompileBox2D")); BuildKeys.Add(TEXT("bCompileICU"));
  BuildKeys.Add(TEXT("bCompileSimplygon")); BuildKeys.Add(TEXT("bCompi8leLeanAndMeanUE")); BuildKeys.Add(TEXT("bIncludeADO"));

Note the “8” in bCompi8leLeanAndMeanUE … this should of course be bCompileLeanAndMeanUE…


Mass Conversion

The next find is in FModuleManager::AddModule()… not a bug as such – but still quite a bad coding mistake. Thankfully, one that’s not in any runtime performance-critical code…

const FString ModuleName = *InModuleName.ToString();

If you disassembled this, you’d be horified with the amount of generated code here. What this line is doing is converting an FName to a string (through ToString()), then converting to a TCHAR array (through the dereference) and then back to an FString. This should of course be:-

const FString ModuleName = InModuleName.ToString();

This code is only used on non-shipping/test builds by the way.


Hidden Complexities

Our third tidbit today is found in UEnum::GenerateEnumPrefix():-

FString UEnum::GenerateEnumPrefix() const
{
  FString Prefix;
  if (Names.Num() > 0)
  {
    Prefix = Names[0].Key.ToString();

    // For each item in the enumeration, trim the prefix as much as necessary to keep it a prefix.
    // This ensures that once all items have been processed, a common prefix will have been constructed.
    // This will be the longest common prefix since as little as possible is trimmed at each step.
    for (int32 NameIdx = 1; NameIdx < Names.Num(); NameIdx++)
    {
      FString EnumItemName = *Names[NameIdx].Key.ToString();

There are actually 2 things to look at here:-

  • line 6: A few subtle things going on here… some suspicious memory deallocations appearing in the disassembly which likely are never needed;
  • line 13: We have another Name->String->TCHAR[]->String case here, as in the previous example.

Let’s look at the disassembly for each of these…

Line 6 compiles to:-

00007FF73D9164AB  mov         rcx,qword ptr [rcx+48h]  
00007FF73D9164AF  mov         qword ptr [rax+10h],rbp  
00007FF73D9164B3  lea         rdx,[rax-48h]  
00007FF73D9164B7  mov         qword ptr [rax-28h],r13  
00007FF73D9164BB  call        FName::ToString (07FF73D8AFB60h)  
00007FF73D9164C0  mov         rbx,rax  
00007FF73D9164C3  cmp         rsi,rax  
00007FF73D9164C6  je          UEnum::GenerateEnumPrefix+6Eh (07FF73D9164EEh)  
00007FF73D9164C8  mov         rcx,qword ptr [rsi]  
00007FF73D9164CB  test        rcx,rcx  
00007FF73D9164CE  je          UEnum::GenerateEnumPrefix+55h (07FF73D9164D5h)  
00007FF73D9164D0  call        FMemory::Free (07FF73D7A7F90h)  
00007FF73D9164D5  mov         rcx,qword ptr [rbx]  
00007FF73D9164D8  mov         qword ptr [rsi],rcx  
00007FF73D9164DB  mov         qword ptr [rbx],r14  
00007FF73D9164DE  mov         eax,dword ptr [rbx+8]  
00007FF73D9164E1  mov         dword ptr [rsi+8],eax  
00007FF73D9164E4  mov         eax,dword ptr [rbx+0Ch]  
00007FF73D9164E7  mov         dword ptr [rsi+0Ch],eax  
00007FF73D9164EA  mov         qword ptr [rbx+8],r14  
00007FF73D9164EE  mov         rcx,qword ptr [rsp+30h]  
00007FF73D9164F3  test        rcx,rcx  
00007FF73D9164F6  je          UEnum::GenerateEnumPrefix+7Dh (07FF73D9164FDh)  
00007FF73D9164F8  call        FMemory::Free (07FF73D7A7F90h)  

And line 13:-

00007FF73D916530  movsxd      rcx,r13d  
00007FF73D916533  lea         rdx,[rsp+30h]  
00007FF73D916538  shl         rcx,4  
00007FF73D91653C  add         rcx,qword ptr [r15+48h]  
00007FF73D916540  call        FName::ToString (07FF73D8AFB60h)  
00007FF73D916545  cmp         dword ptr [rax+8],0  
00007FF73D916549  je          UEnum::GenerateEnumPrefix+0D0h (07FF73D916550h)  
00007FF73D91654B  mov         rdi,qword ptr [rax]  
00007FF73D91654E  jmp         UEnum::GenerateEnumPrefix+0D3h (07FF73D916553h)  
00007FF73D916550  mov         rdi,rbp  
00007FF73D916553  mov         r12,r14  
00007FF73D916556  mov         ebx,r14d  
00007FF73D916559  test        rdi,rdi  
00007FF73D91655C  je          UEnum::GenerateEnumPrefix+135h (07FF73D9165B5h)  
00007FF73D91655E  cmp         word ptr [rdi],0  
00007FF73D916562  je          UEnum::GenerateEnumPrefix+135h (07FF73D9165B5h)  
00007FF73D916564  or          rbx,0FFFFFFFFFFFFFFFFh  
00007FF73D916568  nop         dword ptr [rax+rax]  
00007FF73D916570  inc         rbx  
00007FF73D916573  cmp         word ptr [rdi+rbx*2],0  
00007FF73D916578  jne         UEnum::GenerateEnumPrefix+0F0h (07FF73D916570h)  
00007FF73D91657A  inc         ebx  
00007FF73D91657C  test        ebx,ebx  
00007FF73D91657E  jle         UEnum::GenerateEnumPrefix+124h (07FF73D9165A4h)  
00007FF73D916580  xor         edx,edx  
00007FF73D916582  mov         ecx,ebx  
00007FF73D916584  lea         r8d,[rdx+2]  
00007FF73D916588  call        DefaultCalculateSlack (07FF73D76AF70h)  
00007FF73D91658D  test        eax,eax  
00007FF73D91658F  je          UEnum::GenerateEnumPrefix+124h (07FF73D9165A4h)  
00007FF73D916591  movsxd      rdx,eax  
00007FF73D916594  xor         r8d,r8d  
00007FF73D916597  xor         ecx,ecx  
00007FF73D916599  add         rdx,rdx  
00007FF73D91659C  call        FMemory::Realloc (07FF73D7BEBA0h)  
00007FF73D9165A1  mov         r12,rax  
00007FF73D9165A4  movsxd      r8,ebx  
00007FF73D9165A7  mov         rdx,rdi  
00007FF73D9165AA  mov         rcx,r12  
00007FF73D9165AD  add         r8,r8  
00007FF73D9165B0  call        FGenericPlatformString::Memcpy (07FF73D776F20h)  
00007FF73D9165B5  mov         rcx,qword ptr [rsp+30h]  
00007FF73D9165BA  test        rcx,rcx  
00007FF73D9165BD  je          UEnum::GenerateEnumPrefix+144h (07FF73D9165C4h)  
00007FF73D9165BF  call        FMemory::Free (07FF73D7A7F90h)  

Now let’s fixup the code and compare:-

A much better version of this code:-

FString UEnum::GenerateEnumPrefix() const
{
  FString Prefix;
  if (Names.Num() > 0)
  {
    Names[0].Key.ToString(Prefix);

    // For each item in the enumeration, trim the prefix as much as necessary to keep it a prefix.
    // This ensures that once all items have been processed, a common prefix will have been constructed.
    // This will be the longest common prefix since as little as possible is trimmed at each step.
    for (int32 NameIdx = 1; NameIdx < Names.Num(); NameIdx++)
    {
      FString EnumItemName = Names[NameIdx].Key.ToString();

And let’s see the disassembly again. Line 6 now gives:-

00007FF6AFA76944  mov         rcx,qword ptr [rcx+48h]  
00007FF6AFA76948  mov         qword ptr [rax+8],rsi  
00007FF6AFA7694C  mov         qword ptr [rax+10h],r12  
00007FF6AFA76950  mov         qword ptr [rax+18h],r14  
00007FF6AFA76954  mov         qword ptr [rax-28h],r15  
00007FF6AFA76958  call        FName::ToString (07FF6AFA0FB80h)

And line 13:-

00007FF6AFA76980  movsxd      rcx,r12d  
00007FF6AFA76983  lea         rdx,[EnumItemName]  
00007FF6AFA76988  shl         rcx,4  
00007FF6AFA7698C  add         rcx,qword ptr [r13+48h]  
00007FF6AFA76990  call        FName::ToString (07FF6AFA0FB60h)

In total, the entire GenerateEnumPrefix() was coming out 212 bytes shorter for me.


64k Test Data

The final finding I’m going to talk about is a 64k buffer called RawVoiceTestData[] … note the code below:-

#if WITH_DEV_AUTOMATION_TESTS
uint8 RawVoiceTestData[] = { 0x0, 0x0 };
#else
/** 16khz wav mono voice data "this is a test" 2 sec duration */
uint8 RawVoiceTestData[] = {
  0xcc, 0xff, 0xb8, 0xff, 0x97, 0xff, 0x87, 0xff, 0x94, 0xff, 0xa0, 0xff, 0xad, 0xff, 0xa0, 0xff, 0xb7, 0xff, 0x9f, 0xff, 0x84, 0xff, 0x80, 0xff, 0x80, 0xff, 0x95, 0xff, 0x85, 0xff, 0x8e, 0xff, 

... many more lines of raw data follow 

The above buffer is only ever accessed through code where WITH_DEV_AUTOMATION_TESTS is set, meaning that the shorter, 2-byte, version of the buffer will be used – but never the 64k version. So we have a bug on the test build and we have wasted memory on non-test builds. Doh!

The code should of course be:-

#if WITH_DEV_AUTOMATION_TESTS
/** 16khz wav mono voice data "this is a test" 2 sec duration */
uint8 RawVoiceTestData[] = {
  0xcc, 0xff, 0xb8, 0xff, 0x97, 0xff, 0x87, 0xff, 0x94, 0xff, 0xa0, 0xff, 0xad, 0xff, 0xa0, 0xff, 0xb7, 0xff, 0x9f, 0xff, 0x84, 0xff, 0x80, 0xff, 0x80, 0xff, 0x95, 0xff, 0x85, 0xff, 0x8e, 0xff, 

... etc

See you next time..!


Credit(s): Robert Troughton (Coconut Lizard)
Status: Currently unimplemented in 4.12


Leave a Reply

Your email address will not be published.

*

Latest from ALL

Trash Compactor

We recently found a huge leak in the UE4 garbage collector, particularly

Placating The Natives

In this article we delve into Blueprint Nativization, a relatively new feature
Go to Top
%d bloggers like this: