@@ -345,128 +345,40 @@ With the background above, here is how the addon maps onto the concepts.
345345``` text
346346Python / C++ API LLVM ORC v2 concept
347347─────────────────────────────────────────────────────────────────
348+ default_session() shared (leaked) LLJIT + ExecutionSession
348349ExecutionSession LLJIT + ExecutionSession
349- DynamicLibrary JITDylib
350- dylib.add("foo.o") ObjectLinkingLayer.add(buffer)
351- dylib.get_function("add") ExecutionSession.lookup("__tvm_ffi_add")
352- dylib.set_link_order([a, b]) JITDylib.setLinkOrder([a, b, main, ...])
350+ session.load_module(objs) createJITDylib + addObjectFile(s) + wire imports
351+ (returned) Module JITDylib, wrapped as a tvm_ffi.Module
352+ module.get_function("add") ExecutionSession.lookup("__tvm_ffi_add")
353353```
354354
355- ### 4.2 Object File Loading Pipeline
356-
357- ``` text
358- dylib.add("foo.o")
359- │
360- ▼ ORCJITDynamicLibraryObj::AddObjectFile()
361- │ jit_->addObjectFile(*dylib_, MemoryBuffer)
362- │
363- ▼ ObjectTransformLayer (macOS: skipped; Linux/Win: may strip .pdata)
364- │ Windows: strip .pdata/.xdata avoids JITLink COMDAT limitation
365- │ fix __ImageBase fixes Pointer32NB relocations
366- │
367- ▼ ObjectLinkingLayer (JITLink)
368- │ Parse object → LinkGraph
369- │ Run InitFiniPlugin passes:
370- │ PrePrunePasses: mark .init_array / .ctors blocks as live
371- │ PostAllocationPasses: (Windows) set __ImageBase, strip SEH sections
372- │ PostFixupPasses: extract resolved init/fini function pointers
373- │ → session.AddPendingInitializer(dylib, entry)
374- │ Resolve relocations via ExecutionSession.lookup()
375- │ Allocate JIT memory, write code, mark executable
376- │
377- ▼ JITDylib symbol table updated
378- (symbols are defined but constructors not yet run)
379- ```
380-
381- ### 4.3 Symbol Lookup and Initialization
382-
383- ``` text
384- dylib.get_function("add")
385- │
386- ▼ ORCJITDynamicLibraryObj::GetFunction("add")
387- │ → GetSymbol("__tvm_ffi_add")
388- │ build JITDylibSearchOrder: [this, linked dylibs, LLJIT default]
389- │ jit_->getExecutionSession().lookup(order, "__tvm_ffi_add")
390- │ ← ExecutorAddr
391- │
392- ▼ Linux/Windows: RunPendingInitializers()
393- │ Sort entries by priority
394- │ Call each function pointer (C++ ctors, .init_array entries)
395- │
396- ▼ macOS: jit_->initialize(*dylib_)
397- │ ORC MachOPlatform calls __mod_init_func pointers
398- │
399- ▼ Wrap raw function pointer as tvm_ffi::Function
400- via Function::FromPacked lambda (marshals AnyView args ↔ TVMFFIAny)
401- ```
402-
403- ### 4.4 InitFiniPlugin Detail
404-
405- ` InitFiniPlugin ` is an ` ObjectLinkingLayer::Plugin ` — it receives callbacks during
406- JITLink's pass pipeline for every object being linked.
407-
408- ``` text
409- JITLink pass pipeline for each object file:
410- ─────────────────────────────────────────────────────────────
411- PrePrunePasses
412- → InitFiniPlugin::modifyPassConfig (PrePrunePasses)
413- For each section named .init_array* / .ctors* / .fini_array* / .dtors*
414- (ELF) or __DATA,__mod_init_func (Mach-O) or .CRT$XC* (COFF):
415- Mark all blocks in that section as "keep" (live)
416- → Prevents dead-code elimination from removing constructors
417-
418- PostAllocationPasses (Windows only)
419- → InitFiniPlugin::modifyPassConfig (PostAllocationPasses)
420- Set __ImageBase = lowest allocated block address
421- Strip .pdata / .xdata exception handler sections
422-
423- PostFixupPasses
424- → InitFiniPlugin::modifyPassConfig (PostFixupPasses)
425- Iterate all blocks in init/fini sections
426- Read each 8-byte slot as an ExecutorAddr
427- Parse section name to determine: priority, is_init vs is_fini
428- Call session->AddPendingInitializer(dylib, InitFiniEntry{addr, section, priority})
429- (or AddPendingDeinitializer for dtors/fini_array)
430- ```
431-
432- ### 4.5 Cross-Library Symbol Resolution
433-
434- ``` text
435- libA depends on a symbol defined in libB:
436-
437- libA.set_link_order([libB])
438- → JITDylibSearchOrder for libA: [libA, libB, main(ProcessSymbols)]
439-
440- When libA's object file has an unresolved symbol "foo":
441- JITLink asks ExecutionSession.lookup([libA, libB, main], "foo")
442- → found in libB → returns libB's ExecutorAddr for "foo"
443- → relocation in libA patched with that address
444- ```
445-
446- ### 4.6 Windows DLL Import Stubs
447-
448- Windows MSVC objects reference DLL functions through ` __imp_XXX ` pointer stubs (the
449- Import Address Table pattern). At static link time the linker creates these stubs. In
450- JIT mode there is no linker, so ` DLLImportDefinitionGenerator ` creates them on demand:
451-
452- ``` text
453- JITLink encounters undefined symbol "__imp_malloc"
454- │
455- ▼ DLLImportDefinitionGenerator::tryToGenerate()
456- │ Search ucrtbase.dll, msvcrt.dll, then all process modules
457- │ for the real address of "malloc"
458- │
459- ▼ Allocate two JIT-memory stubs:
460- │ __imp_malloc → 8-byte slot containing &malloc (host address)
461- │ malloc → x86_64 jmp [__imp_malloc] trampoline
462- │
463- ▼ Define both symbols in the JITDylib
464- → JITLink can now apply PCRel32 reloc to __imp_malloc (stub is close in JIT memory)
465- ```
466-
467- The stubs must live in JIT-allocated memory (not at the host process address) because
468- x86_64 ` PCRel32 ` relocations can only reach ±2 GB. The host's ` malloc ` may be farther
469- than 2 GB from the JIT allocation.
355+ ` session.load_module(objects) ` is the sole public loader: it creates one
356+ ` JITDylib ` , adds every object (path or in-memory bytes), injects context symbols
357+ eagerly, expands any embedded library binary into an import tree, and returns a
358+ plain ` tvm_ffi.Module ` . There is no incremental library API — a module is a
359+ unit: load all its objects at once, then look up functions on the result.
360+
361+ ### 4.2 Loading and lookup
362+
363+ ` load_module ` adds each object to the JITDylib, and JITLink parses it into a
364+ ` LinkGraph ` , resolves relocations, and allocates executable JIT memory.
365+ ` get_function ` looks the symbol up (materializing lazily), then wraps the raw
366+ pointer as a ` tvm_ffi::Function ` . Symbols resolve against the dylib's own
367+ default link order (this dylib → Platform → process/runtime symbols); objects
368+ that reference each other must be loaded together, since there is no linking
369+ between separate ` load_module ` results.
370+
371+ Two addon-specific pieces sit in this pipeline:
372+
373+ - ** ` InitFiniPlugin ` ** — a JITLink pass plugin that keeps init/fini sections
374+ (` .init_array ` /` .ctors ` /` .fini_array ` /` .dtors ` , ` __mod_init_func ` , ` .CRT$XC* ` )
375+ live, then collects their function pointers after fixup. The addon runs them
376+ in priority order at first lookup and at teardown, replacing the ORC
377+ platform's initializer machinery. See ` llvm_patches/init_fini_plugin.h ` .
378+ - ** Windows DLL import stubs** — ` DLLImportDefinitionGenerator ` resolves
379+ ` __imp_XXX ` references to host-DLL functions by emitting JIT-memory pointer +
380+ trampoline stubs, keeping ` PCRel32 ` fixups within ±2 GB of the JIT code. See
381+ ` llvm_patches/win_dll_import_generator.h ` .
470382
471383---
472384
@@ -475,22 +387,17 @@ than 2 GB from the JIT allocation.
475387``` python
476388import tvm_ffi_orcjit as oj
477389
478- # 1. Create an ExecutionSession (wraps LLJIT)
479- sess = oj.ExecutionSession()
480-
481- # 2. Create a JITDylib
482- lib = sess.create_dynamic_library()
483-
484- # 3. Load a compiled object file
485- # → object parsed, JITLink links it, InitFiniPlugin collects ctors
486- lib.add(" add.o" )
390+ # 1. Get the shared ExecutionSession (wraps LLJIT; created once per process)
391+ sess = oj.default_session()
487392
488- # 4. Look up a function
489- # → LLVM resolves "__tvm_ffi_add", RunPendingInitializers() fires ctors
490- add = lib.get_function(" add" ) # returns tvm_ffi.Function
393+ # 2. Load a compiled object file into a fresh JITDylib
394+ # → object parsed, JITLink links it, InitFiniPlugin collects ctors,
395+ # context symbols injected eagerly, embedded binary (if any) expanded
396+ mod = sess.load_module(" add.o" ) # returns a tvm_ffi.Module
491397
492- # 5. Call it
493- result = add(3 , 4 ) # → 7
398+ # 3. Look up and call a function
399+ # → LLVM resolves "__tvm_ffi_add"; pending constructors fire on first lookup
400+ result = mod.add(3 , 4 ) # → 7
494401```
495402
496403The corresponding C++ side of ` add.o ` :
@@ -511,7 +418,7 @@ TVM_FFI_DLL_EXPORT_TYPED_FUNC(add, add_impl);
511418
512419| Concept | What it is | Where in the addon |
513420| --- | --- | --- |
514- | Object file | Container of machine code, data, symbols, relocations | Input to `dylib.add ()` |
421+ | Object file | Container of machine code, data, symbols, relocations | Input to `session.load_module ()` |
515422| Relocation | Recipe to patch a code address at link/JIT time | Applied by JITLink |
516423| `.init_array` / `.ctors` | Array of C++ constructor pointers in ELF objects | Collected by `InitFiniPlugin` |
517424| `ExecutionSession` | Root of the ORC JIT environment | `ORCJITExecutionSessionObj` |
@@ -520,7 +427,7 @@ TVM_FFI_DLL_EXPORT_TYPED_FUNC(add, add_impl);
520427| `JITLink` | LLVM's JIT-aware linker | Used inside `ObjectLinkingLayer` |
521428| JITLink pass pipeline | Pre-prune → post-alloc → post-fixup hooks | Where `InitFiniPlugin` runs |
522429| `DefinitionGenerator` | Fallback symbol provider | `DLLImportDefinitionGenerator` (Win) |
523- | Link order | Search path across JITDylibs for symbol resolution | `SetLinkOrder()` |
430+ | Link order | Search path across JITDylibs for symbol resolution | LLJIT default (Main → Platform → ProcessSymbols) |
524431| `__tvm_ffi_` prefix | Namespace for TVM-FFI exported functions | Used in `GetFunction()` |
525432
526433---
0 commit comments