Skip to content

Wrapped TF C APIs as runtime functions, so that IRGen can generate calls to them #19555

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 26, 2018

Conversation

mhong
Copy link

@mhong mhong commented Sep 26, 2018

This patch "dissolved" the call to the packaged experimental C API TF_RunConstOp() into a set of finer-grained C API calls.

An alternative to the runtime function approach is to use Clang importer to call
the C APIs. Clang importer allows the C APIs to become available as SIL
functions with the same names, and thus can be found and called in IRGen via
silModule.findFunction() (see
https://github.com/apple/swift/blob/ad7def2c6bffd95d62e5e665c9faed0f8dac49f5/lib/IRGen/IRGenSIL.cpp#L1956-L1957). The concerns with this approach are:

  1. There are no precedents in IRGen that calls C APIs via this clang importer
    route. Having IRGen depend on clang importer may not be desirable.

  2. Some C APIs cannot be found via clang importer (e.g. TF_NewStatus() can be
    found, but TFE_NewOp() cannot). As a work-around, these C APIs must be called in
    the user module code first, before IRGen over the user module can locate them as
    SIL functions. This feels brittle.

  3. When passing objects between compiler runtime entry points (swift functions)
    such as @_silgen_name("_swift_tfc_GetGlobalEagerContext") and the C APIs, IRGen
    have to issue bitcast to convert between a real struct type (e.g. TFE_Context*)
    and void* (i8* in LLVM type) as used in the entry points.

In contrast, the wrapped runtime functions consistently use void* in their
interfaces, so that IRGen need not do such bitcasts.

One downside with the runtime function approach is that we need to wrap ~20 TF C
APIs in the new files TensorFlow.{h,cpp}. This is however mostly a one-time eng cost.

…lls to

them.

This patch "disolved" the call to the packaged experimental C API
TF_RunConstOp() into a set of finer-grained C API calls.

An alternative to the runtime function approach is to use Clang importer to call
the C APIs. Clang importer allows the C APIs to become available as SIL
functions with the same names, and thus can be found and called in IRGen via
silModule.findFunction() (see
https://github.com/apple/swift/blob/ad7def2c6bffd95d62e5e665c9faed0f8dac49f5/lib/IRGen/IRGenSIL.cpp#L1956-L1957). The
concerns with this approach are:

1. There are no precedents in IRGen that calls C APIs via this clang importer
route. Having IRGen depend on clang importer may not be desirable.

2. Some C APIs cannot be found via clang importer (e.g. TF_NewStatus() can be
found, but TFE_NewOp() cannot). As a work-around, these C APIs must be called in
the user module code first, before IRGen over the user module can locate them as
SIL functions. This feels brittle.

3. When passing objects between compiler runtime entry points (swift functions)
such as @_silgen_name("_swift_tfc_GetGlobalEagerContext") and the C APIs, IRGen
have to issue bitcast to convert between a real struct type (e.g. TFE_Context*)
and void* (i8* in LLVM type) as used in the entry points.

In contrast, the wrapped runtime functions consistently use void* in their
interfaces, so that IRGen need not do such bitcasts.

One downside with the runtime function approach is that we need to wrap ~20 TF C
APIs in the new files TensorFlow.{h,cpp}. This is however mostly a one-time eng cost.
@mhong
Copy link
Author

mhong commented Sep 26, 2018

@swift-ci please test tensorflow

auto *TFNewStatusFn = IGM.getTF_NewStatusFn();
auto status = Builder.CreateCall(TFNewStatusFn, {});

if (opInfo.getOperationName() != "Const") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Special casing the Const op is just a temporary thing for your incremental development, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup! There was a TODO above.

auto opNameValAddr = llvm::ConstantExpr::getInBoundsGetElementPtr(
global->getValueType(), global, indices);
return opNameValAddr;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic all LGTM,

//===----------------------------------------------------------------------===//

#include "swift/Runtime/Config.h"
#include <stdint.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<cstdint> please.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

ARGS(Int32Ty),
ATTRS(NoUnwind))

FUNCTION(TF_DeleteTensor, swift_tfc_TF_DeleteTensor, C_CC,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why do you need these wrappers? Why can't you just declare TF_DeleteTensor here with the void* flavored signature? It should all work out due to the behavior of getRuntimeFunction calling LLVM's getOrCreateFunction API that returns the function or does a bitcast if an incompatible signature is already present.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Things like _swift_tfc_CheckOk that are not trivial wrappers definitely will require entries here, I'm just asking about the trivial wrappers like the TF_DeleteTensor case.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try this and report back.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I'm able to remove the decls and impls in TensorFlow.{h,cpp}. The entries in RuntimeFunctions.def remain, so that we can call them in IRGen (e.g. via IGM.getTF_DeleteTensorFn()). Thanks!

IGM.getAddrOfSILFunction(getContextSilFn, NotForDefinition);
assert(getContextFn);
llvm::Function *getContextFn =
findFunction("_swift_tfc_GetGlobalEagerContext", silModule);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to move this one to runtimefunctions as well?

Copy link
Author

@mhong mhong Sep 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can I change those swift functions to be callable as runtime functions (e.g. i need to access swift objects like _ExecutionContext.global.eagerContext somehow)?

If we can do that, we may not need #19556, which would be nice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just make sure they have a convention(c) calling convention and public linkage and you can call them as C functions!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple change:
Replace @_silgen_name with @_cdecl and you'll get C-convention functions.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Converted some entry points to @_cdecl, and added runtime function entries for them.

For the remaining ones, I'll convert them once we use the new AnyTensorHandle.

Copy link
Contributor

@lattner lattner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thank you for going down this path. I agree it is a pain given the number of runtime functions we have to deal with, but I think it is a good thing to align with the existing design of IRGen rather than paving new roads.

1. Removed runtime func impls, when we can us the C API ones.
2. Changed some compiler RT entry points to using @_cdecl, so that they can be
called in IRGen directly (instead of going through clang importer and silgen names).
@mhong
Copy link
Author

mhong commented Sep 26, 2018

@swift-ci please clean test tensorflow

@@ -1183,3 +1178,8 @@ public func _CreateTensorHandleFromCTensorHandle(
return TensorHandle<Float>(owning: ownedCHandle)
}

@inlinable
@_cdecl("_swift_tfc_CheckOk")
public func _CheckOk(_ s: CTFStatus) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All our previous existing exported runtime functions have prefix “_TFC” in the Swift name. These should be updated for consistency. For example, this function should be “_TFCCheckOK”.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


@inlinable
@_silgen_name("_swift_tfc_GetGlobalEagerContext")
@_cdecl("_swift_tfc_GetGlobalEagerContext")
public func _GetGlobalEagerContext() -> CTFEContext {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think these have to be public. @usableFromInline should work fine. Have you tried it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this works for the @_cdecl functions. Changed.

return fn;
}

void checkOk(llvm::Value *status) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckOk is a really generic name, but it’s tied to TFC. After prefixes for runtime functions are added, this function should be renamed accordingly to prevent confusion with the rest of the IRGen code base.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the TFC prefix.

// SWIFT_ENABLE_TENSORFLOW
// Returns the LLVM function with `funcName`. It must exist.
llvm::Function *findFunction(StringRef funcName, SILModule &silModule) {
LLVM_DEBUG(llvm::dbgs() << "IRGen for " << funcName << "().\n");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This debug comment is confusing. It looks like it’s print the status that the compiler is running IRGen on a function, but it is not. It’s looking up a function.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to "IRGen for calling ". I expect findFunction() to go away once we have AnyTensorHandle, since all compiler runtime entry points can be defined as C functions at that time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great

@mhong
Copy link
Author

mhong commented Sep 26, 2018

@swift-ci please clean test tensorflow

Mingsheng Hong added 2 commits September 26, 2018 10:22
swift/stdlib/public/runtime/CMakeLists.txt, because CI
run (e.g. https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-Linux/859/console)
reported:
```
CMake Error at stdlib/public/runtime/CMakeLists.txt:30 (include_directories):
  include_directories given empty-string as include directory.
```
@mhong
Copy link
Author

mhong commented Sep 26, 2018

@swift-ci please clean test tensorflow

// - Naming convention:
// - TF_XXX functions correspond to TF C APIs
// - TFE_XXX functions correspond to TF eager C APIs
// - TFC_XXX functions are C functions defined in the compiler runtime
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

@@ -27,6 +27,7 @@ list(APPEND swift_runtime_compile_flags
"-D__SWIFT_CURRENT_DYLIB=swiftCore")

# SWIFT_ENABLE_TENSORFLOW
find_package(TensorFlow REQUIRED)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I moved code in the new files TensorFlow.{h,cpp} to the CTensorFlow
module (ctensorflow_init.{h,cpp}), so that the stdlib/public/runtime/ need not
depend on the TF library. As such I reverted the changes to the CMakefile here.

More context for the original plan:
find_package() uses the swift/cmake/modules/FindTensorFlow.cmake script to define the TF_INCLUDE_DIR variable, which the next line uses, so that the new runtime source file swift/stdlib/public/runtime/TensorFlow.cpp can find header files like "tensorflow/c/c_api.h".

https://cmake.org/cmake/help/v3.0/command/find_package.html has more context.

Copy link
Contributor

@lattner lattner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patch looks great, thanks!

@@ -70,7 +70,7 @@ func checkOk(_ s: CTFStatus?, file: StaticString = #file, line: UInt = #line) {
typealias CTFSession = OpaquePointer

/// The `TF_Status *` type.
typealias CTFStatus = OpaquePointer
public typealias CTFStatus = OpaquePointer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since _TFCCheckOk is @usableFromInline now, this typealias can also be @usableFromInline.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

 module (ctensorflow_init.{h,cpp}), so that the stdlib/public/runtime/ need not
 depend on the TF library.

Also addresed more feedback.
@mhong
Copy link
Author

mhong commented Sep 26, 2018

@swift-ci please clean test tensorflow

@mhong
Copy link
Author

mhong commented Sep 26, 2018

Linux tests passed:
https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-Linux/861/console
https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-Linux-gpu/113/

Mac test hit a network error (https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-macOS/90/console):

> git fetch --tags --progress https://github.com/apple/swift.git +refs/pull/*:refs/remotes/origin/pr/*
ERROR: Error fetching remote repo 'origin'

I'll retry it.

@mhong
Copy link
Author

mhong commented Sep 26, 2018

@swift-ci Please test tensorflow macOS

Copy link
Contributor

@bgogul bgogul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@mhong
Copy link
Author

mhong commented Sep 26, 2018

Mac test finished: https://ci-external.swift.org/view/Pull%20Request/job/swift-PR-TensorFlow-macOS/92/

Thanks all for the review!

@mhong mhong merged commit 1c5ea9d into swiftlang:tensorflow Sep 26, 2018
@mhong mhong deleted the irgen_4_runtime_funcs branch September 26, 2018 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants