Bug 1403438 - Add profiler-lul on mips64-linux. r=sewardj
authorqiaopengcheng <qiaopengcheng-hf@loongson.cn>
Thu, 28 Sep 2017 02:04:00 -0400
changeset 391311 8411c490c109ecb8eb0e077c896988041913035c
parent 391310 0b2136c9de024ee156de6bd295a76921bdcc2b50
child 391312 e118b79b153ec2423fc5d455fb50ac6a04375685
push id32868
push userryanvm@gmail.com
push dateFri, 10 Nov 2017 21:13:32 +0000
treeherdermozilla-central@092ccc6335a5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssewardj
bugs1403438
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1403438 - Add profiler-lul on mips64-linux. r=sewardj
tools/profiler/core/platform.cpp
tools/profiler/lul/LulDwarf.cpp
tools/profiler/lul/LulDwarfExt.h
tools/profiler/lul/LulDwarfSummariser.cpp
tools/profiler/lul/LulElf.cpp
tools/profiler/lul/LulMain.cpp
tools/profiler/lul/LulMain.h
tools/profiler/lul/LulMainInt.h
tools/profiler/moz.build
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -102,17 +102,17 @@
 // Android builds use the ARM Exception Handling ABI to unwind.
 #if defined(GP_PLAT_arm_android)
 # define HAVE_NATIVE_UNWIND
 # define USE_EHABI_STACKWALK
 # include "EHABIStackWalk.h"
 #endif
 
 // Linux builds use LUL, which uses DWARF info to unwind stacks.
-#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux)
+#if defined(GP_PLAT_amd64_linux) || defined(GP_PLAT_x86_linux) || defined(GP_PLAT_mips64_linux)
 # define HAVE_NATIVE_UNWIND
 # define USE_LUL_STACKWALK
 # include "lul/LulMain.h"
 # include "lul/platform-linux-lul.h"
 
 // On linux we use LUL for periodic samples and synchronous samples, but we use
 // FramePointerStackWalk for backtrace samples when MOZ_PROFILING is enabled.
 // (See the comment at the top of the file for a definition of
--- a/tools/profiler/lul/LulDwarf.cpp
+++ b/tools/profiler/lul/LulDwarf.cpp
@@ -1851,16 +1851,31 @@ unsigned int DwarfCFIToModule::RegisterN
    8 "s8",  "s9",  "s10", "s11", "s12", "s13", "s14", "s15",
    8 "s16", "s17", "s18", "s19", "s20", "s21", "s22", "s23",
    8 "s24", "s25", "s26", "s27", "s28", "s29", "s30", "s31",
    8 "f0",  "f1",  "f2",  "f3",  "f4",  "f5",  "f6",  "f7"
   */
   return 13 * 8;
 }
 
+unsigned int DwarfCFIToModule::RegisterNames::MIPS() {
+  /*
+   8 "$zero", "$at",  "$v0",  "$v1",  "$a0",   "$a1",  "$a2",  "$a3",
+   8 "$t0",   "$t1",  "$t2",  "$t3",  "$t4",   "$t5",  "$t6",  "$t7",
+   8 "$s0",   "$s1",  "$s2",  "$s3",  "$s4",   "$s5",  "$s6",  "$s7",
+   8 "$t8",   "$t9",  "$k0",  "$k1",  "$gp",   "$sp",  "$fp",  "$ra",
+   9 "$lo",   "$hi",  "$pc",  "$f0",  "$f1",   "$f2",  "$f3",  "$f4",  "$f5",
+   8 "$f6",   "$f7",  "$f8",  "$f9",  "$f10",  "$f11", "$f12", "$f13",
+   7 "$f14",  "$f15", "$f16", "$f17", "$f18",  "$f19", "$f20",
+   7 "$f21",  "$f22", "$f23", "$f24", "$f25",  "$f26", "$f27",
+   6 "$f28",  "$f29", "$f30", "$f31", "$fcsr", "$fir"
+  */
+  return 8 + 8 + 8 + 8 + 9 + 8 + 7 + 7 + 6;
+}
+
 // See prototype for comments.
 int32_t parseDwarfExpr(Summariser* summ, const ByteReader* reader,
                        string expr, bool debug,
                        bool pushCfaAtStart, bool derefAtEnd)
 {
   const char* cursor = expr.c_str();
   const char* end1   = cursor + expr.length();
 
--- a/tools/profiler/lul/LulDwarfExt.h
+++ b/tools/profiler/lul/LulDwarfExt.h
@@ -1205,16 +1205,19 @@ class DwarfCFIToModule: public CallFrame
     // Intel's "x86" or IA-32.
     static unsigned int I386();
 
     // AMD x86_64, AMD64, Intel EM64T, or Intel 64
     static unsigned int X86_64();
 
     // ARM.
     static unsigned int ARM();
+
+    // MIPS.
+    static unsigned int MIPS();
   };
 
   // Create a handler for the dwarf2reader::CallFrameInfo parser that
   // records the stack unwinding information it receives in SUMM.
   //
   // Use REGISTER_NAMES[I] as the name of register number I; *this
   // keeps a reference to the vector, so the vector should remain
   // alive for as long as the DwarfCFIToModule does.
--- a/tools/profiler/lul/LulDwarfSummariser.cpp
+++ b/tools/profiler/lul/LulDwarfSummariser.cpp
@@ -324,16 +324,89 @@ Summariser::Rule(uintptr_t aAddress, int
     mCurrRules.mXspExpr = LExpr(NODEREF, DW_REG_CFA, 0);
   }
 
   // Also, gcc says "Undef" for BP when it is unchanged.
   if (mCurrRules.mXbpExpr.mHow == UNKNOWN) {
     mCurrRules.mXbpExpr = LExpr(NODEREF, DW_REG_INTEL_XBP, 0);
   }
 
+#elif defined(GP_ARCH_mips64)
+  // ---------------- mips ---------------- //
+  //
+  // Now, can we add the rule to our summary?  This depends on whether
+  // the registers and the overall expression are representable.  This
+  // is the heart of the summarisation process.
+   switch (aNewReg) {
+
+    case DW_REG_CFA:
+      // This is a rule that defines the CFA.  The only forms we can
+      // represent are: = SP+offset or = FP+offset.
+      if (how != NODEREF) {
+        reason1 = "rule for DW_REG_CFA: invalid |how|";
+        goto cant_summarise;
+      }
+      if (oldReg != DW_REG_MIPS_SP && oldReg != DW_REG_MIPS_FP) {
+        reason1 = "rule for DW_REG_CFA: invalid |oldReg|";
+        goto cant_summarise;
+      }
+      mCurrRules.mCfaExpr = LExpr(how, oldReg, offset);
+      break;
+
+   case DW_REG_MIPS_SP: case DW_REG_MIPS_FP: case DW_REG_MIPS_PC: {
+      // This is a new rule for SP, FP or PC (the return address).
+      switch (how) {
+        case NODEREF: case DEREF:
+          // Check the old register is one we're tracking.
+          if (!registerIsTracked((DW_REG_NUMBER)oldReg) &&
+              oldReg != DW_REG_CFA) {
+            reason1 = "rule for SP/FP/PC: uses untracked reg";
+            goto cant_summarise;
+          }
+          break;
+        case PFXEXPR: {
+          // Check that the prefix expression only mentions tracked registers.
+          const vector<PfxInstr>* pfxInstrs = mSecMap->GetPfxInstrs();
+          reason2 = checkPfxExpr(pfxInstrs, offset);
+          if (reason2) {
+            reason1 = "rule for SP/FP/PC: ";
+            goto cant_summarise;
+          }
+          break;
+        }
+        default:
+          goto cant_summarise;
+      }
+      LExpr expr = LExpr(how, oldReg, offset);
+      switch (aNewReg) {
+        case DW_REG_MIPS_FP: mCurrRules.mFPexpr = expr; break;
+        case DW_REG_MIPS_SP: mCurrRules.mSPexpr = expr; break;
+        case DW_REG_MIPS_PC: mCurrRules.mPCexpr = expr; break;
+        default: MOZ_CRASH("impossible value for aNewReg");
+      }
+      break;
+    }
+    default:
+      // Leave |reason1| and |reason2| unset here, for the reasons
+      // explained in the analogous point in the ARM case just above.
+      goto cant_summarise;
+  }
+
+  // On MIPS, it seems the old SP value before the call is always the
+  // same as the CFA.  Therefore, in the absence of any other way to
+  // recover the SP, specify that the CFA should be copied.
+  if (mCurrRules.mSPexpr.mHow == UNKNOWN) {
+    mCurrRules.mSPexpr = LExpr(NODEREF, DW_REG_CFA, 0);
+  }
+
+  // Also, gcc says "Undef" for FP when it is unchanged.
+  if (mCurrRules.mFPexpr.mHow == UNKNOWN) {
+    mCurrRules.mFPexpr = LExpr(NODEREF, DW_REG_MIPS_FP, 0);
+  }
+
 #else
 
 # error "Unsupported arch"
 #endif
 
   return;
 
  cant_summarise:
--- a/tools/profiler/lul/LulElf.cpp
+++ b/tools/profiler/lul/LulElf.cpp
@@ -159,16 +159,19 @@ bool DwarfCFIRegisterNames(const typenam
       *num_dw_regnames = DwarfCFIToModule::RegisterNames::I386();
       return true;
     case EM_ARM:
       *num_dw_regnames = DwarfCFIToModule::RegisterNames::ARM();
       return true;
     case EM_X86_64:
       *num_dw_regnames = DwarfCFIToModule::RegisterNames::X86_64();
       return true;
+    case EM_MIPS:
+      *num_dw_regnames = DwarfCFIToModule::RegisterNames::MIPS();
+      return true;
     default:
       MOZ_ASSERT(0);
       return false;
   }
 }
 
 template<typename ElfClass>
 bool LoadDwarfCFI(const string& dwarf_filename,
--- a/tools/profiler/lul/LulMain.cpp
+++ b/tools/profiler/lul/LulMain.cpp
@@ -68,16 +68,20 @@ NameOf_DW_REG(int16_t aReg)
     case DW_REG_INTEL_XIP: return "xip";
 #elif defined(GP_ARCH_arm)
     case DW_REG_ARM_R7:    return "r7";
     case DW_REG_ARM_R11:   return "r11";
     case DW_REG_ARM_R12:   return "r12";
     case DW_REG_ARM_R13:   return "r13";
     case DW_REG_ARM_R14:   return "r14";
     case DW_REG_ARM_R15:   return "r15";
+#elif defined(GP_ARCH_mips64)
+    case DW_REG_MIPS_SP:   return "sp";
+    case DW_REG_MIPS_FP:   return "fp";
+    case DW_REG_MIPS_PC:   return "pc";
 #else
 # error "Unsupported arch"
 #endif
     default: return "???";
   }
 }
 
 string
@@ -127,16 +131,20 @@ RuleSet::Print(void(*aLog)(const char*))
   res += mXbpExpr.ShowRule(" BP");
 #elif defined(GP_ARCH_arm)
   res += mR15expr.ShowRule(" R15");
   res += mR7expr .ShowRule(" R7" );
   res += mR11expr.ShowRule(" R11");
   res += mR12expr.ShowRule(" R12");
   res += mR13expr.ShowRule(" R13");
   res += mR14expr.ShowRule(" R14");
+#elif defined(GP_ARCH_mips64)
+  res += mPCexpr.ShowRule(" PC");
+  res += mSPexpr.ShowRule(" SP");
+  res += mFPexpr.ShowRule(" FP");
 #else
 # error "Unsupported arch"
 #endif
   aLog(res.c_str());
 }
 
 LExpr*
 RuleSet::ExprForRegno(DW_REG_NUMBER aRegno) {
@@ -148,16 +156,20 @@ RuleSet::ExprForRegno(DW_REG_NUMBER aReg
     case DW_REG_INTEL_XBP: return &mXbpExpr;
 #   elif defined(GP_ARCH_arm)
     case DW_REG_ARM_R15:   return &mR15expr;
     case DW_REG_ARM_R14:   return &mR14expr;
     case DW_REG_ARM_R13:   return &mR13expr;
     case DW_REG_ARM_R12:   return &mR12expr;
     case DW_REG_ARM_R11:   return &mR11expr;
     case DW_REG_ARM_R7:    return &mR7expr;
+#elif defined(GP_ARCH_mips64)
+    case DW_REG_MIPS_SP:    return &mSPexpr;
+    case DW_REG_MIPS_FP:    return &mFPexpr;
+    case DW_REG_MIPS_PC:    return &mPCexpr;
 #   else
 #     error "Unknown arch"
 #   endif
     default: return nullptr;
   }
 }
 
 RuleSet::RuleSet()
@@ -897,16 +909,20 @@ TaggedUWord EvaluateReg(int16_t aReg, co
     case DW_REG_INTEL_XIP: return aOldRegs->xip;
 #elif defined(GP_ARCH_arm)
     case DW_REG_ARM_R7:    return aOldRegs->r7;
     case DW_REG_ARM_R11:   return aOldRegs->r11;
     case DW_REG_ARM_R12:   return aOldRegs->r12;
     case DW_REG_ARM_R13:   return aOldRegs->r13;
     case DW_REG_ARM_R14:   return aOldRegs->r14;
     case DW_REG_ARM_R15:   return aOldRegs->r15;
+#elif defined(GP_ARCH_mips64)
+    case DW_REG_MIPS_SP:   return aOldRegs->sp;
+    case DW_REG_MIPS_FP:   return aOldRegs->fp;
+    case DW_REG_MIPS_PC:   return aOldRegs->pc;
 #else
 # error "Unsupported arch"
 #endif
     default: MOZ_ASSERT(0); return TaggedUWord();
   }
 }
 
 // RUNS IN NO-MALLOC CONTEXT
@@ -1089,16 +1105,20 @@ void UseRuleSet(/*MOD*/UnwindRegs* aRegs
   aRegs->xip = TaggedUWord();
 #elif defined(GP_ARCH_arm)
   aRegs->r7  = TaggedUWord();
   aRegs->r11 = TaggedUWord();
   aRegs->r12 = TaggedUWord();
   aRegs->r13 = TaggedUWord();
   aRegs->r14 = TaggedUWord();
   aRegs->r15 = TaggedUWord();
+#elif defined(GP_ARCH_mips64)
+  aRegs->sp  = TaggedUWord();
+  aRegs->fp  = TaggedUWord();
+  aRegs->pc  = TaggedUWord();
 #else
 #  error "Unsupported arch"
 #endif
 
   // This is generally useful.
   const TaggedUWord inval = TaggedUWord();
 
   // First, compute the CFA.
@@ -1126,16 +1146,23 @@ void UseRuleSet(/*MOD*/UnwindRegs* aRegs
   aRegs->r12
     = aRS->mR12expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
   aRegs->r13
     = aRS->mR13expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
   aRegs->r14
     = aRS->mR14expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
   aRegs->r15
     = aRS->mR15expr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
+#elif defined(GP_ARCH_mips64)
+  aRegs->sp
+    = aRS->mSPexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
+  aRegs->fp
+    = aRS->mFPexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
+  aRegs->pc
+    = aRS->mPCexpr.EvaluateExpr(&old_regs, cfa, aStackImg, aPfxInstrs);
 #else
 # error "Unsupported arch"
 #endif
 
   // We're done.  Any regs for which we didn't manage to compute a
   // new value will now be marked as invalid.
 }
 
@@ -1178,27 +1205,38 @@ LUL::Unwind(/*OUT*/uintptr_t* aFramePCs,
                      (int)regs.r15.Valid(), (unsigned long long int)regs.r15.Value(),
                      (int)regs.r7.Valid(),  (unsigned long long int)regs.r7.Value(),
                      (int)regs.r11.Valid(), (unsigned long long int)regs.r11.Value(),
                      (int)regs.r12.Valid(), (unsigned long long int)regs.r12.Value(),
                      (int)regs.r13.Valid(), (unsigned long long int)regs.r13.Value(),
                      (int)regs.r14.Valid(), (unsigned long long int)regs.r14.Value());
       buf[sizeof(buf)-1] = 0;
       mLog(buf);
+#elif defined(GP_ARCH_mips64)
+      SprintfLiteral(buf,
+                     "LoopTop: pc %d/%llx  sp %d/%llx  fp %d/%llx\n",
+                     (int)regs.pc.Valid(), (unsigned long long int)regs.pc.Value(),
+                     (int)regs.sp.Valid(), (unsigned long long int)regs.sp.Value(),
+                     (int)regs.fp.Valid(), (unsigned long long int)regs.fp.Value());
+      buf[sizeof(buf)-1] = 0;
+      mLog(buf);
 #else
 # error "Unsupported arch"
 #endif
     }
 
 #if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
     TaggedUWord ia = regs.xip;
     TaggedUWord sp = regs.xsp;
 #elif defined(GP_ARCH_arm)
     TaggedUWord ia = (*aFramesUsed == 0 ? regs.r15 : regs.r14);
     TaggedUWord sp = regs.r13;
+#elif defined(GP_ARCH_mips64)
+    TaggedUWord ia = regs.pc;
+    TaggedUWord sp = regs.sp;
 #else
 # error "Unsupported arch"
 #endif
 
     if (*aFramesUsed >= aFramesAvail) {
       break;
     }
 
@@ -1405,16 +1443,25 @@ LUL::Unwind(/*OUT*/uintptr_t* aFramePCs,
 
 
 ////////////////////////////////////////////////////////////////
 // LUL Unit Testing                                           //
 ////////////////////////////////////////////////////////////////
 
 static const int LUL_UNIT_TEST_STACK_SIZE = 16384;
 
+#if defined(GP_ARCH_mips64)
+static __attribute__((noinline))
+unsigned long __getpc(void) {
+    unsigned long rtaddr;
+    __asm__ volatile ("move %0, $31" : "=r"(rtaddr));
+    return rtaddr;
+}
+#endif
+
 // This function is innermost in the test call sequence.  It uses LUL
 // to unwind, and compares the result with the sequence specified in
 // the director string.  These need to agree in order for the test to
 // pass.  In order not to screw up the results, this function needs
 // to have a not-very big stack frame, since we're only presenting
 // the innermost LUL_UNIT_TEST_STACK_SIZE bytes of stack to LUL, and
 // that chunk unavoidably includes the frame for this function.
 //
@@ -1475,16 +1522,32 @@ bool GetAndCheckStackTrace(LUL* aLUL, co
   startRegs.r15 = TaggedUWord(block[0]);
   startRegs.r14 = TaggedUWord(block[1]);
   startRegs.r13 = TaggedUWord(block[2]);
   startRegs.r12 = TaggedUWord(block[3]);
   startRegs.r11 = TaggedUWord(block[4]);
   startRegs.r7  = TaggedUWord(block[5]);
   const uintptr_t REDZONE_SIZE = 0;
   uintptr_t start = block[1] - REDZONE_SIZE;
+#elif defined(GP_ARCH_mips64)
+  volatile uintptr_t block[3];
+  MOZ_ASSERT(sizeof(block) == 24);
+  __asm__ __volatile__(
+    "sd $29, 8(%0)     \n"
+    "sd $30, 16(%0)    \n"
+    :
+    :"r"(block)
+    :"memory"
+  );
+  block[0] = __getpc();
+  startRegs.pc = TaggedUWord(block[0]);
+  startRegs.sp = TaggedUWord(block[1]);
+  startRegs.fp = TaggedUWord(block[2]);
+  const uintptr_t REDZONE_SIZE = 0;
+  uintptr_t start = block[1] - REDZONE_SIZE;
 #else
 # error "Unsupported platform"
 #endif
 
   // Get hold of the innermost LUL_UNIT_TEST_STACK_SIZE bytes of the
   // stack.
   uintptr_t end = start + LUL_UNIT_TEST_STACK_SIZE;
   uintptr_t ws  = sizeof(void*);
--- a/tools/profiler/lul/LulMain.h
+++ b/tools/profiler/lul/LulMain.h
@@ -147,16 +147,20 @@ struct UnwindRegs {
   TaggedUWord r12;
   TaggedUWord r13;
   TaggedUWord r14;
   TaggedUWord r15;
 #elif defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
   TaggedUWord xbp;
   TaggedUWord xsp;
   TaggedUWord xip;
+#elif defined(GP_ARCH_mips64)
+  TaggedUWord sp;
+  TaggedUWord fp;
+  TaggedUWord pc;
 #else
 # error "Unknown plat"
 #endif
 };
 
 
 // The maximum number of bytes in a stack snapshot.  This value can be increased
 // if necessary, but testing showed that 160k is enough to obtain good
--- a/tools/profiler/lul/LulMainInt.h
+++ b/tools/profiler/lul/LulMainInt.h
@@ -48,16 +48,20 @@ enum DW_REG_NUMBER {
   // combined, a merged set of register constants is needed.
   DW_REG_INTEL_XBP = 6,
   DW_REG_INTEL_XSP = 7,
   DW_REG_INTEL_XIP = 16,
 #elif defined(GP_ARCH_x86)
   DW_REG_INTEL_XBP = 5,
   DW_REG_INTEL_XSP = 4,
   DW_REG_INTEL_XIP = 8,
+#elif defined(GP_ARCH_mips64)
+  DW_REG_MIPS_SP = 29,
+  DW_REG_MIPS_FP = 30,
+  DW_REG_MIPS_PC = 34,
 #else
 # error "Unknown arch"
 #endif
 };
 
 
 ////////////////////////////////////////////////////////////////
 // PfxExpr                                                    //
@@ -270,32 +274,39 @@ public:
   LExpr  mXbpExpr;
 #elif defined(GP_ARCH_arm)
   LExpr  mR15expr; // return address
   LExpr  mR14expr;
   LExpr  mR13expr;
   LExpr  mR12expr;
   LExpr  mR11expr;
   LExpr  mR7expr;
+#elif defined(GP_ARCH_mips64)
+  LExpr  mPCexpr;
+  LExpr  mFPexpr;
+  LExpr  mSPexpr;
 #else
 #   error "Unknown arch"
 #endif
 };
 
 // Returns |true| for Dwarf register numbers which are members
 // of the set of registers that LUL unwinds on this target.
 static inline bool registerIsTracked(DW_REG_NUMBER reg) {
   switch (reg) {
 #   if defined(GP_ARCH_amd64) || defined(GP_ARCH_x86)
     case DW_REG_INTEL_XBP: case DW_REG_INTEL_XSP: case DW_REG_INTEL_XIP:
       return true;
 #   elif defined(GP_ARCH_arm)
     case DW_REG_ARM_R7:  case DW_REG_ARM_R11: case DW_REG_ARM_R12:
     case DW_REG_ARM_R13: case DW_REG_ARM_R14: case DW_REG_ARM_R15:
       return true;
+#elif defined(GP_ARCH_mips64)
+    case DW_REG_MIPS_FP:  case DW_REG_MIPS_SP: case DW_REG_MIPS_PC:
+      return true;
 #   else
 #     error "Unknown arch"
 #   endif
     default:
       return false;
   }
 }
 
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -42,17 +42,17 @@ if CONFIG['MOZ_GECKO_PROFILER']:
             'gecko/nsProfiler.cpp',
         ]
     else:
         UNIFIED_SOURCES += [
             'gecko/nsProfiler.cpp',
         ]
 
     if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
-        if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64'):
+        if CONFIG['CPU_ARCH'] in ('arm', 'x86', 'x86_64', 'mips64'):
             UNIFIED_SOURCES += [
                 'lul/AutoObjectMapper.cpp',
                 'lul/LulCommon.cpp',
                 'lul/LulDwarf.cpp',
                 'lul/LulDwarfSummariser.cpp',
                 'lul/LulElf.cpp',
                 'lul/LulMain.cpp',
                 'lul/platform-linux-lul.cpp',