This is an archive of the discontinued LLVM Phabricator instance.

[ORC][ORC_RT] Integrate ORC platforms with LLJIT and lli
ClosedPublic

Authored by housel on May 26 2022, 12:02 PM.

Details

Summary

This patch enables integrating orc::LLJIT with the ORCv2 platforms (MachOPlatform and ELFNixPlatform) and the compiler-rt orc runtime. Changes include:

  • Adding SPS wrapper functions for the orc runtime's dlfcn emulation functions, allowing initialization and deinitialization to be invoked by LLJIT
  • Changing the LLJIT code generation default to add UseInitArray so that .init_array constructors are generated for ELF platforms
  • Integrating the ORCv2 Platforms into lli, and adding a PlatformSupport implementation to the LLJIT instance used by lli which implements initialization and deinitialization by calling the new wrapper functions in the runtime.

Open issues:

  • Should the ORCPlatformSupport class included in lli be moved into LLJIT?
  • The integration test for Darwin explicitly sets -relocation-model=pic when invoking lli so that a __DATA,__mod_init_func section is generated; if this is not done the default code generation options use the static relocation mode, which causes a __TEXT, __constructor to be generated, and the runtime does not deal with these. Might there be a better way to handle this?

Diff Detail

Event Timeline

housel created this revision.May 26 2022, 12:02 PM
Herald added a project: Restricted Project. · View Herald TranscriptMay 26 2022, 12:02 PM
housel requested review of this revision.May 26 2022, 12:02 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptMay 26 2022, 12:02 PM
Herald added subscribers: llvm-commits, Restricted Project. · View Herald Transcript
housel updated this revision to Diff 433649.Jun 1 2022, 9:31 PM
housel edited the summary of this revision. (Show Details)

Added an integration test to ensure exception frame registration works for JITed code.

lhames accepted this revision.Jun 9 2022, 3:03 PM

Is there a reason that the eh-frame tests are C++, rather than IR?

Otherwise LGTM -- very cool!

llvm/tools/lli/lli.cpp
380–425

As a side note to this review: We'll want to re-think LLJIT::PlatformSupport when we finally get Windows support in JITLink and the ORC runtime: Generic initialize and deinitialize functions should be implemented in the ORC runtime, and LLJIT::initialize and LLJIT::deinitialize should just call those functions through EPC::callWrapper -- PlatformSupport should vanish entirely.

On MachO and ELF the runtime operations can be implemented in terms of dlopen / dlclose, and on Windows I guess they can be implemented in terms of LoadLibrary / FreeLibrary.

The only wrinkle is GenericIRPlatform, which is supposed to work without the ORC runtime. For this I think we can just inject some IR wrapper-plumbing like this to direct the calls to methods on the GenericIRPlatform instance:

; To be set up as an absolute symbol pointing at the current GenericIRPlatform instance.
@GenericIRPlatformInstance = external global i8*, align 8

; extern "C" CWrapperFunctionResult initializeImplWrapper(const char *Data, size_t Size, void *Instance);
declare { i8*, i64 } @initializeImplWrapper(i8*, i64, i8*)

; extern "C" CWrapperFunctionResult deinitializeImplWrapper(const char *Data, size_t Size, void *Instance);
declare { i8*, i64 } @deinitializeImplWrapper(i8*, i64, i8*)

; extern "C" CWrapperFunctionResult initialize(const char *Data, size_t Size) {
;   return initializeImplWrapper(Data, Size, GenericIRPlatformInstance);
; }
define { i8*, i64 } @initialize(i8* %0, i64 %1) local_unnamed_addr #0 {
  %3 = load i8*, i8** @GenericIRPlatformInstance, align 8, !tbaa !6
  %4 = tail call { i8*, i64 } @initializeImplWrapper(i8* %0, i64 %1, i8* %3)
  ret { i8*, i64 } %4
}

; extern "C" CWrapperFunctionResult deinitialize(const char *Data, size_t Size) {
;   return deinitializeImplWrapper(Data, Size, GenericIRPlatformInstance);
; }
define { i8*, i64 } @deinitialize(i8* %0, i64 %1) {
  %3 = load i8*, i8** @GenericIRPlatformInstance, align 8, !tbaa !6
  %4 = tail call { i8*, i64 } @deinitializeImplWrapper(i8* %0, i64 %1, i8* %3)
  ret { i8*, i64 } %4
}

These wrappers can call through to static methods on the GenericIRPlatform class, which unwrap and call methods on the instance:

class GenericIRPlatform ... {
  // GenericIRPlatform installs init-function transform, as it does now.
  ...
private:

  Error initialize(StringRef Path) { /* run recorded init functions */ }
  Error deinitialize(StringRef Path) { /* run recorded deinit functions */ }

  static CWrapperFunctionResult initializeImplWrapper(const char *Data, size_t Size, void *Instance) {
    return return WrapperFunction<SPSError(SPSString)>::handle(
             ArgData, ArgSize,
             [Instance](StringRef Path) -> Error {
               return static_cast<GenericIRPlatform*>(Instance)->initialize(Path);
             })
      .release();
  }

  static CWrapperFunctionResult initializeImplWrapper(const char *Data, size_t Size, void *Instance) {
    return return WrapperFunction<SPSError(SPSString)>::handle(
             ArgData, ArgSize,
             [Instance](StringRef Path) -> Error {
               return static_cast<GenericIRPlatform*>(Instance)->deinitialize(Path);
             })
      .release();
  }
};

That should be enough to support the initialize and deinitialize ops without the ORC runtime, which I think keeps us on a par with MCJIT's functionality.

This revision is now accepted and ready to land.Jun 9 2022, 3:03 PM
This revision was automatically updated to reflect the committed changes.