Assertion triggered when running simple hello-world code on iOS device using ORC/LLLazyJIT


I am trying to run this basic C++ hello-world code in my iOS app that has LLVM libraries linked in (the app runs on the actual device - iPad Pro, iOS 13.4.1).

int main (int argh, char *argv[]) {

std::cout << “Hello World!” << std::endl;
return 0;

So below is the break down of the steps that I do:
First I compile this code to an instance of llvm::Module by using the logic borrowed from the lli tool.

Once I have the Module instance I construct an instance of orc::LLLazyJIT (J), configure it (again closely following the logic in lli tool)
to which I then add the module like this:

// Add the main module.

ExitOnErr(J->addLazyIRModule(orc::ThreadSafeModule(std::move(MainModule), TSCtx)));

Finally the module is executed like this:

// Run main.

auto MainSym = ExitOnErr(J->lookup(“main”));

typedef int (*MainFnPtr)(int, char *[]);

auto Result = orc::runAsMain(

jitTargetAddressToFunction(MainSym.getAddress()), Args,


The Xcode halts the execution when an assertion is triggered in llvm::jitlink::Symbol::constructNamedDef (the full call stack is below).

The line that triggers the assertion is this:

assert(Offset < Base.getSize() && “Symbol offset is outside block”);

because both Offset and Base.getSize() evaluate to 0).

The data referred to by the Base block is “Hello World!”.

I don’t understand why this assertion happens. Should the Base block size be > 0 ?

I am relatively new to LLVM, I did read the documentation on OCR Design and Implementation - but still can’t figure out what’s going on.

If there is any additional documentation I can read on running code using ORC/JIT APIs that would shed more light on the internals/implementation?

Any help would be greatly appreciated.

Thank you.

+Lang for ORC stuff

Hi Igor,

Thanks for the walkthrough! This sounds like it might be a JITLink MachO parser bug. Are you able to share the module that causes the failure? It may also be interesting to run your tool with ‘-debug-only=jitlink’ and capture the output.


Hi Igor,

The -debug-only option should be provided to your tool’s main function, rather than to the JIT’d main. As long as you’re calling:

cl::ParseCommandLineOptions(argc, argv, “”);

in your tool’s code that should handle the ‘-debug-only’ option.


Hi Lang,

Please see below is the trace.

Hi Lang,

Just wondering if you had a chance to look at the trace I sent you last Saturday.

Hi Igor,

Thank you for that debugging output, and my apologies for the delayed reply – I’m afraid this got buried. I see from the output that the text section contains a single block of zero size. I have not encountered this before in my test cases, but it is perfectly legal – I see no problem with the input object itself. It tripped that assertion in JITLink because the assertion required symbols to start before the end of the block, which is impossible in an empty block. I’ve relaxed that assertion in 22d7a01dd72 to require that symbols end on or before the end of their blocks. This allows for the possibility of zero-sized symbols at the end of blocks (and at the start of empty blocks).

I believe this should fix your issue, but if it does not please let me know and we can dig in further.


Thanks so much Lang.
I will give it a try and let you know!

FWIW - found out LLVM does produce zero length sections if you use
function sections (or have an inline function) for functions like

int f1() { } // at -O0 that'll include a trap instruction, above that
it'll be completely empty/zero-length
void f2() { __builtin_unreachable(); } // similar I Think - though
maybe it's zero length even at -O0 (llvm_unreachable then adds the
-O0-checking behavior on top, perhaps)

Hi Dave,

Yep. This is JITLink specific, so we could only have observed it on MachO x86-64 or arm64 until recently. It takes a little bit of poking to get IR to produce a zero-lengh section on MachO, but not much.

Jared Wyles recently contributed an initial JITLink ELF implementation, so the fix seems timely – we might have been about to see more of it.

– Lang.

Hi Dave,

Yep. This is JITLink specific, so we could only have observed it on MachO x86-64 or arm64 until recently.

Ah, fair enough -presumably if you had one of those empty functions as
the only function in a module you'd get this, then? (& ORC likes small
modules, so maybe not too much of a stretch to get such a situation)

It takes a little bit of poking to get IR to produce a zero-lengh section on MachO, but not much.

Out of curiosity - what'd it take?

Hi Dave,

It takes a little bit of poking to get IR to produce a zero-lengh section on MachO, but not much.
Out of curiosity - what’d it take?

I’m not sure – I never actually tried to reproduce from the IR. I’m just inferring that it’s simple from the debugging output that Igor attached – the failing graph only includes a 13-byte constant string and a couple of labels.

Creating normalized sections…
__text: 0x0000000000000000 – 0x0000000000000000, align: 1, index: 0
__const: 0x0000000000000000 – 0x000000000000000d, align: 1, index: 1
Creating normalized symbols…
ltmp0: value = 0x0000000000000000, type = 0x0e, desc = 0x0000, sect = 0
ltmp1: value = 0x0000000000000000, type = 0x0e, desc = 0x0000, sect = 1
___orc_lcl…str.0: value = 0x0000000000000000, type = 0x1f, desc = 0x0000, sect = 1

I’ve noticed that the MachO output streamer likes to put ltmpN labels at the start of sections (which usually guarantees that relocations will be symbol-based rather than anonymous*), so if anything tickles the streamer in a way that causes it to output a .text directive then you’ll also end up with a label. If the section ends up being empty then you’ll trigger the assertion. With that in mind I just wrote an assembly test case manually.

– Lang.