Merge kotlin/native history into kotlin

There is some preparation commits in kotlin-native branch before merge
and they shouldn't be in kotlin-native master branch
This commit is contained in:
Stanislav Erokhin
2020-10-27 21:06:02 +03:00
2830 changed files with 268253 additions and 0 deletions
+194
View File
@@ -0,0 +1,194 @@
# Also see codestyle/cpp/README.md
# For documentation on options see: https://releases.llvm.org/8.0.0/tools/clang/docs/ClangFormatStyleOptions.html
# "Kt N/A" means that this C/C++/ObjC construct have no corresponding Kotlin construct to refer to
# "Kt U" means that formatting is unspecified in Kotlin formatting guide
# "Kt <url>" means that this is specified in Kotlin formatting guide with url pointing to it
---
DisableFormat: false
Standard: Cpp11
# Kt N/A. Different from 0 to make modifiers stand out. An alternative is -2, but it introduces another level of indentation.
AccessModifierOffset: -4
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting
AlignAfterOpenBracket: AlwaysBreak
# Kt U. Not touching
AlignConsecutiveAssignments: false
# Kt U. Not touching
AlignConsecutiveDeclarations: false
# Kt N/A. Not touching
AlignEscapedNewlines: DontAlign
# Kt U. Not touching
AlignOperands: false
# Kt U. Not touching
AlignTrailingComments: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#function-formatting
AllowAllParametersOfDeclarationOnNextLine: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
AllowShortBlocksOnASingleLine: false
# Kt U. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
# Using false, because case statements usually contain at least 2 statements: doing something + break, which makes them multiline
AllowShortCaseLabelsOnASingleLine: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#function-formatting Inline is somewhat close to "single expression" functions
AllowShortFunctionsOnASingleLine: Inline
# Kt U. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#using-conditional-statements however it mostly refers to the ternary operator.
AllowShortIfStatementsOnASingleLine: true
# Kt U. Same as previous
AllowShortLoopsOnASingleLine: true
# Kt N/A. In Kotlin return type is in a trailing position.
AlwaysBreakAfterReturnType: None
# Kt U. Not touching.
AlwaysBreakBeforeMultilineStrings: false
# Kt N/A. In Kotlin type parameters are declared inline in a much more compact way.
# Using Yes to make it easy to detect function name
AlwaysBreakTemplateDeclarations: Yes
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting
BinPackArguments: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#function-formatting
BinPackParameters: false
BraceWrapping:
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterClass: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterControlStatement: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterEnum: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterFunction: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterNamespace: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterObjCDeclaration: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterStruct: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterUnion: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterExternBlock: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
BeforeCatch: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
BeforeElse: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
IndentBraces: false
# Only used when AfterFunction is true
SplitEmptyFunction: true
# Only used when AfterRecord is true
SplitEmptyRecord: true
# Only used if AfterNamespace is true
SplitEmptyNamespace: true
# Kt U. Break after operators
BreakBeforeBinaryOperators: None
# Configured by BraceWrapping
BreakBeforeBraces: Custom
# Kt U. Using true because it looks more like an if-expression with "then" and "else" branches marked "?" and ":"
BreakBeforeTernaryOperators: true
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting AfterColon looks the most consistent
BreakConstructorInitializers: AfterColon
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting
BreakInheritanceList: AfterColon
# Kt U. Choosing to break long strings to fit into line length
BreakStringLiterals: true
# Kt U. IDEA displays a vertical guide at 120. Choosing it to avoid very long lines.
ColumnLimit: 140
# Kt N/A. Probably should use nested namespaces instead.
CompactNamespaces: false
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
ConstructorInitializerIndentWidth: 4
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting IDEA seems to indent continuations with 8
ContinuationIndentWidth: 8
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
# Braced initalizer braces are close to parens in function call or to brackets in array initialier. And styleguide asks not to put spaces in them.
Cpp11BracedListStyle: true
# Configured by PointerAlignment
DerivePointerAlignment: false
# Confgured by BinPack*
ExperimentalAutoDetectBinPacking: false
# Kt N/A. Setting to true because it helps to see when anonymous namespace ends
FixNamespaceComments: true
# Kt U. Not touching manually created including blocks
IncludeBlocks: Preserve
# Kt U. Sorting: main header (priority 0) > system headers > project headers
IncludeCategories:
- Regex: '^<.*'
Priority: 1
- Regex: '.*'
Priority: 2
# Kt N/A. Main header must match current filename (modulo extension) exactly.
IncludeIsMainRegex: '$'
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
IndentCaseLabels: true
# Kt N/A. Do not indent macro code
IndentPPDirectives: None
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
IndentWidth: 4
# Kt U. IDEA seems to indent
IndentWrappedFunctionNames: true
# Kt U. Do not touch.
KeepEmptyLinesAtTheStartOfBlocks: false
# Kt U. IDEA keeps 1 empty line by default.
MaxEmptyLinesToKeep: 1
# Kt N/A.
NamespaceIndentation: None
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting
ObjCBinPackProtocolList: Never
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
ObjCBlockIndentWidth: 4
# Kt N/A. Following Google and LLVM styleguides.
ObjCSpaceAfterProperty: false
# Kt N/A. The closest is class inheritance list. Following Google and LLVM styleguides.
ObjCSpaceBeforeProtocolList: true
# Kt N/A.
PointerAlignment: Left
# Kt U. Reflow comments to fit into line width
ReflowComments: true
# Kt U. IDEA does not sort by default, but allows this option. Do not touch.
SortIncludes: false
# Kt U. Like SortIncludes.
SortUsingDeclarations: false
# Kt N/A.
SpaceAfterCStyleCast: false
# Kt N/A. IDEA puts space in `fun <T>`
SpaceAfterTemplateKeyword: true
# Kt U. https://kotlinlang.org/docs/reference/coding-conventions.html puts spaces
SpaceBeforeAssignmentOperators: true
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace which does not put space before parens and brackets.
SpaceBeforeCpp11BracedList: false
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#colon
SpaceBeforeCtorInitializerColon: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#colon
SpaceBeforeInheritanceColon: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpaceBeforeParens: ControlStatements
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpaceBeforeRangeBasedForLoopColon: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpaceInEmptyParentheses: false
# Kt U. https://kotlinlang.org/docs/reference/coding-conventions.html uses 1
SpacesBeforeTrailingComments: 1
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInAngles: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInContainerLiterals: false
# Kt N/A.
SpacesInCStyleCastParentheses: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInParentheses: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInSquareBrackets: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
TabWidth: 4
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
UseTab: Never
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
...
+74
View File
@@ -0,0 +1,74 @@
.DS_Store
.idea/shelf
*.iml
/dependencies/all
dist
kotlin-native-*.tar.gz
kotlin-native-*.zip
translator/src/test/kotlin/tests/*/linked
out
tmp
workspace.xml
*.versionsBackup
local.properties
.gradle
build
translator/.gradle/2.9/taskArtifacts
kotstd/kotstd.iml
# test suit products.
*.bc
*.bc.o
*.kt.S
*.kt.exe
*.log
test.output
*.kexe
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# local project files
lib/
.idea/
proto/compiler/protoc-artifacts
proto/compiler/tests
proto/compiler/google/src/google/protobuf/compiler/kotlin/bin
proto/compiler/google/src/google/protobuf/compiler/kotlin/protoc
peformance/build
# translator auto generated artifacts
kotstd/ll
# test teamcity property: commitable only with -f
backend.native/tests/teamcity-test.property
# Sample output
samples/**/*.kt.bc-build
samples/androidNativeActivity/Polyhedron
# CMake
llvmCoverageMappingC/CMakeLists.txt
tools/performance-server/node_modules
tools/performance-server/server
tools/performance-server/ui/js
runtime/cmake-build-debug/
# compilation database
compile_commands.json
# clangd caches
.clangd/
# googletest framework used by runtime tests
runtime/googletest/
+65
View File
@@ -0,0 +1,65 @@
# Building Apple LLVM for Kotlin/Native
This document describes how to compile LLVM distribution and use it to build Kotlin/Native on macOS.
Usually, you don't need to compile LLVM by yourself:
* If you use Kotlin/Native compiler it will download LLVM on the first run.
* If you contribute to kotlin-native repository then use `:dependencies:update` Gradle task.
But if you don't want to download prebuilt LLVM or want to experiment with your own distribution,
you came to the right place.
## Part 1. Building the right LLVM version for macOS.
For macOS host we use LLVM from [Apple downstream](https://github.com/apple/llvm-project).
Branch is [**apple/stable/20190104**](https://github.com/apple/llvm-project/tree/apple/stable/20190104)
because it is similar (or even the same) to what Apple ships with Xcode 11.*.
After cloning the repo and changing the branch we perform the following steps to build LLVM toolchain:
```bash
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS="clang;lld;libcxx;libcxxabi" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_ASSERTIONS=Off \
-G Ninja \
-DCMAKE_INSTALL_PREFIX=clang-llvm-apple-8.0.0-darwin-macos \
../llvm
ninja install
```
After these steps `clang-llvm-apple-8.0.0-darwin-macos` directory will contain LLVM distribution that is suitable for building Kotlin/Native.
## Part 2. Building Kotlin/Native against given LLVM distribution.
By default, Kotlin/Native will try to download LLVM distribution from CDN if it is not present in `$HOME/.konan/dependencies` folder.
There are two ways to bypass this behaviour.
#### Option A. Substitute prebuilt distribution.
This option doesn't require you to edit compiler sources, but a bit harder.
The compiler checks dependency presence by reading contents of `$HOME/.konan/dependencies/.extracted` file.
So to avoid LLVM downloading, we should manually add a record to the `.extracted` file:
1. Create `$HOME/.konan/dependencies/.extracted` file if it is not created.
2. Add `clang-llvm-apple-8.0.0-darwin-macos` line.
and put `clang-llvm-apple-8.0.0-darwin-macos` directory from the Part 1 to `$HOME/.konan/dependencies/`.
#### Option B. Provide an absolute path to the distribution.
This option requires user to edit [konan.properties file](konan/konan.properties).
Set `llvmHome.<HOST_NAME>` to an absolute path to your LLVM distribution and
set `llvmVersion.<HOST_NAME>` to its version.
For example, provide a path to `clang-llvm-apple-8.0.0-darwin-macos` from the Part 1 and set version to 8.0.0.
Now we are ready to build Kotlin/Native itself. The process is described in [README.md](README.md).
Please note that we still need to run `./gradlew dependencies:update` to download other dependencies (e.g. libffi).
## Q&A
— Can I override `.konan` location?
— Yes, by setting `$KONAN_DATA_DIR` environment variable. See [HACKING.md](HACKING.md#compiler-environment-variables).
- Can I use another LLVM distribution without rebuilding Kotlin/Native?
- Yes, see [HACKING.md](HACKING.md#using-different-llvm-distributions-as-part-of-kotlinnative-compilation-pipeline).
+295
View File
@@ -0,0 +1,295 @@
# 1.4.10 (Sep 2020)
* Fixed a newline handling in @Deprecated annotation in ObjCExport ([KT-39206](https://youtrack.jetbrains.com/issue/KT-39206))
* Fixed suspend function types in ObjCExport ([KT-40976](https://youtrack.jetbrains.com/issue/KT-40976))
* Fixed support for unsupported C declarations in cinterop ([KT-39762](https://youtrack.jetbrains.com/issue/KT-39762))
# v1.4.0 (Aug 2020)
* Objective-C/Swift interop:
* Reworked exception handling ([GH-3822](https://github.com/JetBrains/kotlin-native/pull/3822), [GH-3842](https://github.com/JetBrains/kotlin-native/pull/3842))
* Enabled support for Objective-C generics by default ([GH-3778](https://github.com/JetBrains/kotlin-native/pull/3778))
* Support for Kotlins suspending functions ([GH-3915](https://github.com/JetBrains/kotlin-native/pull/3915))
* Handle variadic block types in ObjC interop ([`KT-36766`](https://youtrack.jetbrains.com/issue/KT-36766))
* Added native-specific frontend checkers (implemented in the main Kotlin repository: [GH-3293](https://github.com/JetBrains/kotlin/pull/3293), [GH-3091](https://github.com/JetBrains/kotlin/pull/3091), [GH-3172](https://github.com/JetBrains/kotlin/pull/3172))
* .dSYMs for release binaries on Apple platforms ([GH-4085](https://github.com/JetBrains/kotlin-native/pull/4085))
* Improved compilation time of builds with interop libraries by reworking cinterop under the hood.
* Experimental mimalloc allocator support (-Xallocator=mimalloc) to improve execution time performance. ([GH-3704](https://github.com/JetBrains/kotlin-native/pull/3704))
* Tune GC to improve execution time performance
* Various fixes to compiler caches and Gradle daemon usage
# v1.3.72 (April 2020)
* Fix ios_x64 platform libs cache for iOS 11 and 12 (GH-4071)
# v1.3.71 (March 2020)
* Fix `lazy {}` memory leak regression ([`KT-37232`](https://youtrack.jetbrains.com/issue/KT-37232), GH-3944)
* Fix using cached Kotlin subclasses of Objective-C classes (GH-3986)
# v1.3.70 (Dec 2019)
* Support compiler caches for debug mode (GH-3650)
* Support running Kotlin/Native compiler from Gradle daemon (GH-3442)
* Support multiple independent Kotlin frameworks in the same application (GH-3457)
* Compile-time allocation for some singleton objects (GH-3645)
* Native support for SIMD vector types in compiler and interop (GH-3498)
* API for runtime detector of cyclic garbage (GH-3616)
* Commonized StringBuilder (GH-3593) and Float.rangeTo (KT-35299)
* Fix interop with localized strings (GH-3562)
* Provide utility for user-side generation of platform libraries (GH-3538)
* On-stack allocation using local escape analysis (GH-3625)
* Code coverage support on Linux and Windows (GH-3403)
* Debugging experience improvements (GH-3561, GH-3638, GH-3606)
# v1.3.60 (Oct 2019)
* Support XCode 11
* Switch to LLVM 8.0
* New compiler targets:
* watchOS targets, watchos_x86, watchos_arm64 and watchos_arm32 (GH-3323, GH-3404, GH-3344)
* tvOS targets tvos_x64 and tvos_arm64 (GH-3303, GH-3363)
* native Android targets android_x86 and android_x64 (GH-3306, GH-3314)
* Standard CLI library kotlinx.cli is shipped with the compiler distribution (GH-3215)
* Improved debug information for inline functions (KT-28929, GH-3292)
* Improved runtime performance of interface calls, up to 5x faster (GH-3377)
* Improved runtime performance of type checks, up to 50x faster (GH-3291)
* Produce native binaries directly from klibs, speeds up large project compilation (GH-3246)
* Supported arbitrary (up to 255 inclusive) function arity (GH-3253)
* Supported callable references to suspend functions (GH-3197)
* Implemented experimental -Xg0 switch, symbolication of release binaries for iOS (GH-3233, GH-3367)
* Interop:
* Allow passing untyped null as variadic function's parameter (GH-3312, KT-33525)
* Standard library:
* Allow scheduling jobs in arbitrary K/N context, not only Worker (GH-3316)
* Important bug fixes:
* Boxed negative values can lead to crashes on ios_arm64 (GH-3296)
* Implemented thread-safe tracking of Objective-C references to Kotlin objects (GH-3267)
# v1.3.50 (Aug 2019)
* Kotlin/Native versioning now aligned with Kotlin versioning
* Exhaustive platform libraries on macOS (GH-3141)
* Update to Gradle 5.5 (GH-3166)
* Improved debug information correctness (GH-3130)
* Major memory manager refactoring (GH-3129)
* Embed actual bitcode in produced frameworks (GH-2974)
* Compilation speed improvements
* Interop:
* Support kotlin.Deprecated when producing framework (GH-3114)
* Ensure produced Objective-C header does not have warnings (GH-3101)
* Speed up interop stub generator (GH-3082, GH-3050)
* getOriginalKotlinClass() to get KClass for Kotlin classes in Objective-C (GH-3036)
* Supported nullable primitive types in reverse C interop (GH-3198)
* Standard library
* API for delayed job execution on worker (GH-2971)
* API for running via worker's job queue (GH-3078)
* MonoClock and Duration support (GH-3028)
* Support typeOf (KT-29917, KT-28625)
* New zero-terminated utf8 to String conversion API (GH-3116)
* Optimize StringBuilder for certain cases (GH-3202)
* Implemented Array.fill API (GH-3244)
# v1.3.0 (Jun 2019)
* CoreLocation platform library on macOS (GH-3041)
* Converting Unit type to Void during producing framework for Objective-C/Swift (GH-2549, GH-1271)
* Support linux/arm64 targets (GH-2917)
* Performance improvements of memory manager (GH-2813)
* FreezableAtomicReference prototype (GH-2776)
* Logging and error messages enhancements
* Interop:
* Support nullable String return type in reverse C interop (GH-2956)
* Support setting custom exception hook in reverse C interop (GH-2941)
* Experimental generics support for produced frameworks for Objective-C/Swift implemented by Kevin Galligan (GH-2850)
* Improve support for Objective-C methods clashing with methods of Any (GH-2914)
* Support variadic Objective-C functions (GH-2896)
# v1.2.1 (Apr 2019)
* Fix Objective-C interop with React (GH-2872)
* Fix “not in vtable” compiler crash when generating frameworks (GH-2865)
* Implement some optimizations (GH-2854)
* Fix release build for 32-bit Windows (GH-2848)
* Fix casts to type parameters with multiple bounds (GH-2888)
* Fix “could not get descriptor uniq id for deserialized class FlagEnum” compiler crash when generating framework (GH-2874)
# v1.2.0 (Apr 2019)
* New intermediate representation based library format allowing global optimizations
* Exception backtraces in debug mode on macOS and iOS targets contains symbolic information
* Support for 32-bit Windows targets (target mingw_x86)
* Support for cross-compilation to Linux (x86-64 and arm32) from macOS and Windows hosts
* Static Apple frameworks can be produced
* Support Gradle 5.1
* Fix alignment-related issues on ARM32 and MIPS platforms
* Write unhandled exceptions stacktrace on device to iOS crash log
* Fix undefined behavior in some arithmetic operations
* Interop:
* Get rid of libffi dependency
* Support returning struct from C callbacks
* Support passing Kotlin strings to C interop functions accepting UTF-32 arguments
* Fix bool conversion
* Support variable length arrays
* Provide Kotlin access to C compiler intrinsics via platform.builtins package
* Support clang modules (for Objective-C only)
* Experimental integration with CocoaPods
* IDE
* Kotlin/Native plugin is supported in CLion 2018.3 and AppCode/CLion 2019.1
* Basic highlighting support for .def files
* Navigation to source files from exception backtrace
## v1.1.0 (Dec 2018)
* Performance optimizations:
* runtime: optimization of queue of finalization
* compiler: loop generation optimization
* compiler: reduce RTTI size
* runtime: reduce size of the object header
* Contracts support
* Regex engine: fix quantifier processing
## v0.9.3 (Sep 2018)
* Bugfixes
## v0.9.2 (Sep 2018)
* Support Xcode 10.0
* iOS 9.0 is the minimal supported version for all targets
* Swift interop improvements
* Support shared top level values of some immutable types (i.e. String and atomic references)
* Support release Kotlin 1.3.0
## v0.9.1 (Sep 2018)
* Improve naming in produced Objective-C frameworks. Use Kotlin prefix instead of Stdlib prefix.
* Improvements in KLIB: Library versioning, IDEA-friendly internal format.
# v0.9 (Sep 2018)
* Support Kotlin 1.3M2
* Note: Common modules of multiplatform projects also should use Kotlin 1.3
* Major standard library (native parts) rework and rename
* New Gradle plugin with multiplatform integration and reworked DSL
* Support unsigned types in Kotlin and interop
* Support non-experimental coroutines API (kotlin.coroutines)
* Top level object var/val can only be accessed from the main thread
* Support lazy properties in singleton objects
* Update LLVM to 6.0.1
## v0.8 (Jul 2018)
* Singleton objects are frozen after creation, and shared between threads
* String and primitives types are frozen by default
* Common stdlib with Kotlin/JVM and Kotlin/JS
* Implemented `kotlin.random.*` and `Collection.shuffle`
* Implemented atomic integers and atomic references
* Multiple bugfixes in compiler (coroutines, inliner)
* Support 32-bit iOS (target `ios_arm32`)
* New experimental Gradle plugin
* Support Xcode 9.4.1
* Optimizations (switch by enum, memory management)
## v0.7.1 (Jun 2018)
* Bugfixes in the runtime (indexOf, GC for kotlin.Array, enum equality) and the compiler
* Fix NSBlock problem, preventing upload of binaries to the AppStore
* Create primitive type boxes and kotlin.String as frozen by default
* Support Gradle 4.7, provide separate run task for each executable
* Support Xcode 9.4 and CoreML and ClassKit frameworks on Apple platforms
* Improved runtime Kotlin variable examination
* Minor performance optimizations in compiled code and runtime
* Add `disableDesignatedInitializerChecks` definition file support
## v0.7 (May 2018)
* Interop with Objective-C/Swift changes:
* Uniform direct and reverse interops (values could be passed in both directions now)
* Interop by exceptions
* Type conversion and checks (`as`, `is`) for interop types
* Seamless interop on numbers, strings, lists, maps and sets
* Better interop on constructors and initializers
* Switched to Xcode 9.3 on Apple platforms
* Introduced object freezing API, frozen object could be used from multiple threads
* Kotlin enums are frozen by default
* Switch to Gradle 4.6
* Use Gradle native dependency model, allowing to use `.klib` as Maven artifacts
* Introduced typed arrays API
* Introduced weak references API
* Activated global devirtualization analysis
* Performance improvements (box caching, bridge inlining, others)
## v0.6.2 (Mar 2018)
* Support several `expectedBy`-dependencies in Gradle plugin.
* Improved interaction between Gradle plugin and IDE.
* Various bugfixes
## v0.6.1 (Mar 2018)
* Various bugfixes
* Support total ordering in FP comparisons
* Interop generates string constants from string macrodefinitions
* STM32 blinky demo in pure Kotlin/Native
* Top level variables initialization redesign (proper dependency order)
* Support kotlin.math on WebAssembly targets
* Support embedded targets on Windows hosts
## v0.6 (Feb 2018)
* Support multiplatform projects (expect/actual) in compiler and Gradle plugin
* Support first embedded target (STM32 board)
* Support Kotlin 1.2.20
* Support Java 9
* Support Gradle 4.5
* Transparent Objective-C/Kotlin container classes interoperability
* Produce optimized WebAssembly binaries (10x smaller than it used to be)
* Improved APIs for object transfer between threads and workers
* Allow exporting top level C function in reverse interop with @CName annotation
* Supported debugging of code with inline functions
* Multiple bugfixes and performance optimizations
## v0.5 (Dec 2017)
* Reverse interop allowing to call Kotlin/Native code compiled as framework from Objective-C/Swift programs
* Reverse interop allowing to call Kotlin/Native code compiled as shared object from C/C++ programs
* Support generation of shared objects and DLLs by the compiler
* Migration to LLVM 5.0
* Support WebAssembly target on Linux and Windows hosts
* Make string conversions more robust
* Support kotlin.math package
* Refine workers and string conversion APIs
## v0.4 (Nov 2017) ##
* Objective-C frameworks interop for iOS and macOS targets
* Platform API libraries for Linux, iOS, macOS and Windows
* Kotlin 1.2 supported
* `val` and function parameters can be inspected in debugger
* Experimental support for WebAssembly (wasm32 target)
* Linux MIPS support (little and big endian, mips and mipsel targets)
* Gradle plugin DSL fully reworked
* Support for unit testing annotations and automatic test runner generation
* Final executable size reduced
* Various interop improvements (forward declaration, better handling of unsupported types)
* Workers object subgraph transfer checks implemented
* Optimized low level memory management using more efficient cycle tracing algorithm
## v0.3.4 (Oct 2017) ##
* Intermediate release
## v0.3.2 (Sep 2017) ##
* Bug fixes
## v0.3.1 (Aug 2017) ##
* Improvements in C interop tools (function pointers, bitfields, bugfixes)
* Improvements to Gradle plugin and dependency downloader
* Support for immutable data linked into an executable via ImmutableDataBlob class
* Kotlin 1.1.4 supported
* Basic variable inspection support in the debugger
* Some performance improvements ("for" loops, memory management)
* .klib improvements (keep options from .def file, faster inline handling)
* experimental workers API added (see [`sample`](https://github.com/JetBrains/kotlin-native/blob/master/samples/workers))
## v0.3 (Jun 2017) ##
* Preliminary support for x86-64 Windows hosts and targets
* Support for producing native activities on 32- and 64-bit Android targets
* Extended standard library (bitsets, character classification, regular expression)
* Preliminary support for Kotlin/Native library format (.klib)
* Preliminary source-level debugging support (stepping only, no variable inspection)
* Compiler switch `-entry` to select entry point
* Symbolic backtrace in runtime for unstripped binaries, for all supported targets
## v0.2 (May 2017) ##
* Added support for coroutines
* Fixed most stdlib incompatibilities
* Improved memory management performance
* Cross-module inline function support
* Unicode support independent from installed system locales
* Interoperability improvements
* file-based filtering in definition file
* stateless lambdas could be used as C callbacks
* any Unicode string could be passed to C function
* Very basic debugging support
* Improve compilation and linking performance
## v0.1 (Mar 2017) ##
Initial technical preview of Kotlin/Native
+310
View File
@@ -0,0 +1,310 @@
# CocoaPods integration
Kotlin/Native provides integration with the [CocoaPods dependency manager](https://cocoapods.org/). You can add
dependencies on Pod libraries stored in the CocoaPods repository or locally as well as use a multiplatform project with
native targets as a CocoaPods dependency (Kotlin Pod).
You can manage Pod dependencies directly in IntelliJ IDEA and enjoy all the additional features such as code highlighting
and completion. You can build the whole Kotlin project with Gradle and not ever have to switch to Xcode.
Use Xcode only when you need to write Swift/Objective-C code or run your application on a simulator or device.
Depending on your project and purposes, you can add dependencies between:
* [A Kotlin project and a Pod library from the CocoaPods repository](#add-a-dependency-on-a-pod-library-from-the-cocoapods-repository)
* [A Kotlin project and a Pod library stored locally](#add-a-dependency-on-a-pod-library-stored-locally)
* [A Kotlin Pod and an Xcode project with one target](#add-a-dependency-between-a-kotlin-pod-and-xcode-project-with-one-target)
or [several targets](#add-a-dependency-between-a-kotlin-pod-with-an-xcode-project-with-several-targets)
>You can also add dependencies between a Kotlin Pod and multiple Xcode projects. However, in this case you need to add a
>dependency by calling `pod install` manually for each Xcode project. In other cases, it's done automatically.
{:.note}
## Install the CocoaPods dependency manager and plugin
1. Install the [CocoaPods dependency manager](https://cocoapods.org/).
<div class="sample" markdown="1" theme="idea" mode="ruby" data-highlight-only>
```ruby
$ sudo gem install cocoapods
```
</div>
2. Install the [`cocoapods-generate`](https://github.com/square/cocoapods-generate) plugin.
<div class="sample" markdown="1" theme="idea" mode="ruby" data-highlight-only>
```ruby
$ sudo gem install cocoapods-generate
```
</div>
3. In `build.gradle.kts` (or `build.gradle`) of your IDEA project, apply the CocoaPods plugin as well as the Kotlin
Multiplatform plugin.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
plugins {
kotlin("multiplatform") version "{{ site.data.releases.latest.version }}"
kotlin("native.cocoapods") version "{{ site.data.releases.latest.version }}"
}
```
</div>
4. Configure `summary`, `homepage`, and `frameworkName`of the `Podspec` file in the `cocoapods` block.
`version` is a version of the Gradle project.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
plugins {
kotlin("multiplatform") version "{{ site.data.releases.latest.version }}"
kotlin("native.cocoapods") version "{{ site.data.releases.latest.version }}"
}
// CocoaPods requires the podspec to have a version.
version = "1.0"
kotlin {
cocoapods {
// Configure fields required by CocoaPods.
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
// You can change the name of the produced framework.
// By default, it is the name of the Gradle project.
frameworkName = "my_framework"
}
}
```
</div>
5. Re-import the project.
When applied, the CocoaPods plugin does the following:
* Adds both `debug` and `release` frameworks as output binaries for all macOS, iOS, tvOS, and watchOS targets.
* Creates a `podspec` task which generates a [Podspec](https://guides.cocoapods.org/syntax/podspec.html)
file for the project.
The `Podspec` file includes a path to an output framework and script phases that automate building this framework during
the build process of an Xcode project.
## Add dependencies on Pod libraries
You can add dependencies between a Kotlin project and Pod libraries [stored in the CocoaPods repository](#add-a-dependency-on-a-pod-library-from-the-cocoapods-repository)
and [stored locally](#add-a-dependency-on-a-pod-library-stored-locally).
[Complete the initial configuration](#install-the-cocoapods-dependency-manager-and-plugin), and when you add a new
dependency and re-import the project in IntelliJ IDEA; the new dependency will be added automatically. There are no
additional steps required.
### Add a dependency on a Pod library from the CocoaPods repository
1. Add dependencies on a Pod library that you want to use from the CocoaPods repository with `pod()` to `build.gradle.kts`
(`build.gradle`) of your project.
> You can also add dependencies on subspecs.
{:.note} >
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
kotlin {
ios()
cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
pod("AFNetworking", "~> 4.0.0")
pod("SDWebImage/MapKit")
}
}
```
</div>
2. Re-import the project.
To use these dependencies from the Kotlin code, import the packages `cocoapods.<library-name>`.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
import cocoapods.AFNetworking.*
import cocoapods.SDWebImage.*
```
</div>
You can find a sample project [here](https://github.com/Kotlin/kotlin-with-cocoapods-sample).
### Add a dependency on a Pod library stored locally
1. Add a dependency on a Pod library stored locally with `pod()` to `build.gradle.kts` (`build.gradle`) of your
project.
As the third parameter, specify the path to `Podspec` of the local Pod using `project.file(..)`.
> You can add local dependencies on subspecs as well.
> The `cocoapods` block can include dependencies to Pods stored locally and Pods from the CocoaPods repository at
> the same time.
{:.note}
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
kotlin {
ios()
cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
pod("pod_dependency", "1.0", project.file("../pod_dependency/pod_dependency.podspec"))
pod("subspec_dependency/Core", "1.0", project.file("../subspec_dependency/subspec_dependency.podspec"))
pod("AFNetworking", "~> 4.0.0")
pod("SDWebImage/MapKit")
}
}
```
</div>
2. Re-import the project.
If you want to use dependencies on local pods from Kotlin code, import the corresponding packages.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
import cocoapods.pod_dependency.*
import cocoapods.subspec_dependency.*
```
</div>
You can find a sample project [here](https://github.com/Kotlin/kotlin-with-cocoapods-sample).
## Use a Kotlin Gradle project as a CocoaPods dependency
You can use a Kotlin Multiplatform project with native targets as a CocoaPods dependency (Kotlin Pod). You can include such a dependency
in the Podfile of the Xcode project by its name and path to the project directory containing the generated Podspec.
This dependency will be automatically built (and rebuilt) along with this project.
Such an approach simplifies importing to Xcode by removing a need to write the corresponding Gradle tasks and Xcode build steps manually.
You can add dependencies between:
* [A Kotlin Pod and an Xcode project with one target](#add-a-dependency-between-a-kotlin-pod-and-xcode-project-with-one-target)
* [A Kotlin Pod and an Xcode project with several targets](#add-a-dependency-between-a-kotlin-pod-with-an-xcode-project-with-several-targets)
> To correctly import the dependencies into the Kotlin/Native module, the
`Podfile` must contain either [`use_modular_headers!`](https://guides.cocoapods.org/syntax/podfile.html#use_modular_headers_bang)
or [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang)
directive.
{:.note}
### Add a dependency between a Kotlin Pod and Xcode project with one target
1. Create an Xcode project with a `Podfile` if you havent done so yet.
2. Add the path to your Xcode project `Podfile` with `podfile = project.file(..)` to `build.gradle.kts` (`build.gradle`)
of your Kotlin project.
This step helps synchronize your Xcode project with Kotlin Pod dependencies by calling `pod install` for your `Podfile`.
3. Specify the minimum target version for the Pod library.
> If you don't specify the minimum target version and a dependency Pod requires a higher deployment target, you may get an error.
{:.note}
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
kotlin {
ios()
cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
ios.deploymentTarget = "13.5"
pod("AFNetworking", "~> 4.0.0")
podfile = project.file("../ios-app/Podfile")
}
}
```
</div>
4. Add the name and path of the Kotlin Pod you want to include in the Xcode project to `Podfile`.
<div class="sample" markdown="1" theme="idea" mode="ruby" data-highlight-only>
```ruby
use_frameworks!
platform :ios, '9.0'
target 'ios-app' do
pod 'kotlin_library', :path => '../kotlin-library'
end
```
</div>
5. Re-import the project.
### Add a dependency between a Kotlin Pod with an Xcode project with several targets
1. Create an Xcode project with a `Podfile` if you havent done so yet.
2. Add the path to your Xcode project `Podfile` with `podfile = project.file(..)` to `build.gradle.kts` (`build.gradle`) of
your Kotlin project.
This step helps synchronize your Xcode project with Kotlin Pod dependencies by calling `pod install` for your `Podfile`.
3. Add dependencies to the Pod libraries that you want to use in your project with `pod()`.
4. For each target, specify the minimum target version for the Pod library.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
kotlin {
ios()
tvos()
cocoapods {
summary = "CocoaPods test library"
homepage = "https://github.com/JetBrains/kotlin"
ios.deploymentTarget = "13.5"
tvos.deploymentTarget = "13.4"
pod("AFNetworking", "~> 4.0.0")
podfile = project.file("../severalTargetsXcodeProject/Podfile") // specify the path to Podfile
}
}
```
</div>
5. Add the name and path of the Kotlin Pod you want to include in the Xcode project to the `Podfile`.
<div class="sample" markdown="1" theme="idea" mode="ruby" data-highlight-only>
```ruby
target 'iosApp' do
use_frameworks!
platform :ios, '13.5'
# Pods for iosApp
pod 'kotlin_library', :path => '../kotlin-library'
end
target 'TVosApp' do
use_frameworks!
platform :tvos, '13.4'
# Pods for TVosApp
pod 'kotlin_library', :path => '../kotlin-library'
end
```
</div>
6. Re-import the project.
You can find a sample project [here](https://github.com/Kotlin/multitarget-xcode-with-kotlin-cocoapods-sample).
+67
View File
@@ -0,0 +1,67 @@
# Code Coverage
Kotlin/Native has a code coverage support that is based on Clang's
[Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html).
**Please note**:
1. Coverage support is in it's very early days and is in active development. Known issues are:
* Coverage information may be inaccurate.
* Line execution counts may be wrong.
2. Most of described functionality will be incorporated into Gradle plugin.
### Usage
#### TL;DR
```bash
kotlinc-native main.kt -Xcoverage
./program.kexe
llvm-profdata merge program.kexe.profraw -o program.profdata
llvm-cov report program.kexe -instr-profile program.profdata
```
#### Compiling with coverage enabled
There are 2 compiler flags that allows to generate coverage information:
* `-Xcoverage`. Generate coverage for immediate sources.
* `-Xlibrary-to-cover=<path>`. Generate coverage for specified `klib`.
Note that library also should be either linked via `-library/-l` compiler option or be a transitive dependency.
#### Running covered executable
After the execution of the compiled binary (ex. `program.kexe`) `program.kexe.profraw` will be generated.
By default it will be generated in the same location where binary was created. The are two ways to override this behavior:
* `-Xcoverage-file=<path>` compiler flag.
* `LLVM_PROFILE_FILE` environment variable. So if you run your program like this:
```
LLVM_PROFILE_FILE=build/program.profraw ./program.kexe
```
Then the coverage information will be stored to the `build` dir as `program.profraw`.
#### Parsing `*.profraw`
Generated file can be parsed with `llvm-profdata` utility. Basic usage:
```
llvm-profdata merge default.profraw -o program.profdata
```
See [command guide](http://llvm.org/docs/CommandGuide/llvm-profdata.html) for more options.
#### Creating reports
The last step is to create a report from the `program.profdata` file.
It can be done with `llvm-cov` utility (refer to [command guide](http://llvm.org/docs/CommandGuide/llvm-cov.html) for detailed usage).
For example, we can see a basic report using:
```
llvm-cov report program.kexe -instr-profile program.profdata
```
Or show a line-by-line coverage information in html:
```
llvm-cov show program.kexe -instr-profile program.profdata -format=html > report.html
```
### Sample
Usually coverage information is collected during running of the tests.
Please refer to `samples/coverage` to see how it can be done.
### Useful links
* [LLVM Code Coverage Mapping Format](https://llvm.org/docs/CoverageMappingFormat.html)
+222
View File
@@ -0,0 +1,222 @@
## Concurrency in Kotlin/Native
Kotlin/Native runtime doesn't encourage a classical thread-oriented concurrency
model with mutually exclusive code blocks and conditional variables, as this model is
known to be error-prone and unreliable. Instead, we suggest a collection of
alternative approaches, allowing you to use hardware concurrency and implement blocking IO.
Those approaches are as follows, and they will be elaborated on in further sections:
* Workers with message passing
* Object subgraph ownership transfer
* Object subgraph freezing
* Object subgraph detachment
* Raw shared memory using C globals
* Atomic primitives and references
* Coroutines for blocking operations (not covered in this document)
### Workers
Instead of threads Kotlin/Native runtime offers the concept of workers: concurrently executed
control flow streams with an associated request queue. Workers are very similar to the actors
in the Actor Model. A worker can exchange Kotlin objects with another worker, so that at any moment
each mutable object is owned by a single worker, but ownership can be transferred.
See section [Object transfer and freezing](#transfer).
Once a worker is started with the `Worker.start` function call, it can be addressed with its own unique integer
worker id. Other workers, or non-worker concurrency primitives, such as OS threads, can send a message
to the worker with the `execute` call.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val future = execute(TransferMode.SAFE, { SomeDataForWorker() }) {
// data returned by the second function argument comes to the
// worker routine as 'input' parameter.
input ->
// Here we create an instance to be returned when someone consumes result future.
WorkerResult(input.stringParam + " result")
}
future.consume {
// Here we see result returned from routine above. Note that future object or
// id could be transferred to another worker, so we don't have to consume future
// in same execution context it was obtained.
result -> println("result is $result")
}
```
</div>
The call to `execute` uses a function passed as its second parameter to produce an object subgraph
(i.e. set of mutually referring objects) which is then passed as a whole to that worker, it is then no longer
available to the thread that initiated the request. This property is checked if the first parameter
is `TransferMode.SAFE` by graph traversal and is just assumed to be true, if it is `TransferMode.UNSAFE`.
The last parameter to `execute` is a special Kotlin lambda, which is not allowed to capture any state,
and is actually invoked in the target worker's context. Once processed, the result is transferred to whatever consumes
it in the future, and it is attached to the object graph of that worker/thread.
If an object is transferred in `UNSAFE` mode and is still accessible from multiple concurrent executors,
program will likely crash unexpectedly, so consider that last resort in optimizing, not a general purpose
mechanism.
For a more complete example please refer to the [workers example](https://github.com/JetBrains/kotlin-native/tree/master/samples/workers)
in the Kotlin/Native repository.
<a name="transfer"></a>
### Object transfer and freezing
An important invariant that Kotlin/Native runtime maintains is that the object is either owned by a single
thread/worker, or it is immutable (_shared XOR mutable_). This ensures that the same data has a single mutator,
and so there is no need for locking to exist. To achieve such an invariant, we use the concept of not externally
referred object subgraphs.
This is a subgraph which has no external references from outside of the subgraph, which could be checked
algorithmically with O(N) complexity (in ARC systems), where N is the number of elements in such a subgraph.
Such subgraphs are usually produced as a result of a lambda expression, for example some builder, and may not
contain objects, referred to externally.
Freezing is a runtime operation making a given object subgraph immutable, by modifying the object header
so that future mutation attempts throw an `InvalidMutabilityException`. It is deep, so
if an object has a pointer to other objects - transitive closure of such objects will be frozen.
Freezing is a one way transformation, frozen objects cannot be unfrozen. Frozen objects have a nice
property that due to their immutability, they can be freely shared between multiple workers/threads
without breaking the "mutable XOR shared" invariant.
If an object is frozen it can be checked with an extension property `isFrozen`, and if it is, object sharing
is allowed. Currently, Kotlin/Native runtime only freezes the enum objects after creation, although additional
autofreezing of certain provably immutable objects could be implemented in the future.
<a name="detach"></a>
### Object subgraph detachment
An object subgraph without external references can be disconnected using `DetachedObjectGraph<T>` to
a `COpaquePointer` value, which could be stored in `void*` data, so the disconnected object subgraphs
can be stored in a C data structure, and later attached back with `DetachedObjectGraph<T>.attach()` in an arbitrary thread
or a worker. Combining it with [raw memory sharing](#shared) it allows side channel object transfer between
concurrent threads, if the worker mechanisms are insufficient for a particular task. Note, that object detachment
may require explicit leaving function holding object references and then performing cyclic garbage collection.
For example, code like:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val graph = DetachedObjectGraph {
val map = mutableMapOf<String, String>()
for (entry in map.entries) {
// ...
}
map
}
```
</div>
will not work as expected and will throw runtime exception, as there are uncollected cycles in the detached graph, while:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val graph = DetachedObjectGraph {
{
val map = mutableMapOf<String, String>()
for (entry in map.entries) {
// ...
}
map
}().also {
kotlin.native.internal.GC.collect()
}
}
```
</div>
will work properly, as holding references will be released, and then cyclic garbage affecting reference counter is
collected.
<a name="shared"></a>
### Raw shared memory
Considering the strong ties between Kotlin/Native and C via interoperability, in conjunction with the other mechanisms
mentioned above it is possible to build popular data structures, like concurrent hashmap or shared cache with
Kotlin/Native. It is possible to rely upon shared C data, and store in it references to detached object subgraphs.
Consider the following .def file:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
package = global
---
typedef struct {
int version;
void* kotlinObject;
} SharedData;
SharedData sharedData;
```
</div>
After running the cinterop tool it can share Kotlin data in a versionized global structure,
and interact with it from Kotlin transparently via autogenerated Kotlin like this:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
class SharedData(rawPtr: NativePtr) : CStructVar(rawPtr) {
var version: Int
var kotlinObject: COpaquePointer?
}
```
</div>
So in combination with the top level variable declared above, it can allow looking at the same memory from different
threads and building traditional concurrent structures with platform-specific synchronization primitives.
<a name="top_level"></a>
### Global variables and singletons
Frequently, global variables are a source of unintended concurrency issues, so _Kotlin/Native_ implements
the following mechanisms to prevent the unintended sharing of state via global objects:
* global variables, unless specially marked, can be only accessed from the main thread (that is, the thread
_Kotlin/Native_ runtime was first initialized), if other thread access such a global, `IncorrectDereferenceException` is thrown
* for global variables marked with the `@kotlin.native.ThreadLocal` annotation each threads keeps thread-local copy,
so changes are not visible between threads
* for global variables marked with the `@kotlin.native.SharedImmutable` annotation value is shared, but frozen
before publishing, so each threads sees the same value
* singleton objects unless marked with `@kotlin.native.ThreadLocal` are frozen and shared, lazy values allowed,
unless cyclic frozen structures were attempted to be created
* enums are always frozen
Combined, these mechanisms allow natural race-free programming with code reuse across platforms in MPP projects.
<a name="atomic_references"></a>
### Atomic primitives and references
Kotlin/Native standard library provides primitives for safe working with concurrently mutable data, namely
`AtomicInt`, `AtomicLong`, `AtomicNativePtr`, `AtomicReference` and `FreezableAtomicReference` in the package
`kotlin.native.concurrent`.
Atomic primitives allows concurrency-safe update operations, such as increment, decrement and compare-and-swap,
along with value setters and getters. Atomic primitives are considered always frozen by the runtime, and
while their fields can be updated with the regular `field.value += 1`, it is not concurrency safe.
Value must be be changed using dedicated operations, so it is possible to perform concurrent-safe
global counters and similar data structures.
Some algorithms require shared mutable references across the multiple workers, for example global mutable
configuration could be implemented as an immutable instance of properties list atomically replaced with the
new version on configuration update as the whole in a single transaction. This way no inconsistent configuration
could be seen, and at the same time configuration could be updated as needed.
To achieve such functionality Kotlin/Native runtime provides two related classes:
`kotlin.native.concurrent.AtomicReference` and `kotlin.native.concurrent.FreezableAtomicReference`.
Atomic reference holds reference to a frozen or immutable object, and its value could be updated by set
or compare-and-swap operation. Thus, dedicated set of objects could be used to create mutable shared object graphs
(of immutable objects). Cycles in the shared memory could be created using atomic references.
Kotlin/Native runtime doesn't support garbage collecting cyclic data when reference cycle goes through
`AtomicReference` or frozen `FreezableAtomicReference`. So to avoid memory leaks atomic references
that are potentially parts of shared cyclic data should be zeroed out once no longer needed.
If atomic reference value is attempted to be set to non-frozen value runtime exception is thrown.
Freezable atomic reference is similar to the regular atomic reference, but until frozen behaves like regular box
for a reference. After freezing it behaves like an atomic reference, and can only hold a reference to a frozen object.
+261
View File
@@ -0,0 +1,261 @@
## Debugging
Currently the Kotlin/Native compiler produces debug info compatible with the DWARF 2 specification, so modern debugger tools can
perform the following operations:
- breakpoints
- stepping
- inspection of type information
- variable inspection
### Producing binaries with debug info with Kotlin/Native compiler
To produce binaries with the Kotlin/Native compiler it's sufficient to use the ``-g`` option on the command line.<br/>
_Example:_
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
0:b-debugger-fixes:minamoto@unit-703(0)# cat - > hello.kt
fun main(args: Array<String>) {
println("Hello world")
println("I need your clothes, your boots and your motocycle")
}
0:b-debugger-fixes:minamoto@unit-703(0)# dist/bin/konanc -g hello.kt -o terminator
KtFile: hello.kt
0:b-debugger-fixes:minamoto@unit-703(0)# lldb terminator.kexe
(lldb) target create "terminator.kexe"
Current executable set to 'terminator.kexe' (x86_64).
(lldb) b kfun:main(kotlin.Array<kotlin.String>)
Breakpoint 1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
(lldb) r
Process 28473 launched: '/Users/minamoto/ws/.git-trees/debugger-fixes/terminator.kexe' (x86_64)
Process 28473 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x00000001000012e4 terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) at hello.kt:2
1 fun main(args: Array<String>) {
-> 2 println("Hello world")
3 println("I need your clothes, your boots and your motocycle")
4 }
(lldb) n
Hello world
Process 28473 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
frame #0: 0x00000001000012f0 terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) at hello.kt:3
1 fun main(args: Array<String>) {
2 println("Hello world")
-> 3 println("I need your clothes, your boots and your motocycle")
4 }
(lldb)
```
</div>
### Breakpoints
Modern debuggers provide several ways to set a breakpoint, see below for a tool-by-tool breakdown:
#### lldb
- by name
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(lldb) b -n kfun:main(kotlin.Array<kotlin.String>)
Breakpoint 4: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
```
</div>
_``-n`` is optional, this flag is applied by default_
- by location (filename, line number)
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(lldb) b -f hello.kt -l 1
Breakpoint 1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = 0x00000001000012e4
```
</div>
- by address
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(lldb) b -a 0x00000001000012e4
Breakpoint 2: address = 0x00000001000012e4
```
</div>
- by regex, you might find it useful for debugging generated artifacts, like lambda etc. (where used ``#`` symbol in name).
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
3: regex = 'main\(', locations = 1
3.1: where = terminator.kexe`kfun:main(kotlin.Array<kotlin.String>) + 4 at hello.kt:2, address = terminator.kexe[0x00000001000012e4], unresolved, hit count = 0
```
</div>
#### gdb
- by regex
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(gdb) rbreak main(
Breakpoint 1 at 0x1000109b4
struct ktype:kotlin.Unit &kfun:main(kotlin.Array<kotlin.String>);
```
</div>
- by name __unusable__, because ``:`` is a separator for the breakpoint by location
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(gdb) b kfun:main(kotlin.Array<kotlin.String>)
No source file named kfun.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (kfun:main(kotlin.Array<kotlin.String>)) pending
```
</div>
- by location
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(gdb) b hello.kt:1
Breakpoint 2 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 1.
```
</div>
- by address
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
(gdb) b *0x100001704
Note: breakpoint 2 also set at pc 0x100001704.
Breakpoint 3 at 0x100001704: file /Users/minamoto/ws/.git-trees/hello.kt, line 2.
```
</div>
### Stepping
Stepping functions works mostly the same way as for C/C++ programs
### Variable inspection
Variable inspections for var variables works out of the box for primitive types.
For non-primitive types there are custom pretty printers for lldb in
`konan_lldb.py`:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
λ cat main.kt | nl
1 fun main(args: Array<String>) {
2 var x = 1
3 var y = 2
4 var p = Point(x, y)
5 println("p = $p")
6 }
7 data class Point(val x: Int, val y: Int)
λ lldb ./program.kexe -o 'b main.kt:5' -o
(lldb) target create "./program.kexe"
Current executable set to './program.kexe' (x86_64).
(lldb) b main.kt:5
Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 289 at main.kt:5, address = 0x000000000040af11
(lldb) r
Process 4985 stopped
* thread #1, name = 'program.kexe', stop reason = breakpoint 1.1
frame #0: program.kexe`kfun:main(kotlin.Array<kotlin.String>) at main.kt:5
2 var x = 1
3 var y = 2
4 var p = Point(x, y)
-> 5 println("p = $p")
6 }
7
8 data class Point(val x: Int, val y: Int)
Process 4985 launched: './program.kexe' (x86_64)
(lldb) fr var
(int) x = 1
(int) y = 2
(ObjHeader *) p = 0x00000000007643d8
(lldb) command script import dist/tools/konan_lldb.py
(lldb) fr var
(int) x = 1
(int) y = 2
(ObjHeader *) p = Point(x=1, y=2)
(lldb) p p
(ObjHeader *) $2 = Point(x=1, y=2)
(lldb)
```
</div>
Getting representation of the object variable (var) could also be done using the
built-in runtime function `Konan_DebugPrint` (this approach also works for gdb,
using a module of command syntax):
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
0:b-debugger-fixes:minamoto@unit-703(0)# cat ../debugger-plugin/1.kt | nl -p
1 fun foo(a:String, b:Int) = a + b
2 fun one() = 1
3 fun main(arg:Array<String>) {
4 var a_variable = foo("(a_variable) one is ", 1)
5 var b_variable = foo("(b_variable) two is ", 2)
6 var c_variable = foo("(c_variable) two is ", 3)
7 var d_variable = foo("(d_variable) two is ", 4)
8 println(a_variable)
9 println(b_variable)
10 println(c_variable)
11 println(d_variable)
12 }
0:b-debugger-fixes:minamoto@unit-703(0)# lldb ./program.kexe -o 'b -f 1.kt -l 9' -o r
(lldb) target create "./program.kexe"
Current executable set to './program.kexe' (x86_64).
(lldb) b -f 1.kt -l 9
Breakpoint 1: where = program.kexe`kfun:main(kotlin.Array<kotlin.String>) + 463 at 1.kt:9, address = 0x0000000100000dbf
(lldb) r
(a_variable) one is 1
Process 80496 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000dbf program.kexe`kfun:main(kotlin.Array<kotlin.String>) at 1.kt:9
6 var c_variable = foo("(c_variable) two is ", 3)
7 var d_variable = foo("(d_variable) two is ", 4)
8 println(a_variable)
-> 9 println(b_variable)
10 println(c_variable)
11 println(d_variable)
12 }
Process 80496 launched: './program.kexe' (x86_64)
(lldb) expression -- Konan_DebugPrint(a_variable)
(a_variable) one is 1(KInt) $0 = 0
(lldb)
```
</div>
### Known issues
- performance of Python bindings.
_Note:_ Supporting the DWARF 2 specification means that the debugger tool recognizes Kotlin as C89, because before the DWARF 5 specification, there is no identifier for the Kotlin language type in specification.
+33
View File
@@ -0,0 +1,33 @@
# Kotlin/Native #
_Kotlin/Native_ is a LLVM backend for the Kotlin compiler, runtime
implementation and native code generation facility using LLVM toolchain.
_Kotlin/Native_ is primarily designed to allow compilation for platforms where
virtual machines are not desirable or possible (such as iOS, embedded targets),
or where developer is willing to produce reasonably-sized self-contained program
without need to ship an additional execution runtime.
_Kotlin/Native_ could be used either as standalone compiler toolchain or as Gradle
plugin. See [documentation](https://kotlinlang.org/docs/reference/native/gradle_plugin.html)
for more details on how to use this plugin.
Compile your programs like that:
export PATH=kotlin-native-<platform>-<version>/bin:$PATH
kotlinc hello.kt -o hello
For an optimized compilation use -opt:
kotlinc hello.kt -o hello -opt
To generate interoperability stubs create library definition file
(take a look on [Tetris sample](samples/tetris))
and run `cinterop` tool like this:
cinterop -def lib.def
See [C interop documentation](https://kotlinlang.org/docs/reference/native/c_interop.html)
for more information on how to use C libraries from _Kotlin/Native_.
See [`RELEASE_NOTES.md`](https://github.com/JetBrains/kotlin-native/blob/master/RELEASE_NOTES.md) for information on supported platforms and current limitations.
+206
View File
@@ -0,0 +1,206 @@
### Q: How do I run my program?
A: Define a top level function `fun main(args: Array<String>)` or just `fun main()` if you are not interested
in passed arguments, please ensure it's not in a package.
Also compiler switch `-entry` could be used to make any function taking `Array<String>` or no arguments
and return `Unit` as an entry point.
### Q: What is Kotlin/Native memory management model?
A: Kotlin/Native provides an automated memory management scheme, similar to what Java or Swift provides.
The current implementation includes an automated reference counter with a cycle collector to collect cyclical
garbage.
### Q: How do I create a shared library?
A: Use the `-produce dynamic` compiler switch, or `binaries.sharedLib()` in Gradle, i.e.
<div class="sample" markdown="1" theme="idea" mode="kotlin" data-highlight-only>
```kotlin
kotlin {
iosArm64("mylib") {
binaries.sharedLib()
}
}
```
</div>
It will produce a platform-specific shared object (.so on Linux, .dylib on macOS, and .dll on Windows targets) and a
C language header, allowing the use of all public APIs available in your Kotlin/Native program from C/C++ code.
See `samples/python_extension` for an example of using such a shared object to provide a bridge between Python and
Kotlin/Native.
### Q: How do I create a static library or an object file?
A: Use the `-produce static` compiler switch, or `binaries.staticLib()` in Gradle, i.e.
<div class="sample" markdown="1" theme="idea" mode="kotlin" data-highlight-only>
```kotlin
kotlin {
iosArm64("mylib") {
binaries.staticLib()
}
}
```
</div>
It will produce a platform-specific static object (.a library format) and a C language header, allowing you to
use all the public APIs available in your Kotlin/Native program from C/C++ code.
### Q: How do I run Kotlin/Native behind a corporate proxy?
A: As Kotlin/Native needs to download a platform specific toolchain, you need to specify
`-Dhttp.proxyHost=xxx -Dhttp.proxyPort=xxx` as the compiler's or `gradlew` arguments,
or set it via the `JAVA_OPTS` environment variable.
### Q: How do I specify a custom Objective-C prefix/name for my Kotlin framework?
A: Use the `-module-name` compiler option or matching Gradle DSL statement, i.e.
<div class="multi-language-sample" data-lang="kotlin">
<div class="sample" markdown="1" theme="idea" mode="kotlin" data-highlight-only>
```kotlin
kotlin {
iosArm64("myapp") {
binaries.framework {
freeCompilerArgs += listOf("-module-name", "TheName")
}
}
}
```
</div>
</div>
<div class="multi-language-sample" data-lang="groovy">
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
iosArm64("myapp") {
binaries.framework {
freeCompilerArgs += ["-module-name", "TheName"]
}
}
}
```
</div>
</div>
### Q: How do I rename the iOS framework? (default name is _\<project name\>_.framework)
A: Use the `baseName` option. This will also set the module name.
<div class="sample" markdown="1" theme="idea" mode="kotlin" data-highlight-only>
```kotlin
kotlin {
iosArm64("myapp") {
binaries {
framework {
baseName = "TheName"
}
}
}
}
```
</div>
### Q: How do I enable bitcode for my Kotlin framework?
A: By default gradle plugin adds it on iOS target.
* For debug build it embeds placeholder LLVM IR data as a marker.
* For release build it embeds bitcode as data.
Or commandline arguments: `-Xembed-bitcode` (for release) and `-Xembed-bitcode-marker` (debug)
Setting this in a Gradle DSL:
<div class="sample" markdown="1" theme="idea" mode="kotlin" data-highlight-only>
```kotlin
kotlin {
iosArm64("myapp") {
binaries {
framework {
// Use "marker" to embed the bitcode marker (for debug builds).
// Use "disable" to disable embedding.
embedBitcode("bitcode") // for release binaries.
}
}
}
}
```
</div>
These options have nearly the same effect as clang's `-fembed-bitcode`/`-fembed-bitcode-marker`
and swiftc's `-embed-bitcode`/`-embed-bitcode-marker`.
### Q: Why do I see `InvalidMutabilityException`?
A: It likely happens, because you are trying to mutate a frozen object. An object can transfer to the
frozen state either explicitly, as objects reachable from objects on which the `kotlin.native.concurrent.freeze` is called,
or implicitly (i.e. reachable from `enum` or global singleton object - see the next question).
### Q: How do I make a singleton object mutable?
A: Currently, singleton objects are immutable (i.e. frozen after creation), and it's generally considered
good practise to have the global state immutable. If for some reason you need a mutable state inside such an
object, use the `@konan.ThreadLocal` annotation on the object. Also the `kotlin.native.concurrent.AtomicReference` class could be
used to store different pointers to frozen objects in a frozen object and automatically update them.
### Q: How can I compile my project against the Kotlin/Native master?
A: One of the following should be done:
<details>
<summary>For the CLI, you can compile using gradle as stated in the README (and if you get errors, you can try to do a <code>./gradlew clean</code>):</summary>
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
./gradlew dependencies:update
./gradlew dist distPlatformLibs
```
</div>
You can then set the `KONAN_HOME` env variable to the generated `dist` folder in the git repository.
</details>
<details>
<summary>For Gradle, you can use <a href="https://docs.gradle.org/current/userguide/composite_builds.html">Gradle composite builds</a> like this:</summary>
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
# Set with the path of your kotlin-native clone
export KONAN_REPO=$PWD/../kotlin-native
# Run this once since it is costly, you can remove the `clean` task if not big changes were made from the last time you did this
pushd $KONAN_REPO && git pull && ./gradlew clean dependencies:update dist distPlatformLibs && popd
# In your project, you set have to the org.jetbrains.kotlin.native.home property, and include as composite the shared and gradle-plugin builds
./gradlew check -Porg.jetbrains.kotlin.native.home=$KONAN_REPO/dist --include-build $KONAN_REPO/shared --include-build $KONAN_REPO/tools/kotlin-native-gradle-plugin
```
</div>
</details>
+729
View File
@@ -0,0 +1,729 @@
# Kotlin/Native Gradle plugin
Since 1.3.40, a separate Gradle plugin for Kotlin/Native is deprecated in favor of the `kotlin-multiplatform` plugin.
This plugin provides an IDE support along with support of the new multiplatform project model introduced in Kotlin 1.3.0.
Below you can find a short list of differences between `kotlin-platform-native` and `kotlin-muliplatform` plugins.
For more information see the `kotlin-muliplatform` [documentation page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html).
For `kotlin-platform-native` reference see the [corresponding section](#kotlin-platform-native-reference).
### Applying the multiplatform plugin
To apply the `kotlin-multiplatform` plugin, just add the following snippet into your build script:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
plugins {
id("org.jetbrains.kotlin.multiplatform") version '1.3.40'
}
```
</div>
### Managing targets
With the `kotlin-platform-native` plugin a set of target platforms is specified as a list in properties of the main component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
targets = ['macos_x64', 'linux_x64', 'mingw_x64']
}
```
</div>
With the `kotlin-multiplatform` plugin target platforms can be added into a project using special methods available in the `kotlin` extension.
Each method adds into a project one __target__ which can be accessed using the `targets` property. Each target can be configured independently
including output kinds, additional compiler options etc. See details about targets at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets).
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
kotlin {
// These targets are declared without any target-specific settings.
macosX64()
linuxX64()
// You can specify a custom name used to access the target.
mingwX64("windows")
iosArm64 {
// Additional settings for ios_arm64.
}
// You can access declared targets using the `targets` property.
println(targets.macosX64)
println(targets.windows)
// You also can configure all native targets in a single block.
targets.withType(KotlinNativeTarget) {
// Native target configuration.
}
}
```
</div>
Each target includes two __compilations__: `main` and `test` compiling product and test sources respectively. A compilation is an abstraction
over a compiler invocation and described at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#configuring-compilations).
### Managing sources
With the `kotlin-platform-native` plugin source sets are used to separate test and product sources. Also you can specify different sources
for different platforms in the same source set:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
sourceSets {
// Adding target-independent sources.
main.kotlin.srcDirs += 'src/main/mySources'
// Adding Linux-specific code.
main.target('linux_x64').srcDirs += 'src/main/linux'
}
```
</div>
With the `kotlin-multiplatform` plugin __source__ __sets__ are also used to group sources but source files for different platforms are located in different source sets.
For each declared target two source sets are created: `<target-name>Main` and `<target-name>Test` containing product and test sources for this platform. Common for all
platforms sources are located in `commonMain` and `commonTest` source sets created by default. More information about source sets can be found
[here](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#configuring-source-sets).
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
sourceSets {
// Adding target-independent sources.
commonMain.kotlin.srcDirs += file("src/main/mySources")
// Adding Linux-specific code.
linuxX64Main.kotlin.srcDirs += file("src/main/linux")
}
}
```
</div>
### Managing dependencies
With the `kotlin-platform-native` plugin dependencies are configured in a traditional for Gradle way by grouping them into configurations
using the project `dependencies` block:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
dependencies {
implementation 'org.sample.test:mylibrary:1.0'
testImplementation 'org.sample.test:testlibrary:1.0'
}
```
</div>
The `kotlin-multiplatform` plugin also uses configurations under the hood but it also provides a `dependencies` block for each source set
allowing configuring dependencies of this sources set:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin.sourceSets {
commonMain {
dependencies {
implementation("org.sample.test:mylibrary:1.0")
}
}
commonTest {
dependencies {
implementation("org.sample.test:testlibrary:1.0")
}
}
}
```
</div>
Note that a module referenced by a dependency declared for `commonMain` or `commonTest` source set must be published using the `kotlin-multiplatform` plugin.
If you want to use libraries published by the `kotlin-platform-native` plugin, you need to declare a separate source set for common native sources.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin.sourceSets {
// Create a common source set used by native targets only.
nativeMain {
dependsOn(commonMain)
dependencies {
// Depend on a library published by the kotlin-platform-naive plugin.
implementation("org.sample.test:mylibrary:1.0")
}
}
// Configure all native platform sources sets to use it as a common one.
linuxX64Main.dependsOn(nativeMain)
macosX64Main.dependsOn(nativeMain)
//...
}
```
</div>
See more info about dependencies at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#adding-dependencies).
### Output kinds
With the `kotlin-platform-native` plugin output kinds are specified as a list in properties of a component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Compile the component into an executable and a Kotlin/Native library.
outputKinds = [EXECUTABLE, KLIBRARY]
}
```
</div>
With the `kotlin-multiplatform` plugin a compilation always produces a `*.klib` file. A separate `binaries` block is used to configure what
final native binaries should be produced by each target. Each binary can be configured independently including linker options, executable entry point etc.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
macosX64 {
binaries {
executable {
// Binary configuration: linker options, name, etc.
}
framework {
// ...
}
}
}
}
```
</div>
See more about native binaries declaration at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#building-final-native-binaries).
### Publishing
Both `kotlin-platform-native` and `kotlin-multiplatform` plugins automatically set up artifact publication when the
`maven-publish` plugin is applied. See details about publication at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#publishing-a-multiplatform-library).
Note that currently only Kotlin/Native libraries (`*.klib`) can be published for native targets.
### Cinterop support
With the `kotlin-platform-native` plugin interop with a native library can be declared in component dependencies:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
dependencies {
cinterop('mystdio') {
// Cinterop configuration.
}
}
}
```
</div>
With the `kotlin-multiplatform` plugin interops are configured as a part of a compilation (see details [here](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#cinterop-support)).
The rest of an interop configuration is the same as for the `kotlin-platform-native` plugin.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
macosX64 {
compilations.main.cinterops {
mystdio {
// Cinterop configuration.
}
}
}
}
```
</div>
## `kotlin-platform-native` reference
### Overview
You may use the Gradle plugin to build _Kotlin/Native_ projects. Builds of the plugin are
[available](https://plugins.gradle.org/plugin/org.jetbrains.kotlin.platform.native) at the Gradle plugin portal, so you can apply it
using Gradle plugin DSL:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
plugins {
id "org.jetbrains.kotlin.platform.native" version "1.3.0-rc-146"
}
```
</div>
You also can get the plugin from a Bintray repository. In addition to releases, this repo contains old and development
versions of the plugin which are not available at the plugin portal. To get the plugin from the Bintray repo, include
the following snippet in your build script:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
buildscript {
repositories {
mavenCentral()
maven {
url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:1.3.0-rc-146"
}
}
apply plugin: 'org.jetbrains.kotlin.platform.native'
```
</div>
By default the plugin downloads the Kotlin/Native compiler during the first run. If you have already downloaded the compiler
manually you can specify the path to its root directory using `org.jetbrains.kotlin.native.home` project property (e.g. in `gradle.properties`).
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
org.jetbrains.kotlin.native.home=/home/user/kotlin-native-0.8
```
</div>
In this case the compiler will not be downloaded by the plugin.
### Source management
Source management in the `kotlin.platform.native` plugin is uniform with other Kotlin plugins and is based on source sets.
A source set is a group of Kotlin/Native source which may contain both common and platform-specific code. The plugin
provides a top-level script block `sourceSets` allowing you to configure source sets. Also it creates the default
source sets `main` and `test` (for production and test code respectively).
By default the production sources are located in `src/main/kotlin` and the test sources - in `src/test/kotlin`.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
sourceSets {
// Adding target-independent sources.
main.kotlin.srcDirs += 'src/main/mySources'
// Adding Linux-specific code. It will be compiled in Linux binaries only.
main.target('linux_x64').srcDirs += 'src/main/linux'
}
```
</div>
### Targets and output kinds
By default the plugin creates software components for the main and test source sets. You can access them via the
`components` container provided by Gradle or via the `component` property of a corresponding source set:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
// Main component.
components.main
sourceSets.main.component
// Test component.
components.test
sourceSets.test.component
```
</div>
Components allow you to specify:
* Targets (e.g. Linux/x64 or iOS/arm64 etc)
* Output kinds (e.g. executable, library, framework etc)
* Dependencies (including interop ones)
Targets can be specified by setting a corresponding component property:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Compile this component for 64-bit MacOS, Linux and Windows.
targets = ['macos_x64', 'linux_x64', 'mingw_x64']
}
```
</div>
The plugin uses the same notation as the compiler. By default, test component uses the same targets as specified for the main one.
Output kinds can also be specified using a special property:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Compile the component into an executable and a Kotlin/Native library.
outputKinds = [EXECUTABLE, KLIBRARY]
}
```
</div>
All constants used here are available inside a component configuration script block.
The plugin supports producing binaries of the following kinds:
* `EXECUTABLE` - an executable file;
* `KLIBRARY` - a Kotlin/Native library (*.klib);
* `FRAMEWORK` - an Objective-C framework;
* `DYNAMIC` - shared native library;
* `STATIC` - static native library.
Also each native binary is built in two variants (build types): `debug` (debuggable, not optimized) and `release` (not debuggable, optimized).
Note that Kotlin/Native libraries have only `debug` variant because optimizations are preformed only during compilation
of a final binary (executable, static lib etc) and affect all libraries used to build it.
### Compile tasks
The plugin creates a compilation task for each combination of the target, output kind, and build type. The tasks have the following naming convention:
compile<ComponentName><BuildType><OutputKind><Target>KotlinNative
For example `compileDebugKlibraryMacos_x64KotlinNative`, `compileTestDebugKotlinNative`.
The name contains the following parts (some of them may be empty):
* `<ComponentName>` - name of a component. Empty for the main component.
* `<BuildType>` - `Debug` or `Release`.
* `<OutputKind>` - output kind name, e.g. `Executabe` or `Dynamic`. Empty if the component has only one output kind.
* `<Target>` - target the component is built for, e.g. `Macos_x64` or `Wasm32`. Empty if the component is built only for one target.
Also the plugin creates a number of aggregate tasks allowing you to build all the binaries for a build type (e.g.
`assembleAllDebug`) or all the binaries for a particular target (e.g. `assembleAllWasm32`).
Basic lifecycle tasks like `assemble`, `build`, and `clean` are also available.
### Running tests
The plugin builds a test executable for all the targets specified for the `test` component. If the current host platform is
included in this list the test running tasks are also created. To run tests, execute the standard lifecycle `check` task:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
./gradlew check
```
</div>
### Dependencies
The plugin allows you to declare dependencies on files and other projects using traditional Gradle's mechanism of
configurations. The plugin supports Kotlin multiplatform projects allowing you to declare the `expectedBy` dependencies
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
dependencies {
implementation files('path/to/file/dependencies')
implementation project('library')
testImplementation project('testLibrary')
expectedBy project('common')
}
```
</div>
It's possible to depend on a Kotlin/Native library published earlier in a maven repo. The plugin relies on Gradle's
[metadata](https://github.com/gradle/gradle/blob/master/subprojects/docs/src/docs/design/gradle-module-metadata-specification.md)
support so the corresponding feature must be enabled. Add the following line in your `settings.gradle`:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
enableFeaturePreview('GRADLE_METADATA')
```
</div>
Now you can declare a dependency on a Kotlin/Native library in the traditional `group:artifact:version` notation:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
dependencies {
implementation 'org.sample.test:mylibrary:1.0'
testImplementation 'org.sample.test:testlibrary:1.0'
}
```
</div>
Dependency declaration is also possible in the component block:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
dependencies {
implementation 'org.sample.test:mylibrary:1.0'
}
}
components.test {
dependencies {
implementation 'org.sample.test:testlibrary:1.0'
}
}
```
</div>
### Using cinterop
It's possible to declare a cinterop dependency for a component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
dependencies {
cinterop('mystdio') {
// src/main/c_interop/mystdio.def is used as a def file.
// Set up compiler options
compilerOpts '-I/my/include/path'
// It's possible to set up different options for different targets
target('linux') {
compilerOpts '-I/linux/include/path'
}
}
}
}
```
</div>
Here an interop library will be built and added in the component dependencies.
Often it's necessary to specify target-specific linker options for a Kotlin/Native binary using an interop. It can be
done using the `target` script block:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
target('linux') {
linkerOpts '-L/path/to/linux/libs'
}
}
```
</div>
Also the `allTargets` block is available.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Configure all targets.
allTargets {
linkerOpts '-L/path/to/libs'
}
}
```
</div>
### Publishing
In the presence of `maven-publish` plugin the publications for all the binaries built are created. The plugin uses Gradle
metadata to publish the artifacts so this feature must be enabled (see the [dependencies](#dependencies) section).
Now you can publish the artifacts with the standard Gradle `publish` task:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
./gradlew publish
```
</div>
Only `EXECUTABLE` and `KLIBRARY` binaries are published currently.
The plugin allows you to customize the pom generated for the publication with the `pom` code block available for every component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
pom {
withXml {
def root = asNode()
root.appendNode('name', 'My library')
root.appendNode('description', 'A Kotlin/Native library')
}
}
}
```
</div>
### Serialization plugin
The plugin is shipped with a customized version of the `kotlinx.serialization` plugin. To use it you don't have to
add new buildscript dependencies, just apply the plugins and add a dependency on the serialization library:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
apply plugin: 'org.jetbrains.kotlin.platform.native'
apply plugin: 'kotlinx-serialization-native'
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime-native'
}
```
</div>
The [example project](https://github.com/ilmat192/kotlin-native-serialization-sample) for details.
### DSL example
In this section a commented DSL is shown.
See also the example projects that use this plugin, e.g.
[Kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines),
[MPP http client](https://github.com/e5l/http-client-common/tree/master/samples/ios-test-application)
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
plugins {
id "org.jetbrains.kotlin.platform.native" version "1.3.0-rc-146"
}
sourceSets.main {
// Plugin uses Gradle's source directory sets here,
// so all the DSL methods available in SourceDirectorySet can be called here.
// Platform independent sources.
kotlin.srcDirs += 'src/main/customDir'
// Linux-specific sources
target('linux').srcDirs += 'src/main/linux'
}
components.main {
// Set up targets
targets = ['linux_x64', 'macos_x64', 'mingw_x64']
// Set up output kinds
outputKinds = [EXECUTABLE, KLIBRARY, FRAMEWORK, DYNAMIC, STATIC]
// Specify custom entry point for executables
entryPoint = "org.test.myMain"
// Target-specific options
target('linux_x64') {
linkerOpts '-L/linux/lib/path'
}
// Targets independent options
allTargets {
linkerOpts '-L/common/lib/path'
}
dependencies {
// Dependency on a published Kotlin/Native library.
implementation 'org.test:mylib:1.0'
// Dependency on a project
implementation project('library')
// Cinterop dependency
cinterop('interop-name') {
// Def-file describing the native API.
// The default path is src/main/c_interop/<interop-name>.def
defFile project.file("deffile.def")
// Package to place the Kotlin API generated.
packageName 'org.sample'
// Options to be passed to compiler and linker by cinterop tool.
compilerOpts 'Options for native stubs compilation'
linkerOpts 'Options for native stubs'
// Additional headers to parse.
headers project.files('header1.h', 'header2.h')
// Directories to look for headers.
includeDirs {
// All objects accepted by the Project.file method may be used with both options.
// Directories for header search (an analogue of the -I<path> compiler option).
allHeaders 'path1', 'path2'
// Additional directories to search headers listed in the 'headerFilter' def-file option.
// -headerFilterAdditionalSearchPrefix command line option analogue.
headerFilterOnly 'path1', 'path2'
}
// A shortcut for includeDirs.allHeaders.
includeDirs "include/directory" "another/directory"
// Pass additional command line options to the cinterop tool.
extraOpts '-verbose'
// Additional configuration for Linux.
target('linux') {
compilerOpts 'Linux-specific options'
}
}
}
// Additional pom settings for publication.
pom {
withXml {
def root = asNode()
root.appendNode('name', 'My library')
root.appendNode('description', 'A Kotlin/Native library')
}
}
// Additional options passed to the compiler.
extraOpts '--time'
}
```
</div>
+324
View File
@@ -0,0 +1,324 @@
## Profiling the compiler
### Profiling with Async profiler
IDEA Ultimate contains an Async sampling profiler.
As of IDEA 2018.3 Async sampling profiler is still an experimental feature, so use Ctrl-Alt-Shift-/ on Linux,
Cmd-Alt-Shift-/ on macOS to activate it. Then start compilation in CLI with `--no-daemon` and
`-Porg.gradle.workers.max=1` flags (running Gradle task with the profiler doesn't seem to work properly) and attach
to the running process using "Run/Attach Profiler to Local Process" menu item.
Select "K2NativeKt" or "org.jetbrains.kotlin.cli.utilities.MainKt" process.
On completion profiler will produce flame diagram which could be navigated with the mouse
(click-drag moves, wheel scales). More RAM in IDE (>4G) could be helpful when analyzing longer runs.
As Async is a sampling profiler, to get sensible coverage longer runs are important.
### Profiling with YourKit
Unlike Async profiler in IDEA, YourKit can work as an exact profiler and provide complete coverage
of all methods along with exact invocation counters.
Install the YourKit profiler for your platform from https://www.yourkit.com/java/profiler.
Set AGENT variable to the JVMTI agent provided by YourKit, like
export AGENT=/Applications/YourKit-Java-Profiler-2018.04.app/Contents/Resources/bin/mac/libyjpagent.jnilib
To profile standard library compilation:
./gradlew -PstdLibJvmArgs="-agentpath:$AGENT=probe_disable=*,listen=all,tracing" dist
To profile platform libraries start build of proper target like this:
./gradlew -PplatformLibsJvmArgs="-agentpath:$AGENT=probe_disable=*,listen=all,tracing" ios_arm64PlatformLibs
To profile standalone code compilation use:
JAVA_OPTS="-agentpath:$AGENT=probe_disable=*,listen=all,tracing" ./dist/bin/konanc file.kt
Then attach to the desired application in YourKit GUI and use CPU tab to inspect CPU consuming methods.
Saving the trace may be needed for more analysis. Adjusting `-Xmx` in `$HOME/.yjp/ui.ini` could help
with the big traces.
To perform memory profiling follow the steps above, and after attachment to the running process
use "Start Object Allocation Recording" button. See https://www.yourkit.com/docs/java/help/allocations.jsp for more details.
## Compiler Gradle options
There are several gradle flags one can use for Konan build.
* **-Pbuild_flags** passes flags to the compiler used to build stdlib
./gradlew -Pbuild_flags="--disable lower_inline --print_ir" stdlib
* **-Pshims** compiles LLVM interface with tracing "shims". Allowing one
to trace the LLVM calls from the compiler.
Make sure to rebuild the project.
./gradlew -Pshims=true dist
## Compiler environment variables
* **KONAN_DATA_DIR** changes `.konan` local data directory location (`$HOME/.konan` by default). Works both with cli compiler and gradle plugin
## Testing
### Compiler integration tests
To run blackbox compiler tests from JVM Kotlin use (takes time):
./gradlew run_external
To update the blackbox compiler tests set TeamCity build number in `gradle.properties`:
testKotlinVersion=<build number>
* **-Pfilter** allows one to choose test files to run.
./gradlew -Pfilter=overflowLong.kt run_external
* **-Pprefix** allows one to choose external test directories to run. Only tests from directories with given prefix will be executed.
./gradlew -Pprefix=build_external_compiler_codegen_box_cast run_external
* **-Ptest_flags** passes flags to the compiler used to compile tests
./gradlew -Ptest_flags="--time" backend.native:tests:array0
* **-Ptest_target** specifies cross target for a test run.
./gradlew -Ptest_target=raspberrypi backend.native:tests:array0
* **-Premote=user@host** sets remote test execution login/hostname. Good for cross compiled tests.
./gradlew -Premote=kotlin@111.22.33.444 backend.native:tests:run
* **-Ptest_verbose** enables printing compiler args and other helpful information during a test execution.
./gradlew -Ptest_verbose :backend.native:tests:mpp_optional_expectation
* **-Ptest_two_stage** enables two-stage compilation of tests. If two-stage compilation is enabled, test sources are compiled into a klibrary
and then a final native binary is produced from this klibrary using the -Xinclude compiler flag.
./gradlew -Ptest_two_stage backend.native:tests:array0
### Runtime unit tests
To run runtime unit tests on the host machine for both mimalloc and the standard allocator:
./gradlew hostRuntimeTests
To run tests for only one of these two allocators, run `hostStdAllocRuntimeTests` or `hostMimallocRuntimeTests`.
We use [Google Test](https://github.com/google/googletest) to execute the runtime unit tests. The build automatically fetches
the specified Google Test revision to `runtime/googletest`. It is possible to manually modify the downloaded GTest sources for debug
purposes; the build will not overwrite them by default.
To forcibly redownload Google Test when running tests, use the corresponding project property:
./gradlew hostRuntimeTests -Prefresh-gtest
or run the `downloadGTest` task directly with the `--refresh` CLI key:
./gradlew downloadGTest --refresh
To use a local GTest copy instead of the downloaded one, add the following line to `runtime/build.gradle.kts`:
googletest.useLocalSources("<path to local GTest sources>")
## Performance measurement
Firstly, it's necessary to build analyzer tool to have opportunity to compare different performance results:
cd tools/benchmarksAnalyzer
../../gradlew build
To measure performance of Kotlin/Native compiler on existing benchmarks:
./gradlew :performance:konanRun
**NOTE**: **konanRun** task needs built compiler and libs. To test against working tree make sure to run
./gradlew dist distPlatformLibs
before **konanRun**
**konanRun** task can be run separately for one/several benchmark applications:
./gradlew :performance:cinterop:konanRun
**konanRun** task has parameter `filter` which allows to run only some subset of benchmarks:
./gradlew :performance:cinterop:konanRun --filter=struct,macros
Or you can use `filterRegex` if you want to specify the filter as regexes:
./gradlew :performance:ring:konanRun --filterRegex=String.*,Loop.*
There us also verbose mode to follow progress of running benchmarks
./gradlew :performance:cinterop:konanRun --verbose
> Task :performance:cinterop:konanRun
[DEBUG] Warm up iterations for benchmark macros
[DEBUG] Running benchmark macros
...
There are also tasks for running benchmarks on JVM (pay attention, some benchmarks e.g. cinterop benchmarks can't be run on JVM)
./gradlew :performance:jvmRun
Files with results of benchmarks run are saved in `performance/build/nativeReport.json` for konanRun and `jvmReport.json` for jvmRun.
You can change the output filename by setting the `nativeJson` property for konanRun and `jvmJson` for jvmRun:
./gradlew :performance:ring:konanRun --filter=String.*,Loop.* -PnativeJson=stringsAndLoops.json
You can use the `compilerArgs` property to pass flags to the compiler used to compile the benchmarks:
./gradlew :performance:konanRun -PcompilerArgs="--time -g"
To compare different results run benchmarksAnalyzer tool:
cd tools/benchmarksAnalyzer/build/bin/<target>/benchmarksAnalyzerReleaseExecutable/
./benchmarksAnalyzer.kexe <file1> <file2>
Tool has several renders which allow produce output report in different forms (text, html, etc.). To set up render use flag `--render/-r`.
Output can be redirected to file with flag `--output/-o`.
To get detailed information about supported options, please use `--help/-h`.
Analyzer tool can compare both local files and files placed on Artifactory/TeamCity.
File description stored on Artifactory
artifactory:<build number>:<target (Linux|Windows10|MacOSX)>:<filename>
Example
artifactory:1.2-dev-7942:Windows10:nativeReport.json
File description stored on TeamCity
teamcity:<build locator>:<filename>
Example
teamcity:id:42491947:nativeReport.json
Pay attention, user and password information(with flag `-u <username>:<password>`) should be provided to get data from TeamCity.
## Composite build and testing
If you have a fix spanning both Kotlin and Kotlin/native workspaces you need to be able to test Kotlin/Native composite build. Here's how to do it manually:
### Have a composite build with the proper Kotlin tag.
Find the version of Kotlin the current native is guaranteed to build with.
The version is specified in `kotlin-native/gradle.properties`. For example:
```
kotlinVersion=1.3.70-dev-1526
```
Checkout `kotlin` workspace to tag `build-1.3.70-dev-1526`. Make sure its path ends with `.../kotlin`.
Otherwise issues will arise.
Direct `kotlin-native` build to the kotlin with `kotlinProjectPath` in native's `gradle.properties`.
Now you have the kotlin + kotlin-native combination that is known to build.
Apply your fix on top of both workspaces and run
```
$ ./gradlew dist
```
in `kotlin-native` to check the buildability.
### Testing native
For a quick check use:
```
$ ./gradlew sanity 2>&1 | tee log
```
For a longer, more thorough testing build the complete build. Make sure you are running it on a macOS.
Have a complete build:
```
$ ./gradlew bundle # includes dist as its part
```
then run two test sets:
```
$ ./gradlew backend.native:tests:run 2>&1 | tee log
$ ./gradlew backend.native:tests:runExternal -Ptest_two_stage=true 2>&1 | tee log
```
## LLVM
See [BUILDING_LLVM.md](BUILDING_LLVM.md) if you want to build and use your own LLVM distribution
instead of provided one.
### Using different LLVM distributions as part of Kotlin/Native compilation pipeline.
`llvmHome.<HOST_NAME>` variable in `<distribution_location>/konan/konan.properties` controls
which LLVM distribution Kotlin/Native will use in its compilation pipeline.
You can replace its value with either `$llvm.<HOST_NAME>.{dev, user}` to use one of predefined distributions
or pass an absolute to your own distribution.
Don't forget to set `llvmVersion.<HOST_NAME>` to the version of your LLVM distribution.
### Playing with compilation pipeline.
Following compiler phases control different parts of LLVM pipeline:
1. `LinkBitcodeDependencies`. Linkage of produced bitcode with runtime and some other dependencies.
2. `BitcodeOptimization`. Running LLVM optimization pipeline.
3. `ObjectFiles`. Compilation of bitcode with Clang.
For example, pass `-Xdisable-phases=BitcodeOptimization` to skip optimization pipeline.
Note that disabling `LinkBitcodeDependencies` or `ObjectFiles` will break compilation pipeline.
By default, compiler takes options for Clang from [konan.properties](konan/konan.properties) file
by combining `clangFlags.<TARGET>` and `clang<Noopt/Opt/Debug>Flags.<TARGET>` properties.
To override this behaviour, one can specify flag `-Xoverride-clang-options=<arg1, ..., argN>`.
Please note:
1. Kotlin Native passes bitcode files to Clang instead of C or C++, so many flags won't work.
2. `-c` should be passed because Kotlin/Native calls linker by itself.
Another useful compiler option is `-Xtemporary-files-dir=<PATH>` which allows
to specify a directory for intermediate compiler artifacts like bitcode and object files.
#### Example 1. Bitcode right after IR to Bitcode translation.
```shell script
konanc main.kt -produce bitcode -o bitcode.bc
```
#### Example 2. Bitcode after LLVM optimizations.
```shell script
konanc main.kt -Xtemporary-files-dir=<PATH> -o <OUTPUT_NAME>
```
`<PATH>/<OUTPUT_NAME>.kt.bc` will contain bitcode after LLVM optimization pipeline.
#### Example 3. Replace predefined LLVM pipeline with Clang options.
```shell script
konanc main.kt -Xdisable-phases=BitcodeOptimization -Xoverride-clang-options=-c,-O2
```
## Running Clang the same way Kotlin/Native compiler does
Kotlin/Native compiler (including `cinterop` tool) has machinery that manages LLVM, Clang and native SDKs for supported targets
and runs bundled Clang with proper arguments.
To utilize this machinery, use `$dist/bin/run_konan clang $tool $target $arguments`, e.g.
```
$dist/bin/run_konan clang clang ios_arm64 1.c
```
will print and run the following command:
```
~/.konan/dependencies/clang-llvm-apple-8.0.0-darwin-macos/bin/clang \
-B/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin \
-fno-stack-protector -stdlib=libc++ -arch arm64 \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.5.sdk \
-miphoneos-version-min=9.0 1.c
```
The similar helper is available for LLVM tools, `$dist/bin/run_konan llvm $tool $arguments`.
+31
View File
@@ -0,0 +1,31 @@
# Immutability in Kotlin/Native
Kotlin/Native implements strict mutability checks, ensuring
the important invariant that the object is either immutable or
accessible from the single thread at that moment in time (`mutable XOR global`).
Immutability is a runtime property in Kotlin/Native, and can be applied
to an arbitrary object subgraph using the `kotlin.native.concurrent.freeze` function.
It makes all the objects reachable from the given one immutable,
such a transition is a one-way operation (i.e., objects cannot be unfrozen later).
Some naturally immutable objects such as `kotlin.String`, `kotlin.Int`, and
other primitive types, along with `AtomicInt` and `AtomicReference` are frozen
by default. If a mutating operation is applied to a frozen object,
an `InvalidMutabilityException` is thrown.
To achieve `mutable XOR global` invariant, all globally visible state (currently,
`object` singletons and enums) are automatically frozen. If object freezing
is not desired, a `kotlin.native.ThreadLocal` annotation can be used, which will make
the object state thread local, and so, mutable (but the changed state is not visible to
other threads).
Top level/global variables of non-primitive types are by default accessible in the
main thread (i.e., the thread which initialized _Kotlin/Native_ runtime first) only.
Access from another thread will lead to an `IncorrectDereferenceException` being thrown.
To make such variables accessible in other threads, you can use either the `@ThreadLocal` annotation,
and mark the value thread local or `@SharedImmutable`, which will make the value frozen and accessible
from other threads.
Class `AtomicReference` can be used to publish the changed frozen state to
other threads, and so build patterns like shared caches.
+722
View File
@@ -0,0 +1,722 @@
# _Kotlin/Native_ interoperability #
## Introduction ##
_Kotlin/Native_ follows the general tradition of Kotlin to provide excellent
existing platform software interoperability. In the case of a native platform,
the most important interoperability target is a C library. So _Kotlin/Native_
comes with a `cinterop` tool, which can be used to quickly generate
everything needed to interact with an external library.
The following workflow is expected when interacting with the native library.
* create a `.def` file describing what to include into bindings
* use the `cinterop` tool to produce Kotlin bindings
* run _Kotlin/Native_ compiler on an application to produce the final executable
The interoperability tool analyses C headers and produces a "natural" mapping of
the types, functions, and constants into the Kotlin world. The generated stubs can be
imported into an IDE for the purpose of code completion and navigation.
Interoperability with Swift/Objective-C is provided too and covered in a
separate document [OBJC_INTEROP.md](OBJC_INTEROP.md).
## Platform libraries ##
Note that in many cases there's no need to use custom interoperability library creation mechanisms described below,
as for APIs available on the platform standardized bindings called [platform libraries](PLATFORM_LIBS.md)
could be used. For example, POSIX on Linux/macOS platforms, Win32 on Windows platform, or Apple frameworks
on macOS/iOS are available this way.
## Simple example ##
Install libgit2 and prepare stubs for the git library:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
cd samples/gitchurn
../../dist/bin/cinterop -def src/main/c_interop/libgit2.def \
-compiler-option -I/usr/local/include -o libgit2
```
</div>
Compile the client:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
../../dist/bin/kotlinc src/main/kotlin \
-library libgit2 -o GitChurn
```
</div>
Run the client:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
./GitChurn.kexe ../..
```
</div>
## Creating bindings for a new library ##
To create bindings for a new library, start by creating a `.def` file.
Structurally it's a simple property file, which looks like this:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
headers = png.h
headerFilter = png.h
package = png
```
</div>
Then run the `cinterop` tool with something like this (note that for host libraries that are not included
in the sysroot search paths, headers may be needed):
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
cinterop -def png.def -compiler-option -I/usr/local/include -o png
```
</div>
This command will produce a `png.klib` compiled library and
`png-build/kotlin` directory containing Kotlin source code for the library.
If the behavior for a certain platform needs to be modified, you can use a format like
`compilerOpts.osx` or `compilerOpts.linux` to provide platform-specific values
to the options.
Note, that the generated bindings are generally platform-specific, so if you are developing for
multiple targets, the bindings need to be regenerated.
After the generation of bindings, they can be used by the IDE as a proxy view of the
native library.
For a typical Unix library with a config script, the `compilerOpts` will likely contain
the output of a config script with the `--cflags` flag (maybe without exact paths).
The output of a config script with `--libs` will be passed as a `-linkedArgs` `kotlinc`
flag value (quoted) when compiling.
### Selecting library headers
When library headers are imported to a C program with the `#include` directive,
all of the headers included by these headers are also included in the program.
So all header dependencies are included in generated stubs as well.
This behavior is correct but it can be very inconvenient for some libraries. So
it is possible to specify in the `.def` file which of the included headers are to
be imported. The separate declarations from other headers can also be imported
in case of direct dependencies.
#### Filtering headers by globs
It is possible to filter headers by globs. The `headerFilter` property value
from the `.def` file is treated as a space-separated list of globs. If the
included header matches any of the globs, then the declarations from this header
are included into the bindings.
The globs are applied to the header paths relative to the appropriate include
path elements, e.g. `time.h` or `curl/curl.h`. So if the library is usually
included with `#include <SomeLibrary/Header.h>`, then it would probably be
correct to filter headers with
<div class="sample" markdown="1" theme="idea" mode="c">
```c
headerFilter = SomeLibrary/**
```
</div>
If a `headerFilter` is not specified, then all headers are included.
#### Filtering by module maps
Some libraries have proper `module.modulemap` or `module.map` files in its
headers. For example, macOS and iOS system libraries and frameworks do.
The [module map file](https://clang.llvm.org/docs/Modules.html#module-map-language)
describes the correspondence between header files and modules. When the module
maps are available, the headers from the modules that are not included directly
can be filtered out using the experimental `excludeDependentModules` option of the
`.def` file:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
headers = OpenGL/gl.h OpenGL/glu.h GLUT/glut.h
compilerOpts = -framework OpenGL -framework GLUT
excludeDependentModules = true
```
</div>
When both `excludeDependentModules` and `headerFilter` are used, they are
applied as an intersection.
### C compiler and linker options ###
Options passed to the C compiler (used to analyze headers, such as preprocessor definitions) and the linker
(used to link final executables) can be passed in the definition file as `compilerOpts` and `linkerOpts`
respectively. For example
<div class="sample" markdown="1" theme="idea" mode="c">
```c
compilerOpts = -DFOO=bar
linkerOpts = -lpng
```
</div>
Target-specific options, only applicable to the certain target can be specified as well, such as
<div class="sample" markdown="1" theme="idea" mode="c">
```c
compilerOpts = -DBAR=bar
compilerOpts.linux_x64 = -DFOO=foo1
compilerOpts.mac_x64 = -DFOO=foo2
```
</div>
and so, C headers on Linux will be analyzed with `-DBAR=bar -DFOO=foo1` and on macOS with `-DBAR=bar -DFOO=foo2`.
Note that any definition file option can have both common and the platform-specific part.
### Adding custom declarations ###
Sometimes it is required to add custom C declarations to the library before
generating bindings (e.g., for [macros](#macros)). Instead of creating an
additional header file with these declarations, you can include them directly
to the end of the `.def` file, after a separating line, containing only the
separator sequence `---`:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
headers = errno.h
---
static inline int getErrno() {
return errno;
}
```
</div>
Note that this part of the `.def` file is treated as part of the header file, so
functions with the body should be declared as `static`.
The declarations are parsed after including the files from the `headers` list.
### Including static library in your klib
Sometimes it is more convenient to ship a static library with your product,
rather than assume it is available within the user's environment.
To include a static library into `.klib` use `staticLibrary` and `libraryPaths`
clauses. For example:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
headers = foo.h
staticLibraries = libfoo.a
libraryPaths = /opt/local/lib /usr/local/opt/curl/lib
```
</div>
When given the above snippet the `cinterop` tool will search `libfoo.a` in
`/opt/local/lib` and `/usr/local/opt/curl/lib`, and if it is found include the
library binary into `klib`.
When using such `klib` in your program, the library is linked automatically.
## Using bindings ##
### Basic interop types ###
All the supported C types have corresponding representations in Kotlin:
* Signed, unsigned integral, and floating point types are mapped to their
Kotlin counterpart with the same width.
* Pointers and arrays are mapped to `CPointer<T>?`.
* Enums can be mapped to either Kotlin enum or integral values, depending on
heuristics and the [definition file hints](#definition-file-hints).
* Structs / unions are mapped to types having fields available via the dot notation,
i.e. `someStructInstance.field1`.
* `typedef` are represented as `typealias`.
Also, any C type has the Kotlin type representing the lvalue of this type,
i.e., the value located in memory rather than a simple immutable self-contained
value. Think C++ references, as a similar concept.
For structs (and `typedef`s to structs) this representation is the main one
and has the same name as the struct itself, for Kotlin enums it is named
`${type}Var`, for `CPointer<T>` it is `CPointerVar<T>`, and for most other
types it is `${type}Var`.
For types that have both representations, the one with a "lvalue" has a mutable
`.value` property for accessing the value.
#### Pointer types ####
The type argument `T` of `CPointer<T>` must be one of the "lvalue" types
described above, e.g., the C type `struct S*` is mapped to `CPointer<S>`,
`int8_t*` is mapped to `CPointer<int_8tVar>`, and `char**` is mapped to
`CPointer<CPointerVar<ByteVar>>`.
C null pointer is represented as Kotlin's `null`, and the pointer type
`CPointer<T>` is not nullable, but the `CPointer<T>?` is. The values of this
type support all the Kotlin operations related to handling `null`, e.g. `?:`, `?.`,
`!!` etc.:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val path = getenv("PATH")?.toKString() ?: ""
```
</div>
Since the arrays are also mapped to `CPointer<T>`, it supports the `[]` operator
for accessing values by index:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
fun shift(ptr: CPointer<BytePtr>, length: Int) {
for (index in 0 .. length - 2) {
ptr[index] = ptr[index + 1]
}
}
```
</div>
The `.pointed` property for `CPointer<T>` returns the lvalue of type `T`,
pointed by this pointer. The reverse operation is `.ptr`: it takes the lvalue
and returns the pointer to it.
`void*` is mapped to `COpaquePointer` the special pointer type which is the
supertype for any other pointer type. So if the C function takes `void*`, then
the Kotlin binding accepts any `CPointer`.
Casting a pointer (including `COpaquePointer`) can be done with
`.reinterpret<T>`, e.g.:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val intPtr = bytePtr.reinterpret<IntVar>()
```
</div>
or
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val intPtr: CPointer<IntVar> = bytePtr.reinterpret()
```
</div>
As is with C, these reinterpret casts are unsafe and can potentially lead to
subtle memory problems in the application.
Also there are unsafe casts between `CPointer<T>?` and `Long` available,
provided by the `.toLong()` and `.toCPointer<T>()` extension methods:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val longValue = ptr.toLong()
val originalPtr = longValue.toCPointer<T>()
```
</div>
Note that if the type of the result is known from the context, the type argument
can be omitted as usual due to the type inference.
### Memory allocation ###
The native memory can be allocated using the `NativePlacement` interface, e.g.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val byteVar = placement.alloc<ByteVar>()
```
</div>
or
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val bytePtr = placement.allocArray<ByteVar>(5)
```
</div>
The most "natural" placement is in the object `nativeHeap`.
It corresponds to allocating native memory with `malloc` and provides an additional
`.free()` operation to free allocated memory:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val buffer = nativeHeap.allocArray<ByteVar>(size)
<use buffer>
nativeHeap.free(buffer)
```
</div>
However, the lifetime of allocated memory is often bound to the lexical scope.
It is possible to define such scope with `memScoped { ... }`.
Inside the braces, the temporary placement is available as an implicit receiver,
so it is possible to allocate native memory with `alloc` and `allocArray`,
and the allocated memory will be automatically freed after leaving the scope.
For example, the C function returning values through pointer parameters can be
used like
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val fileSize = memScoped {
val statBuf = alloc<stat>()
val error = stat("/", statBuf.ptr)
statBuf.st_size
}
```
</div>
### Passing pointers to bindings ###
Although C pointers are mapped to the `CPointer<T>` type, the C function
pointer-typed parameters are mapped to `CValuesRef<T>`. When passing
`CPointer<T>` as the value of such a parameter, it is passed to the C function as is.
However, the sequence of values can be passed instead of a pointer. In this case
the sequence is passed "by value", i.e., the C function receives the pointer to
the temporary copy of that sequence, which is valid only until the function returns.
The `CValuesRef<T>` representation of pointer parameters is designed to support
C array literals without explicit native memory allocation.
To construct the immutable self-contained sequence of C values, the following
methods are provided:
* `${type}Array.toCValues()`, where `type` is the Kotlin primitive type
* `Array<CPointer<T>?>.toCValues()`, `List<CPointer<T>?>.toCValues()`
* `cValuesOf(vararg elements: ${type})`, where `type` is a primitive or pointer
For example:
C:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
void foo(int* elements, int count);
...
int elements[] = {1, 2, 3};
foo(elements, 3);
```
</div>
Kotlin:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
foo(cValuesOf(1, 2, 3), 3)
```
</div>
### Working with the strings ###
Unlike other pointers, the parameters of type `const char*` are represented as
a Kotlin `String`. So it is possible to pass any Kotlin string to a binding
expecting a C string.
There are also some tools available to convert between Kotlin and C strings
manually:
* `fun CPointer<ByteVar>.toKString(): String`
* `val String.cstr: CValuesRef<ByteVar>`.
To get the pointer, `.cstr` should be allocated in native memory, e.g.
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```
val cString = kotlinString.cstr.getPointer(nativeHeap)
```
</div>
In all cases, the C string is supposed to be encoded as UTF-8.
To skip automatic conversion and ensure raw pointers are used in the bindings, a `noStringConversion`
statement in the `.def` file could be used, i.e.
<div class="sample" markdown="1" theme="idea" mode="c">
```c
noStringConversion = LoadCursorA LoadCursorW
```
</div>
This way any value of type `CPointer<ByteVar>` can be passed as an argument of `const char*` type.
If a Kotlin string should be passed, code like this could be used:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
memScoped {
LoadCursorA(null, "cursor.bmp".cstr.ptr) // for ASCII version
LoadCursorW(null, "cursor.bmp".wcstr.ptr) // for Unicode version
}
```
</div>
### Scope-local pointers ###
It is possible to create a scope-stable pointer of C representation of `CValues<T>`
instance using the `CValues<T>.ptr` extension property, available under `memScoped { ... }`.
It allows using the APIs which require C pointers with a lifetime bound to a certain `MemScope`. For example:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
memScoped {
items = arrayOfNulls<CPointer<ITEM>?>(6)
arrayOf("one", "two").forEachIndexed { index, value -> items[index] = value.cstr.ptr }
menu = new_menu("Menu".cstr.ptr, items.toCValues().ptr)
...
}
```
</div>
In this example, all values passed to the C API `new_menu()` have a lifetime of the innermost `memScope`
it belongs to. Once the control flow leaves the `memScoped` scope the C pointers become invalid.
### Passing and receiving structs by value ###
When a C function takes or returns a struct / union `T` by value, the corresponding
argument type or return type is represented as `CValue<T>`.
`CValue<T>` is an opaque type, so the structure fields cannot be accessed with
the appropriate Kotlin properties. It should be possible, if an API uses structures
as handles, but if field access is required, there are the following conversion
methods available:
* `fun T.readValue(): CValue<T>`. Converts (the lvalue) `T` to a `CValue<T>`.
So to construct the `CValue<T>`, `T` can be allocated, filled, and then
converted to `CValue<T>`.
* `CValue<T>.useContents(block: T.() -> R): R`. Temporarily places the
`CValue<T>` to memory, and then runs the passed lambda with this placed
value `T` as receiver. So to read a single field, the following code can be
used:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val fieldValue = structValue.useContents { field }
```
</div>
### Callbacks ###
To convert a Kotlin function to a pointer to a C function,
`staticCFunction(::kotlinFunction)` can be used. It is also able to provide
the lambda instead of a function reference. The function or lambda must not
capture any values.
If the callback doesn't run in the main thread, it is mandatory to init the _Kotlin/Native_
runtime by calling `kotlin.native.initRuntimeIfNeeded()`.
#### Passing user data to callbacks ####
Often C APIs allow passing some user data to callbacks. Such data is usually
provided by the user when configuring the callback. It is passed to some C function
(or written to the struct) as e.g. `void*`.
However, references to Kotlin objects can't be directly passed to C.
So they require wrapping before configuring the callback and then unwrapping in
the callback itself, to safely swim from Kotlin to Kotlin through the C world.
Such wrapping is possible with `StableRef` class.
To wrap the reference:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val stableRef = StableRef.create(kotlinReference)
val voidPtr = stableRef.asCPointer()
```
</div>
where the `voidPtr` is a `COpaquePointer` and can be passed to the C function.
To unwrap the reference:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
val stableRef = voidPtr.asStableRef<KotlinClass>()
val kotlinReference = stableRef.get()
```
</div>
where `kotlinReference` is the original wrapped reference.
The created `StableRef` should eventually be manually disposed using
the `.dispose()` method to prevent memory leaks:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
stableRef.dispose()
```
</div>
After that it becomes invalid, so `voidPtr` can't be unwrapped anymore.
See the `samples/libcurl` for more details.
### Macros ###
Every C macro that expands to a constant is represented as a Kotlin property.
Other macros are not supported. However, they can be exposed manually by
wrapping them with supported declarations. E.g. function-like macro `FOO` can be
exposed as function `foo` by
[adding the custom declaration](#adding-custom-declarations) to the library:
<div class="sample" markdown="1" theme="idea" mode="c">
```c
headers = library/base.h
---
static inline int foo(int arg) {
return FOO(arg);
}
```
</div>
### Definition file hints ###
The `.def` file supports several options for adjusting the generated bindings.
* `excludedFunctions` property value specifies a space-separated list of the names
of functions that should be ignored. This may be required because a function
declared in the C header is not generally guaranteed to be really callable, and
it is often hard or impossible to figure this out automatically. This option
can also be used to workaround a bug in the interop itself.
* `strictEnums` and `nonStrictEnums` properties values are space-separated
lists of the enums that should be generated as a Kotlin enum or as integral
values correspondingly. If the enum is not included into any of these lists,
then it is generated according to the heuristics.
* `noStringConversion` property value is space-separated lists of the functions whose
`const char*` parameters shall not be autoconverted as Kotlin string
### Portability ###
Sometimes the C libraries have function parameters or struct fields of a
platform-dependent type, e.g. `long` or `size_t`. Kotlin itself doesn't provide
neither implicit integer casts nor C-style integer casts (e.g.
`(size_t) intValue`), so to make writing portable code in such cases easier,
the `convert` method is provided:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
fun ${type1}.convert<${type2}>(): ${type2}
```
</div>
where each of `type1` and `type2` must be an integral type, either signed or unsigned.
`.convert<${type}>` has the same semantics as one of the
`.toByte`, `.toShort`, `.toInt`, `.toLong`,
`.toUByte`, `.toUShort`, `.toUInt` or `.toULong`
methods, depending on `type`.
The example of using `convert`:
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
fun zeroMemory(buffer: COpaquePointer, size: Int) {
memset(buffer, 0, size.convert<size_t>())
}
```
</div>
Also, the type parameter can be inferred automatically and so may be omitted
in some cases.
### Object pinning ###
Kotlin objects could be pinned, i.e. their position in memory is guaranteed to be stable
until unpinned, and pointers to such objects inner data could be passed to the C functions. For example
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
fun readData(fd: Int): String {
val buffer = ByteArray(1024)
buffer.usePinned { pinned ->
while (true) {
val length = recv(fd, pinned.addressOf(0), buffer.size.convert(), 0).toInt()
if (length <= 0) {
break
}
// Now `buffer` has raw data obtained from the `recv()` call.
}
}
}
```
</div>
Here we use service function `usePinned`, which pins an object, executes block and unpins it on normal and
exception paths.
+74
View File
@@ -0,0 +1,74 @@
# Symbolicating iOS crash reports
Debugging an iOS application crash sometimes involves analyzing crash reports.
More info about crash reports can be found
[in the official documentation](https://developer.apple.com/library/archive/technotes/tn2151/_index.html).
Crash reports generally require symbolication to become properly readable:
symbolication turns machine code addresses into human-readable source locations.
The document below describes some specific details of symbolicating crash reports
from iOS applications using Kotlin.
## Producing .dSYM for release Kotlin binaries
To symbolicate addresses in Kotlin code (e.g. for stack trace elements
corresponding to Kotlin code) `.dSYM` bundle for Kotlin code is required.
By default Kotlin/Native compiler produces `.dSYM` for release
(i.e. optimized) binaries on Darwin platforms. This can be disabled with `-Xadd-light-debug=disable`
compiler flag. At the same time this option is disabled by default for other platforms, to enable it use `-Xadd-light-debug=enable`.
To control option in Gradle, use
```kotlin
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.all {
freeCompilerArgs += "-Xadd-light-debug={enable|disable}"
}
}
}
```
(in Kotlin DSL).
In projects created from IntelliJ IDEA or AppCode templates these `.dSYM` bundles
are then discovered by Xcode automatically.
## Make frameworks static when using rebuild from bitcode
Rebuilding Kotlin-produced framework from bitcode invalidates the original `.dSYM`.
If it is performed locally, make sure the updated `.dSYM` is used when symbolicating
crash reports.
If rebuilding is performed on App Store side, then `.dSYM` of rebuilt *dynamic* framework
seems discarded and not downloadable from App Store Connect.
So in this case it may be required to make the framework static, e.g. with
```kotlin
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
binaries.withType<org.jetbrains.kotlin.gradle.plugin.mpp.Framework> {
isStatic = true
}
}
}
```
(in Kotlin DSL).
## Decode inlined stack frames
Xcode doesn't seem to properly decode stack trace elements of inlined function
calls (these aren't only Kotlin `inline` functions but also functions that are
inlined when optimizing machine code). So some stack trace elements may be
missing. If this is the case, consider using `lldb` to process crash report
that is already symbolicated by Xcode, for example:
```bash
$ lldb -b -o "script import lldb.macosx" -o "crashlog file.crash"
```
This command should output crash report that is additionally processed and
includes inlined stack trace elements.
More details can be found in [LLDB documentation](https://lldb.llvm.org/use/symbolication.html).
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$/.." />
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/Indexer" />
<option value="$PROJECT_DIR$/Runtime" />
<option value="$PROJECT_DIR$/StubGenerator" />
<option value="$PROJECT_DIR$/../InteropExample" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-runtime/1.0.3/10f40d016700cf4287e49fa1d51c2a8507e9b946/kotlin-runtime-1.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-runtime/1.0.3/44d99f6d9a1d69f25797590cdd3efe2cbce8bcb6/kotlin-runtime-1.0.3-sources.jar!/" />
</SOURCES>
</library>
</component>
@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.0.3/20738122b53399036c321eeb84687367757d622a/kotlin-stdlib-1.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.0.3/efeac5a1b15300f742d2119667f90df44230b94a/kotlin-stdlib-1.0.3-sources.jar!/" />
</SOURCES>
</library>
</component>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Indexer/Indexer.iml" filepath="$PROJECT_DIR$/.idea/modules/Indexer/Indexer.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Runtime/Runtime.iml" filepath="$PROJECT_DIR$/.idea/modules/Runtime/Runtime.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/StubGenerator/StubGenerator.iml" filepath="$PROJECT_DIR$/.idea/modules/StubGenerator/StubGenerator.iml" />
</modules>
</component>
</project>
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":Interop:Indexer" external.linked.project.path="$MODULE_DIR$/../../../Indexer" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" external.system.module.group="experiments.Interop" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../../Indexer/build/classes/main" />
<output-test url="file://$MODULE_DIR$/../../../Indexer/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../../Indexer">
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/prebuilt/nativeInteropStubs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/../../../Indexer/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../../Indexer/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="Runtime" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3" level="project" />
</component>
</module>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":Interop:Runtime" external.linked.project.path="$MODULE_DIR$/../../../Runtime" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" external.system.module.group="experiments.Interop" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../../Runtime/build/classes/main" />
<output-test url="file://$MODULE_DIR$/../../../Runtime/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../../Runtime">
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/../../../Runtime/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../../Runtime/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3" level="project" />
</component>
</module>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":Interop:StubGenerator" external.linked.project.path="$MODULE_DIR$/../../../StubGenerator" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" external.system.module.group="experiments.Interop" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../../StubGenerator/build/classes/main" />
<output-test url="file://$MODULE_DIR$/../../../StubGenerator/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../../StubGenerator">
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/../../../StubGenerator/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../../StubGenerator/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="Indexer" />
<orderEntry type="module" module-name="Runtime" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3" level="project" />
</component>
</module>
+173
View File
@@ -0,0 +1,173 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
buildscript {
ext.rootBuildDirectory = file('../..')
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
dependencies {
classpath "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
}
}
apply plugin: 'kotlin'
apply plugin: org.jetbrains.kotlin.NativeInteropPlugin
apply plugin: 'c'
apply plugin: 'cpp'
import org.jetbrains.kotlin.konan.target.ClangArgs
final Project libclangextProject = project(":libclangext")
final String libclangextTask = libclangextProject.path + ":build"
File libclangextDir = new File(libclangextProject.buildDir, "libs/clangext/static")
final boolean libclangextIsEnabled = libclangextProject.isEnabled
final String libclang
if (isWindows()) {
libclang = "bin/libclang.dll"
} else {
libclang = "lib/${System.mapLibraryName("clang")}"
}
List<String> cflags = [
"-I$llvmDir/include",
"-I${project(":libclangext").projectDir.absolutePath + "/src/main/include"}"
]*.toString()
List<String> ldflags = ["$llvmDir/$libclang", "-L$libclangextDir.absolutePath", "-lclangext"]*.toString()
if (libclangextIsEnabled) {
assert(isMac())
ldflags.addAll(['-Wl,--no-demangle', '-Wl,-search_paths_first', '-Wl,-headerpad_max_install_names', '-Wl,-U,_futimens',
'-Wl,-U,__ZN4llvm7remarks11parseFormatENS_9StringRefE',
'-Wl,-U,__ZN4llvm7remarks22createRemarkSerializerENS0_6FormatENS0_14SerializerModeERNS_11raw_ostreamE',
'-Wl,-U,__ZN4llvm7remarks14YAMLSerializerC1ERNS_11raw_ostreamENS0_14UseStringTableE'])
List<String> llvmLibs = [
"clangAST", "clangASTMatchers", "clangAnalysis", "clangBasic", "clangDriver", "clangEdit",
"clangFrontend", "clangFrontendTool", "clangLex", "clangParse", "clangSema", "clangEdit",
"clangRewrite", "clangRewriteFrontend", "clangStaticAnalyzerFrontend",
"clangStaticAnalyzerCheckers", "clangStaticAnalyzerCore", "clangSerialization",
"clangToolingCore",
"clangTooling", "clangFormat", "LLVMTarget", "LLVMMC", "LLVMLinker", "LLVMTransformUtils",
"LLVMBitWriter", "LLVMBitReader", "LLVMAnalysis", "LLVMProfileData", "LLVMCore",
"LLVMSupport", "LLVMBinaryFormat", "LLVMDemangle"
].collect { "$llvmDir/lib/lib${it}.a".toString() }
ldflags.addAll(llvmLibs)
ldflags.addAll(['-lpthread', '-lz', '-lm', '-lcurses'])
}
model {
components {
clangstubs(NativeLibrarySpec) {
sources {
c.source.srcDir 'prebuilt/nativeInteropStubs/c'
cpp.source.srcDir 'src/nativeInteropStubs/cpp'
}
binaries.all {
cCompiler.args hostPlatform.clang.hostCompilerArgsForJni
cCompiler.args.addAll(cflags)
}
binaries.withType(SharedLibraryBinarySpec) {
linker.args.addAll(ldflags)
}
}
}
toolChains {
clang(Clang) {
eachPlatform {
cppCompiler.withArguments(ClangArgs.&filterGradleNativeSoftwareFlags)
cCompiler.withArguments(ClangArgs.&filterGradleNativeSoftwareFlags)
}
}
}
}
sourceSets {
main {
kotlin {
srcDirs 'prebuilt/nativeInteropStubs/kotlin'
}
}
}
repositories {
maven {
url buildKotlinCompilerRepo
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compile project(':Interop:Runtime')
}
task nativelibs(type: Copy) {
dependsOn 'clangstubsSharedLibrary'
from "$buildDir/libs/clangstubs/shared/"
into "$buildDir/nativelibs/"
}
classes.dependsOn nativelibs
kotlinNativeInterop {
clang {
defFile 'clang.def'
compilerOpts cflags
linkerOpts ldflags
genTask.dependsOn libclangextTask
genTask.inputs.dir libclangextDir
}
}
compileKotlin {
kotlinOptions {
allWarningsAsErrors=true
}
}
tasks.matching { it.name == 'linkClangstubsSharedLibrary' }.all {
it.dependsOn libclangextTask
it.inputs.dir libclangextDir
}
task updatePrebuilt {
dependsOn genClangInteropStubs
doLast {
copy {
from("$buildDir/nativeInteropStubs/clang/kotlin") {
include 'clang/clang.kt'
}
into 'prebuilt/nativeInteropStubs/kotlin'
}
copy {
from("$buildDir/interopTemp") {
include 'clangstubs.c'
}
into 'prebuilt/nativeInteropStubs/c'
}
}
}
+16
View File
@@ -0,0 +1,16 @@
headers = clang-c/Index.h clang-c/ext.h clang-c/ExtVector.h
headerFilter = clang-c/**
compiler = clang
compilerOpts = -std=c99 -fPIC
linkerOpts.linux = -Wl,-z,noexecstack
linker = clang++
linkerOpts = -fPIC
strictEnums = CXErrorCode CXCursorKind CXTypeKind CXDiagnosticSeverity CXLoadDiag_Error CXSaveError \
CXTUResourceUsageKind CXLinkageKind CXVisibilityKind CXLanguageKind CXCallingConv CXChildVisitResult \
CXTokenKind CXEvalResultKind CXVisitorResult CXResult CXIdxEntityKind
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.indexer
import java.io.File
class HeaderToIdMapper(sysRoot: String) {
private val headerPathToId = mutableMapOf<String, HeaderId>()
private val sysRoot = File(sysRoot).canonicalFile.toPath()
internal fun getHeaderId(filePath: String) = headerPathToId.getOrPut(filePath) {
val path = File(filePath).canonicalFile.toPath()
val headerIdValue = if (path.startsWith(sysRoot)) {
val relative = sysRoot.relativize(path)
relative.toString()
} else {
headerContentsHash(filePath)
}
HeaderId(headerIdValue)
}
}
@@ -0,0 +1,322 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
import java.io.File
/**
* Finds all "macro constants" and registers them as [NativeIndex.constants] in given index.
*/
internal fun findMacros(
nativeIndex: NativeIndexImpl,
compilation: CompilationWithPCH,
translationUnit: CXTranslationUnit,
headers: Set<CXFile?>
) {
val names = collectMacroNames(nativeIndex, translationUnit, headers)
// TODO: apply user-defined filters.
val macros = expandMacros(compilation, names, typeConverter = { nativeIndex.convertType(it) })
macros.filterIsInstanceTo(nativeIndex.macroConstants)
macros.filterIsInstanceTo(nativeIndex.wrappedMacros)
}
private typealias TypeConverter = (CValue<CXType>) -> Type
/**
* For each name expands the macro with this name declared in the library,
* checking if it gets expanded to a constant expression.
*
* Note: in the worst case this method parses the code against the library a lot of times,
* so it requires library headers precompiled to significantly speed up the parsing and avoid visiting headers' AST.
*
* @return the list of constants.
*/
private fun expandMacros(
library: CompilationWithPCH,
names: List<String>,
typeConverter: TypeConverter
): List<MacroDef> {
withIndex(excludeDeclarationsFromPCH = true) { index ->
val sourceFile = library.createTempSource()
val compilerArgs = library.compilerArgs.toMutableList()
// We disable implicit function declaration to filter out cases when a macro is expanded as a function
// or function-like construction (e.g. #define FOO throw()) but such a function is undeclared.
compilerArgs += "-Werror=implicit-function-declaration"
// Ensure libclang reports all errors:
compilerArgs += "-ferror-limit=0"
val translationUnit = parseTranslationUnit(index, sourceFile, compilerArgs, options = 0)
try {
val nameToMacroDef = mutableMapOf<String, MacroDef>()
val unprocessedMacros = names.toMutableList()
// Note: will be slow for a library with a lot of macros having unbalanced '{'. TODO: Optimize this case too.
while (unprocessedMacros.isNotEmpty()) {
val processedMacros =
tryExpandMacros(library, translationUnit, sourceFile, unprocessedMacros, typeConverter)
unprocessedMacros -= (processedMacros.keys + unprocessedMacros.first())
// Note: removing first macro should not have any effect, doing this to ensure the loop is finite.
processedMacros.forEach { (name, macroDef) ->
if (macroDef != null) nameToMacroDef[name] = macroDef
}
}
return names.mapNotNull { nameToMacroDef[it] }
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
}
/**
* Tries to expand macros [names] defined in [library].
* Returns the map of successfully processed macros with resulting constant as a value
* or `null` if the result is not a constant (expression).
*
* As a side effect, modifies the [sourceFile] and reparses the [translationUnit].
*/
private fun tryExpandMacros(
library: CompilationWithPCH,
translationUnit: CXTranslationUnit,
sourceFile: File,
names: List<String>,
typeConverter: TypeConverter
): Map<String, MacroDef?> {
reparseWithCodeSnippets(library, translationUnit, sourceFile, names)
val macrosWithErrorsInSnippetFunctionHeader = mutableSetOf<String>()
val macrosWithErrorsInSnippetFunctionBody = mutableSetOf<String>()
val preambleSize = library.preambleLines.size
translationUnit.getErrorLineNumbers().map { it - preambleSize - 1 }.forEach { lineNumber ->
val index = lineNumber / CODE_SNIPPET_LINES_NUMBER
if (index >= 0 && index < names.size) {
when (lineNumber % CODE_SNIPPET_LINES_NUMBER) {
0 -> macrosWithErrorsInSnippetFunctionHeader += names[index]
1 -> macrosWithErrorsInSnippetFunctionBody += names[index]
else -> {}
}
}
}
val result = mutableMapOf<String, MacroDef?>()
visitChildren(translationUnit) { cursor, _ ->
if (cursor.kind == CXCursorKind.CXCursor_FunctionDecl) {
val functionName = getCursorSpelling(cursor)
if (functionName.startsWith(CODE_SNIPPET_FUNCTION_NAME_PREFIX)) {
val macroName = functionName.removePrefix(CODE_SNIPPET_FUNCTION_NAME_PREFIX)
if (macroName in macrosWithErrorsInSnippetFunctionHeader) {
// Code snippet is likely affected by previous macros' snippets, skip it for now.
} else {
result[macroName] = if (macroName in macrosWithErrorsInSnippetFunctionBody) {
// Code snippet is likely unaffected by previous ones but parsed with its own errors,
// so suppose macro is processed successfully as non-expression:
null
} else {
processCodeSnippet(cursor, macroName, typeConverter)
}
}
}
}
CXChildVisitResult.CXChildVisit_Continue
}
return result
}
private const val CODE_SNIPPET_LINES_NUMBER = 3
private const val CODE_SNIPPET_FUNCTION_NAME_PREFIX = "kni_indexer_function_"
/**
* Adds code snippets to be then processed with [processCodeSnippet] to the [sourceFile]
* and reparses the [translationUnit].
*
* - If a code snippet allows extracting the constant value using libclang API, we'll add a [ConstantDef] in the
* native index and generate a Kotlin constant for it.
* - If the expression type can be inferred by libclang, we'll add a [WrappedMacroDef] in the native index and
* generate a bridge for this macro.
* - Otherwise the macro is skipped.
*/
private fun reparseWithCodeSnippets(library: CompilationWithPCH,
translationUnit: CXTranslationUnit, sourceFile: File,
names: List<String>) {
// TODO: consider using CXUnsavedFile instead of writing the modified file to OS file system.
sourceFile.bufferedWriter().use { writer ->
writer.appendPreamble(library)
names.forEach { name ->
val codeSnippetLines = when (library.language) {
Language.C, Language.OBJECTIVE_C ->
listOf("void $CODE_SNIPPET_FUNCTION_NAME_PREFIX$name() {",
" __auto_type KNI_INDEXER_VARIABLE_$name = $name;",
"}")
}
assert(codeSnippetLines.size == CODE_SNIPPET_LINES_NUMBER)
codeSnippetLines.forEach { writer.appendLine(it) }
}
}
clang_reparseTranslationUnit(translationUnit, 0, null, 0)
}
/**
* Checks that [functionCursor] is parsed exactly as expected for the code appended by [reparseWithCodeSnippets],
* and returns the constant on success.
*/
private fun processCodeSnippet(
functionCursor: CValue<CXCursor>,
name: String,
typeConverter: TypeConverter
): MacroDef? {
val kindsToSkip = setOf(CXCursorKind.CXCursor_CompoundStmt)
var state = VisitorState.EXPECT_NODES_TO_SKIP
var evalResultOrNull: CXEvalResult? = null
var typeOrNull: Type? = null
val visitor: CursorVisitor = { cursor, _ ->
val kind = cursor.kind
when {
state == VisitorState.EXPECT_VARIABLE && kind == CXCursorKind.CXCursor_VarDecl -> {
evalResultOrNull = clang_Cursor_Evaluate(cursor)
state = VisitorState.EXPECT_VARIABLE_VALUE
CXChildVisitResult.CXChildVisit_Recurse
}
state == VisitorState.EXPECT_VARIABLE_VALUE && clang_isExpression(kind) != 0 -> {
typeOrNull = typeConverter(clang_getCursorType(cursor))
state = VisitorState.EXPECT_END
CXChildVisitResult.CXChildVisit_Continue
}
// Skip auxiliary elements.
state == VisitorState.EXPECT_NODES_TO_SKIP && kind in kindsToSkip ->
CXChildVisitResult.CXChildVisit_Recurse
state == VisitorState.EXPECT_NODES_TO_SKIP && kind == CXCursorKind.CXCursor_DeclStmt -> {
state = VisitorState.EXPECT_VARIABLE
CXChildVisitResult.CXChildVisit_Recurse
}
else -> {
state = VisitorState.INVALID
CXChildVisitResult.CXChildVisit_Break
}
}
}
try {
visitChildren(functionCursor, visitor)
if (state != VisitorState.EXPECT_END) {
return null
}
val type = typeOrNull!!
return if (evalResultOrNull == null) {
// The macro cannot be evaluated as a constant so we will wrap it in a bridge.
when(type.unwrapTypedefs()) {
is PrimitiveType,
is PointerType,
is ObjCPointer -> WrappedMacroDef(name, type)
else -> null
}
} else {
// Otherwise we can evaluate the expression and create a Kotlin constant for it.
val evalResult = evalResultOrNull!!
val evalResultKind = clang_EvalResult_getKind(evalResult)
when (evalResultKind) {
CXEvalResultKind.CXEval_Int ->
IntegerConstantDef(name, type, clang_EvalResult_getAsLongLong(evalResult))
CXEvalResultKind.CXEval_Float ->
FloatingConstantDef(name, type, clang_EvalResult_getAsDouble(evalResult))
CXEvalResultKind.CXEval_CFStr,
CXEvalResultKind.CXEval_ObjCStrLiteral,
CXEvalResultKind.CXEval_StrLiteral ->
if (evalResultKind == CXEvalResultKind.CXEval_StrLiteral && !type.canonicalIsPointerToChar()) {
// libclang doesn't seem to support wide string literals properly in this API;
// thus disable wide literals here:
null
} else {
StringConstantDef(name, type, clang_EvalResult_getAsStr(evalResult)!!.toKString())
}
CXEvalResultKind.CXEval_Other,
CXEvalResultKind.CXEval_UnExposed -> null
}
}
} finally {
evalResultOrNull?.let { clang_EvalResult_dispose(it) }
}
}
enum class VisitorState {
EXPECT_NODES_TO_SKIP,
EXPECT_VARIABLE, EXPECT_VARIABLE_VALUE,
EXPECT_END, INVALID
}
private fun collectMacroNames(nativeIndex: NativeIndexImpl, translationUnit: CXTranslationUnit, headers: Set<CXFile?>): List<String> {
val result = mutableSetOf<String>()
visitChildren(translationUnit) { cursor, _ ->
val file = memScoped {
val fileVar = alloc<CXFileVar>()
clang_getFileLocation(clang_getCursorLocation(cursor), fileVar.ptr, null, null, null)
fileVar.value
}
if (cursor.kind == CXCursorKind.CXCursor_MacroDefinition &&
nativeIndex.library.includesDeclaration(cursor) &&
file != null && // Builtin macros mostly seem to be useless.
file in headers &&
canMacroBeConstant(cursor))
{
val spelling = getCursorSpelling(cursor)
result.add(spelling)
}
CXChildVisitResult.CXChildVisit_Continue
}
return result.toList()
}
private fun canMacroBeConstant(cursor: CValue<CXCursor>): Boolean {
if (clang_Cursor_isMacroFunctionLike(cursor) != 0) {
return false
}
// TODO: check number of tokens and filter out empty definitions;
// Requires updating to 3.9.1 due to https://bugs.llvm.org//show_bug.cgi?id=9069
return true
}
@@ -0,0 +1,138 @@
package org.jetbrains.kotlin.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
data class ModulesInfo(val topLevelHeaders: List<String>, val ownHeaders: Set<String>)
fun getModulesInfo(compilation: Compilation, modules: List<String>): ModulesInfo {
if (modules.isEmpty()) return ModulesInfo(emptyList(), emptySet())
withIndex { index ->
ModularCompilation(compilation).use {
val modulesASTFiles = getModulesASTFiles(index, it, modules)
return buildModulesInfo(index, modules, modulesASTFiles)
}
}
}
private fun buildModulesInfo(index: CXIndex, modules: List<String>, modulesASTFiles: List<String>): ModulesInfo {
val ownHeaders = mutableSetOf<String>()
val topLevelHeaders = linkedSetOf<String>()
modulesASTFiles.forEach {
val moduleTranslationUnit = clang_createTranslationUnit(index, it)!!
try {
val modulesHeaders = getModulesHeaders(index, moduleTranslationUnit, modules.toSet(), topLevelHeaders)
modulesHeaders.mapTo(ownHeaders) { it.canonicalPath }
} finally {
clang_disposeTranslationUnit(moduleTranslationUnit)
}
}
return ModulesInfo(topLevelHeaders.toList(), ownHeaders)
}
internal open class ModularCompilation(compilation: Compilation): Compilation by compilation, Disposable {
companion object {
private const val moduleCacheFlag = "-fmodules-cache-path="
}
private val moduleCacheDirectory = if (compilation.compilerArgs.none { it.startsWith(moduleCacheFlag) }) {
createTempDir("ModuleCache")
} else {
null
}
override val compilerArgs: List<String> = compilation.compilerArgs +
listOfNotNull("-fmodules", moduleCacheDirectory?.let { "$moduleCacheFlag${it.absolutePath}" })
override fun dispose() {
moduleCacheDirectory?.deleteRecursively()
}
}
private fun getModulesASTFiles(index: CXIndex, compilation: ModularCompilation, modules: List<String>): List<String> {
val compilationWithImports = compilation.copy(
additionalPreambleLines = modules.map { "@import $it;" } + compilation.additionalPreambleLines
)
val result = linkedSetOf<String>()
val translationUnit = compilationWithImports.parse(
index,
options = CXTranslationUnit_DetailedPreprocessingRecord
)
try {
translationUnit.ensureNoCompileErrors()
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun importedASTFile(info: CXIdxImportedASTFileInfo) {
result += info.file!!.canonicalPath
}
})
} finally {
clang_disposeTranslationUnit(translationUnit)
}
return result.toList()
}
private fun getModulesHeaders(
index: CXIndex,
translationUnit: CXTranslationUnit,
modules: Set<String>,
topLevelHeaders: LinkedHashSet<String>
): Set<CXFile> {
val nonModularIncludes = mutableMapOf<CXFile, MutableSet<CXFile>>()
val result = mutableSetOf<CXFile>()
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
val file = info.file!!
val includer = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue()).getContainingFile()
if (includer == null) {
// i.e. the header is included by the module itself.
topLevelHeaders += file.path
}
val module = clang_getModuleForFile(translationUnit, file)
if (module != null) {
val moduleWithParents = generateSequence(module, { clang_Module_getParent(it) }).map {
clang_Module_getFullName(it).convertAndDispose()
}
if (moduleWithParents.any { it in modules }) {
result += file
}
} else if (includer != null) {
nonModularIncludes.getOrPut(includer, { mutableSetOf() }) += file
}
}
})
// There are cases when non-modular includes should also be considered as a part of module. For example:
// 1. Some module maps are broken,
// e.g. system header `IOKit/hid/IOHIDProperties.h` isn't included to framework module map at all.
// 2. Textual headers are reported as non-modular by libclang.
//
// Find and include non-modular headers too:
result += findReachable(roots = result, arcs = nonModularIncludes)
return result
}
private fun <T> findReachable(roots: Set<T>, arcs: Map<T, Set<T>>): Set<T> {
val visited = mutableSetOf<T>()
fun dfs(vertex: T) {
if (!visited.add(vertex)) return
arcs[vertex].orEmpty().forEach { dfs(it) }
}
roots.forEach { dfs(it) }
return visited
}
@@ -0,0 +1,331 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.indexer
enum class Language(val sourceFileExtension: String) {
C("c"),
OBJECTIVE_C("m")
}
interface HeaderInclusionPolicy {
/**
* Whether unused declarations from given header should be excluded.
*
* @param headerName header path relative to the appropriate include path element (e.g. `time.h` or `curl/curl.h`),
* or `null` for builtin declarations.
*/
fun excludeUnused(headerName: String?): Boolean
}
interface HeaderExclusionPolicy {
/**
* Whether all declarations from this header should be excluded.
*
* Note: the declarations from such headers can be actually present in the internal representation,
* but not included into the root collections.
*/
fun excludeAll(headerId: HeaderId): Boolean
}
sealed class NativeLibraryHeaderFilter {
class NameBased(
val policy: HeaderInclusionPolicy,
val excludeDepdendentModules: Boolean
) : NativeLibraryHeaderFilter()
class Predefined(val headers: Set<String>) : NativeLibraryHeaderFilter()
}
interface Compilation {
val includes: List<String>
val additionalPreambleLines: List<String>
val compilerArgs: List<String>
val language: Language
}
data class CompilationWithPCH(
override val compilerArgs: List<String>,
override val language: Language
) : Compilation {
constructor(compilerArgs: List<String>, precompiledHeader: String, language: Language)
: this(compilerArgs + listOf("-include-pch", precompiledHeader), language)
override val includes: List<String>
get() = emptyList()
override val additionalPreambleLines: List<String>
get() = emptyList()
}
// TODO: Compilation hierarchy seems to require some refactoring.
data class NativeLibrary(override val includes: List<String>,
override val additionalPreambleLines: List<String>,
override val compilerArgs: List<String>,
val headerToIdMapper: HeaderToIdMapper,
override val language: Language,
val excludeSystemLibs: Boolean, // TODO: drop?
val headerExclusionPolicy: HeaderExclusionPolicy,
val headerFilter: NativeLibraryHeaderFilter) : Compilation
data class IndexerResult(val index: NativeIndex, val compilation: CompilationWithPCH)
/**
* Retrieves the definitions from given C header file using given compiler arguments (e.g. defines).
*/
fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult = buildNativeIndexImpl(library, verbose)
/**
* This class describes the IR of definitions from C header file(s).
*/
abstract class NativeIndex {
abstract val structs: Collection<StructDecl>
abstract val enums: Collection<EnumDef>
abstract val objCClasses: Collection<ObjCClass>
abstract val objCProtocols: Collection<ObjCProtocol>
abstract val objCCategories: Collection<ObjCCategory>
abstract val typedefs: Collection<TypedefDef>
abstract val functions: Collection<FunctionDecl>
abstract val macroConstants: Collection<ConstantDef>
abstract val wrappedMacros: Collection<WrappedMacroDef>
abstract val globals: Collection<GlobalDecl>
abstract val includedHeaders: Collection<HeaderId>
}
/**
* The (contents-based) header id.
* Its [value] remains valid across different runs of the indexer and the process,
* and thus can be used to 'serialize' the id.
*/
data class HeaderId(val value: String)
data class Location(val headerId: HeaderId)
interface TypeDeclaration {
val location: Location
}
sealed class StructMember(val name: String, val type: Type) {
abstract val offset: Long?
}
/**
* C struct field.
*/
class Field(name: String, type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
: StructMember(name, type)
val Field.isAligned: Boolean
get() = offset % (typeAlign * 8) == 0L
class BitField(name: String, type: Type, override val offset: Long, val size: Int) : StructMember(name, type)
class IncompleteField(name: String, type: Type) : StructMember(name, type) {
override val offset: Long? get() = null
}
/**
* C struct declaration.
*/
abstract class StructDecl(val spelling: String) : TypeDeclaration {
abstract val def: StructDef?
}
/**
* C struct definition.
*
* @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`.
* May be `false` even if the struct has natural layout.
*/
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) {
enum class Kind {
STRUCT, UNION
}
abstract val members: List<StructMember>
abstract val kind: Kind
val fields: List<Field> get() = members.filterIsInstance<Field>()
val bitFields: List<BitField> get() = members.filterIsInstance<BitField>()
}
/**
* C enum value.
*/
class EnumConstant(val name: String, val value: Long, val isExplicitlyDefined: Boolean)
/**
* C enum definition.
*/
abstract class EnumDef(val spelling: String, val baseType: Type) : TypeDeclaration {
abstract val constants: List<EnumConstant>
}
sealed class ObjCContainer {
abstract val protocols: List<ObjCProtocol>
abstract val methods: List<ObjCMethod>
abstract val properties: List<ObjCProperty>
}
sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclaration {
abstract val isForwardDeclaration: Boolean
}
data class ObjCMethod(
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
val isVariadic: Boolean, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean
) {
fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
fun getReturnType(container: ObjCClassOrProtocol): Type = if (returnType is ObjCInstanceType) {
when (container) {
is ObjCClass -> ObjCObjectPointer(container, returnType.nullability, protocols = emptyList())
is ObjCProtocol -> ObjCIdType(returnType.nullability, protocols = listOf(container))
}
} else {
returnType
}
}
data class ObjCProperty(val name: String, val getter: ObjCMethod, val setter: ObjCMethod?) {
fun getType(container: ObjCClassOrProtocol): Type = getter.getReturnType(container)
}
abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) {
abstract val binaryName: String?
abstract val baseClass: ObjCClass?
}
abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name)
abstract class ObjCCategory(val name: String, val clazz: ObjCClass) : ObjCContainer()
/**
* C function parameter.
*/
data class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
/**
* C function declaration.
*/
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type, val binaryName: String,
val isDefined: Boolean, val isVararg: Boolean)
/**
* C typedef definition.
*
* ```
* typedef $aliased $name;
* ```
*/
class TypedefDef(val aliased: Type, val name: String, override val location: Location) : TypeDeclaration
abstract class MacroDef(val name: String)
abstract class ConstantDef(name: String, val type: Type): MacroDef(name)
class IntegerConstantDef(name: String, type: Type, val value: Long) : ConstantDef(name, type)
class FloatingConstantDef(name: String, type: Type, val value: Double) : ConstantDef(name, type)
class StringConstantDef(name: String, type: Type, val value: String) : ConstantDef(name, type)
class WrappedMacroDef(name: String, val type: Type) : MacroDef(name)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean)
/**
* C type.
*/
interface Type
interface PrimitiveType : Type
object CharType : PrimitiveType
open class BoolType: PrimitiveType
object CBoolType : BoolType()
object ObjCBoolType : BoolType()
// We omit `const` qualifier for IntegerType and FloatingType to make `CBridgeGen` simpler.
// See KT-28102.
data class IntegerType(val size: Int, val isSigned: Boolean, val spelling: String) : PrimitiveType
// TODO: floating type is not actually defined entirely by its size.
data class FloatingType(val size: Int, val spelling: String) : PrimitiveType
data class VectorType(val elementType: Type, val elementCount: Int, val spelling: String) : PrimitiveType
object VoidType : Type
data class RecordType(val decl: StructDecl) : Type
data class EnumType(val def: EnumDef) : Type
data class PointerType(val pointeeType: Type, val pointeeIsConst: Boolean = false) : Type
// TODO: refactor type representation and support type modifiers more generally.
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
interface ArrayType : Type {
val elemType: Type
}
data class ConstArrayType(override val elemType: Type, val length: Long) : ArrayType
data class IncompleteArrayType(override val elemType: Type) : ArrayType
data class VariableArrayType(override val elemType: Type) : ArrayType
data class Typedef(val def: TypedefDef) : Type
sealed class ObjCPointer : Type {
enum class Nullability {
Nullable, NonNull, Unspecified
}
abstract val nullability: Nullability
}
sealed class ObjCQualifiedPointer : ObjCPointer() {
abstract val protocols: List<ObjCProtocol>
}
data class ObjCObjectPointer(
val def: ObjCClass,
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCClassPointer(
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCIdType(
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCInstanceType(override val nullability: Nullability) : ObjCPointer()
data class ObjCBlockPointer(
override val nullability: Nullability,
val parameterTypes: List<Type>, val returnType: Type
) : ObjCPointer()
object UnsupportedType : Type
@@ -0,0 +1,770 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
import java.io.Closeable
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.DigestInputStream
import java.security.MessageDigest
internal val CValue<CXType>.kind: CXTypeKind get() = this.useContents { kind }
internal val CValue<CXCursor>.kind: CXCursorKind get() = this.useContents { kind }
internal val CValue<CXCursor>.type: CValue<CXType> get() = clang_getCursorType(this)
internal val CValue<CXCursor>.spelling: String get() = clang_getCursorSpelling(this).convertAndDispose()
internal val CValue<CXType>.name: String get() = clang_getTypeSpelling(this).convertAndDispose()
internal val CXTypeKind.spelling: String get() = clang_getTypeKindSpelling(this).convertAndDispose()
internal val CXCursorKind.spelling: String get() = clang_getCursorKindSpelling(this).convertAndDispose()
internal fun CValue<CXString>.convertAndDispose(): String {
try {
return clang_getCString(this)!!.toKString()
} finally {
clang_disposeString(this)
}
}
internal fun CPointer<CXStringSet>.convertAndDispose(): Set<String> = try {
(0 until this.pointed.Count).mapTo(mutableSetOf()) {
clang_getCString(this.pointed.Strings!![it].readValue())!!.toKString()
}
} finally {
clang_disposeStringSet(this)
}
internal fun getCursorSpelling(cursor: CValue<CXCursor>) =
clang_getCursorSpelling(cursor).convertAndDispose()
internal fun CValue<CXType>.getSize(): Long {
val size = clang_Type_getSizeOf(this)
if (size < 0) {
throw Error(size.toString())
}
return size
}
internal inline fun <R> withIndex(
excludeDeclarationsFromPCH: Boolean = false,
displayDiagnostics: Boolean = false,
block: (index: CXIndex) -> R
): R {
val index = clang_createIndex(
excludeDeclarationsFromPCH = if (excludeDeclarationsFromPCH) 1 else 0,
displayDiagnostics = if (displayDiagnostics) 1 else 0
)!!
return try {
block(index)
} finally {
clang_disposeIndex(index)
}
}
internal fun parseTranslationUnit(
index: CXIndex,
sourceFile: File,
compilerArgs: List<String>,
options: Int
): CXTranslationUnit {
memScoped {
val resultVar = alloc<CXTranslationUnitVar>()
val errorCode = clang_parseTranslationUnit2(
index,
sourceFile.absolutePath,
compilerArgs.toNativeStringArray(memScope), compilerArgs.size,
null, 0,
options,
resultVar.ptr
)
if (errorCode != CXErrorCode.CXError_Success) {
val copiedSourceFile = sourceFile.copyTo(createTempFile(suffix = sourceFile.name), overwrite = true)
error("""
clang_parseTranslationUnit2 failed with $errorCode;
sourceFile = ${copiedSourceFile.absolutePath}
arguments = ${compilerArgs.joinToString(" ")}
""".trimIndent())
}
return resultVar.value!!
}
}
internal fun Compilation.parse(index: CXIndex, options: Int = 0): CXTranslationUnit =
parseTranslationUnit(index, this.createTempSource(), this.compilerArgs, options)
internal data class Diagnostic(val severity: CXDiagnosticSeverity, val format: String,
val location: CValue<CXSourceLocation>)
internal fun CXTranslationUnit.getDiagnostics(): Sequence<Diagnostic> {
val numDiagnostics = clang_getNumDiagnostics(this)
return (0 until numDiagnostics).asSequence()
.map { index ->
val diagnostic = clang_getDiagnostic(this, index)
try {
val severity = clang_getDiagnosticSeverity(diagnostic)
val format = clang_formatDiagnostic(diagnostic, clang_defaultDiagnosticDisplayOptions())
.convertAndDispose()
val location = clang_getDiagnosticLocation(diagnostic)
Diagnostic(severity, format, location)
} finally {
clang_disposeDiagnostic(diagnostic)
}
}
}
internal fun CXTranslationUnit.getCompileErrors(): Sequence<String> =
getDiagnostics().filter { it.isError() }.map { it.format }
private fun Diagnostic.isError() = (severity == CXDiagnosticSeverity.CXDiagnostic_Error) ||
(severity == CXDiagnosticSeverity.CXDiagnostic_Fatal)
internal fun CXTranslationUnit.hasCompileErrors() = (this.getCompileErrors().firstOrNull() != null)
internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit {
val firstError = this.getCompileErrors().firstOrNull() ?: return this
throw Error(firstError)
}
internal typealias CursorVisitor = (cursor: CValue<CXCursor>, parent: CValue<CXCursor>) -> CXChildVisitResult
internal fun visitChildren(parent: CValue<CXCursor>, visitor: CursorVisitor) {
val visitorStableRef = StableRef.create(visitor)
try {
val clientData = visitorStableRef.asCPointer()
clang_visitChildren(parent, staticCFunction { cursorIt, parentIt, clientDataIt ->
val visitorIt = clientDataIt!!.asStableRef<CursorVisitor>().get()
visitorIt(cursorIt, parentIt)
}, clientData)
} finally {
visitorStableRef.dispose()
}
}
internal fun visitChildren(translationUnit: CXTranslationUnit, visitor: CursorVisitor) =
visitChildren(clang_getTranslationUnitCursor(translationUnit), visitor)
internal fun getFields(type: CValue<CXType>): List<CValue<CXCursor>> {
val result = mutableListOf<CValue<CXCursor>>()
val resultStableRef = StableRef.create(result)
try {
val clientData = resultStableRef.asCPointer()
@Suppress("NAME_SHADOWING")
clang_Type_visitFields(type, staticCFunction { cursor, clientData ->
val result = clientData!!.asStableRef<MutableList<CValue<CXCursor>>>().get()
result.add(cursor)
CXVisitorResult.CXVisit_Continue
}, clientData)
} finally {
resultStableRef.dispose()
}
return result
}
fun StructDef.fieldsHaveDefaultAlignment(): Boolean {
fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and (alignment - 1).inv()
var offset = 0L
this.members.forEach {
when (it) {
is Field -> {
if (alignUp(offset, it.typeAlign) * 8 != it.offset) return false
offset = it.offset / 8 + it.typeSize
}
is BitField -> return false
}
}
return true
}
internal fun CValue<CXCursor>.hasExpressionChild(): Boolean {
var result = false
visitChildren(this) { cursor, _ ->
if (clang_isExpression(cursor.kind) != 0) {
result = true
CXChildVisitResult.CXChildVisit_Break
} else {
CXChildVisitResult.CXChildVisit_Continue
}
}
return result
}
internal fun List<String>.toNativeStringArray(scope: AutofreeScope): CArrayPointer<CPointerVar<ByteVar>> {
return scope.allocArray(this.size) { index ->
this.value = this@toNativeStringArray[index].cstr.getPointer(scope)
}
}
val Compilation.preambleLines: List<String>
get() = this.includes.map { "#include <$it>" } + this.additionalPreambleLines
internal fun Appendable.appendPreamble(compilation: Compilation) = this.apply {
compilation.preambleLines.forEach {
this.appendLine(it)
}
}
/**
* Creates temporary source file which includes the library.
*/
internal fun Compilation.createTempSource(): File {
val result = createTempFile(suffix = ".${language.sourceFileExtension}")
result.deleteOnExit()
result.bufferedWriter().use { writer ->
writer.appendPreamble(this)
}
return result
}
fun Compilation.copy(
includes: List<String> = this.includes,
additionalPreambleLines: List<String> = this.additionalPreambleLines,
compilerArgs: List<String> = this.compilerArgs,
language: Language = this.language
): Compilation = CompilationImpl(
includes = includes,
additionalPreambleLines = additionalPreambleLines,
compilerArgs = compilerArgs,
language = language
)
// Clang-8 crashes when consuming a precompiled header built with -fmodule-map-file argument (see KT-34467).
// We ignore this argument when building a pch to workaround this crash.
fun Compilation.copyWithArgsForPCH(): Compilation =
copy(compilerArgs = compilerArgs.filterNot { it.startsWith("-fmodule-map-file") })
data class CompilationImpl(
override val includes: List<String>,
override val additionalPreambleLines: List<String>,
override val compilerArgs: List<String>,
override val language: Language
) : Compilation
/**
* Precompiles the headers of this library.
*
* @return the library which includes the precompiled header instead of original ones.
*/
fun Compilation.precompileHeaders(): CompilationWithPCH = withIndex { index ->
val options = CXTranslationUnit_ForSerialization
val translationUnit = copyWithArgsForPCH().parse(index, options)
try {
translationUnit.ensureNoCompileErrors()
withPrecompiledHeader(translationUnit)
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
internal fun Compilation.withPrecompiledHeader(translationUnit: CXTranslationUnit): CompilationWithPCH {
val precompiledHeader = createTempFile(suffix = ".pch").apply { this.deleteOnExit() }
clang_saveTranslationUnit(translationUnit, precompiledHeader.absolutePath, 0)
return CompilationWithPCH(this.compilerArgs, precompiledHeader.absolutePath, this.language)
}
internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolean {
return if (this.excludeSystemLibs) {
clang_Location_isInSystemHeader(clang_getCursorLocation(cursor)) == 0
} else {
true
}
}
internal fun CXTranslationUnit.getErrorLineNumbers(): Sequence<Int> =
getDiagnostics().filter {
it.isError()
}.map {
memScoped {
val lineNumberVar = alloc<IntVar>()
clang_getFileLocation(it.location, null, lineNumberVar.ptr, null, null)
lineNumberVar.value
}
}
/**
* For each list of lines, checks if the code fragment composed from these lines is compilable against given library.
*/
fun List<List<String>>.mapFragmentIsCompilable(originalLibrary: CompilationWithPCH): List<Boolean> {
val library: CompilationWithPCH = originalLibrary
.copy(compilerArgs = originalLibrary.compilerArgs + "-ferror-limit=0")
val indicesOfNonCompilable = mutableSetOf<Int>()
val fragmentsToCheck = this.withIndex().toMutableList()
withIndex(excludeDeclarationsFromPCH = true) { index ->
val sourceFile = library.createTempSource()
val translationUnit = parseTranslationUnit(index, sourceFile, library.compilerArgs, options = 0)
try {
translationUnit.ensureNoCompileErrors()
while (fragmentsToCheck.isNotEmpty()) {
// Combine all fragments to be checked in a single file:
sourceFile.bufferedWriter().use { writer ->
writer.appendPreamble(library)
fragmentsToCheck.forEach {
it.value.forEach {
assert(!it.contains('\n'))
writer.appendLine(it)
}
}
}
clang_reparseTranslationUnit(translationUnit, 0, null, 0)
val errorLineNumbers = translationUnit.getErrorLineNumbers().toSet()
// Retain only those fragments that contain compilation error locations:
var lastLineNumber = library.preambleLines.size
fragmentsToCheck.retainAll {
val firstLineNumber = lastLineNumber + 1
lastLineNumber += it.value.size
(firstLineNumber .. lastLineNumber).any { it in errorLineNumbers }
}
if (fragmentsToCheck.isNotEmpty()) {
// The first fragment is now known to be non-compilable.
val firstFragment = fragmentsToCheck.removeAt(0)
indicesOfNonCompilable.add(firstFragment.index)
}
// The remaining fragments was potentially influenced by the first one,
// and thus require to be checked again.
}
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
return this.indices.map { it !in indicesOfNonCompilable }
}
internal interface Indexer {
/**
* Called when entered main file.
*/
fun enteredMainFile(file: CXFile) {}
/**
* Called when a file gets #included/#imported.
*/
fun ppIncludedFile(info: CXIdxIncludedFileInfo) {}
/**
* Called when a AST file (PCH or module) gets imported.
*/
fun importedASTFile(info: CXIdxImportedASTFileInfo) {}
/**
* Called to index a declaration.
*/
fun indexDeclaration(info: CXIdxDeclInfo) {}
}
internal fun indexTranslationUnit(index: CXIndex, translationUnit: CXTranslationUnit, options: Int, indexer: Indexer) {
val indexerStableRef = StableRef.create(indexer)
try {
val clientData = indexerStableRef.asCPointer()
memScoped {
val indexerCallbacks = alloc<IndexerCallbacks>().apply {
abortQuery = null
diagnostic = null
enteredMainFile = staticCFunction { clientData, mainFile, _ ->
@Suppress("NAME_SHADOWING")
val indexer = clientData!!.asStableRef<Indexer>().get()
indexer.enteredMainFile(mainFile!!)
// We must ensure only interop types exist in function signature.
@Suppress("USELESS_CAST")
null as CXIdxClientFile?
}
ppIncludedFile = staticCFunction { clientData, info ->
@Suppress("NAME_SHADOWING")
val indexer = clientData!!.asStableRef<Indexer>().get()
indexer.ppIncludedFile(info!!.pointed)
// We must ensure only interop types exist in function signature.
@Suppress("USELESS_CAST")
null as CXIdxClientFile?
}
importedASTFile = staticCFunction { clientData, info ->
@Suppress("NAME_SHADOWING")
val indexer = clientData!!.asStableRef<Indexer>().get()
indexer.importedASTFile(info!!.pointed)
// We must ensure only interop types exist in function signature.
@Suppress("USELESS_CAST")
null as CXIdxClientFile?
}
startedTranslationUnit = null
indexDeclaration = staticCFunction { clientData, info ->
@Suppress("NAME_SHADOWING")
val nativeIndex = clientData!!.asStableRef<Indexer>().get()
nativeIndex.indexDeclaration(info!!.pointed)
}
indexEntityReference = null
}
val indexAction = clang_IndexAction_create(index)
try {
val result = clang_indexTranslationUnit(indexAction, clientData,
indexerCallbacks.ptr, sizeOf<IndexerCallbacks>().toInt(), options, translationUnit)
if (result != 0) {
throw Error("clang_indexTranslationUnit returned $result")
}
} finally {
clang_IndexAction_dispose(indexAction)
}
}
} finally {
indexerStableRef.dispose()
}
}
internal class ModulesMap(
val compilation: Compilation,
val translationUnit: CXTranslationUnit
) : Closeable {
private val modularCompilation: ModularCompilation
private val index: CXIndex
private val translationUnitWithModules: CXTranslationUnit
private val arena = Arena()
private inline fun <T> T.toBeDisposedWith(crossinline block: (T) -> Unit): T = apply {
arena.defer { block(this) }
}
override fun close() {
arena.clear()
}
init {
try {
modularCompilation = ModularCompilation(compilation)
.toBeDisposedWith { it.dispose() }
index = clang_createIndex(0, 0)!!
.toBeDisposedWith { clang_disposeIndex(it) }
translationUnitWithModules = modularCompilation.parse(index)
.toBeDisposedWith { clang_disposeTranslationUnit(it) }
translationUnitWithModules.ensureNoCompileErrors()
} catch (e: Throwable) {
this.close()
throw e
}
}
data class Module(private val cxModule: CXModule)
fun getModule(file: CXFile): Module? {
// `file` is bound to `translationUnit`, however `translationUnitWithModules` is used to access modules.
// Find the corresponding file in `translationUnitWithModules`:
val fileInTuWithModules =
clang_getFile(translationUnitWithModules, clang_getFileName(file).convertAndDispose())!!
return clang_getModuleForFile(translationUnitWithModules, fileInTuWithModules)?.let { Module(it) }
}
}
internal fun getHeaderId(library: NativeLibrary, header: CXFile?): HeaderId {
if (header == null) {
return HeaderId("builtins")
}
val filePath = header.path
return library.headerToIdMapper.getHeaderId(filePath)
}
internal fun getFilteredHeaders(
nativeIndex: NativeIndexImpl,
index: CXIndex,
translationUnit: CXTranslationUnit
): Set<CXFile?> = getHeaders(nativeIndex.library, index, translationUnit).ownHeaders
class NativeLibraryHeaders<Header>(val ownHeaders: Set<Header>, val importedHeaders: Set<Header>)
internal fun getHeaders(
library: NativeLibrary,
index: CXIndex,
translationUnit: CXTranslationUnit
): NativeLibraryHeaders<CXFile?> {
val ownHeaders = mutableSetOf<CXFile?>()
val allHeaders = mutableSetOf<CXFile?>(null)
val filter = library.headerFilter
when (filter) {
is NativeLibraryHeaderFilter.NameBased ->
filterHeadersByName(library, filter, index, translationUnit, ownHeaders, allHeaders)
is NativeLibraryHeaderFilter.Predefined ->
filterHeadersByPredefined(filter, index, translationUnit, ownHeaders, allHeaders)
}
ownHeaders.removeAll { library.headerExclusionPolicy.excludeAll(getHeaderId(library, it)) }
return NativeLibraryHeaders(ownHeaders, allHeaders - ownHeaders)
}
private fun filterHeadersByName(
compilation: Compilation,
filter: NativeLibraryHeaderFilter.NameBased,
index: CXIndex,
translationUnit: CXTranslationUnit,
ownHeaders: MutableSet<CXFile?>,
allHeaders: MutableSet<CXFile?>
) {
val topLevelFiles = mutableListOf<CXFile>()
var mainFile: CXFile? = null
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
val headerToName = mutableMapOf<CXFile, String>()
// The *name* of the header here is the path relative to the include path element., e.g. `curl/curl.h`.
override fun enteredMainFile(file: CXFile) {
mainFile = file
allHeaders += file
}
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
val includeLocation = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue())
val file = info.file!!
allHeaders += file
if (clang_Location_isFromMainFile(includeLocation) != 0) {
topLevelFiles.add(file)
}
val name = info.filename!!.toKString()
val headerName = if (info.isAngled != 0) {
// If the header is included with `#include <$name>`, then `name` is probably
// the path relative to the include path element.
name
} else {
// If it is included with `#include "$name"`, then `name` can also be the path relative to the includer.
val includerFile = includeLocation.getContainingFile()!!
val includerName = headerToName[includerFile] ?: ""
val includerPath = includerFile.path
if (clang_getFile(translationUnit, Paths.get(includerPath).resolveSibling(name).toString()) == file) {
// included file is accessible from the includer by `name` used as relative path, so
// `name` seems to be relative to the includer:
Paths.get(includerName).resolveSibling(name).normalize().toString()
} else {
name
}
}
headerToName[file] = headerName
if (!filter.policy.excludeUnused(headerName)) {
ownHeaders.add(file)
}
}
})
if (filter.excludeDepdendentModules) {
ModulesMap(compilation, translationUnit).use { modulesMap ->
val topLevelModules = topLevelFiles.map { modulesMap.getModule(it) }.toSet()
ownHeaders.removeAll {
val module = modulesMap.getModule(it!!)
module !in topLevelModules
}
// Note: if some of the top-level headers don't belong to modules,
// then all non-modular headers are included.
}
} else {
if (!filter.policy.excludeUnused(headerName = null)) {
// Builtins.
ownHeaders.add(null)
}
}
ownHeaders.add(mainFile!!)
}
private fun filterHeadersByPredefined(
filter: NativeLibraryHeaderFilter.Predefined,
index: CXIndex,
translationUnit: CXTranslationUnit,
ownHeaders: MutableSet<CXFile?>,
allHeaders: MutableSet<CXFile?>
) {
// Note: suboptimal but simple.
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
val file = info.file
allHeaders += file
if (file?.canonicalPath in filter.headers) {
ownHeaders += file
}
}
})
}
fun NativeLibrary.getHeaderPaths(): NativeLibraryHeaders<String> {
withIndex { index ->
val translationUnit =
this.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord).ensureNoCompileErrors()
try {
fun getPath(file: CXFile?) = if (file == null) "<builtins>" else file.canonicalPath
val headers = getHeaders(this, index, translationUnit)
return NativeLibraryHeaders(
headers.ownHeaders.map(::getPath).toSet(),
headers.importedHeaders.map(::getPath).toSet()
)
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
}
fun ObjCMethod.replaces(other: ObjCMethod): Boolean =
this.isClass == other.isClass && this.selector == other.selector
fun ObjCProperty.replaces(other: ObjCProperty): Boolean =
this.getter.replaces(other.getter)
fun File.sha256(): String {
val digest = MessageDigest.getInstance("SHA-256")
DigestInputStream(this.inputStream(), digest).use { dis ->
val buffer = ByteArray(8192)
// Read all bytes:
while (dis.read(buffer, 0, buffer.size) != -1) {}
}
// Convert to hex:
return digest.digest().joinToString("") {
Integer.toHexString((it.toInt() and 0xff) + 0x100).substring(1)
}
}
fun headerContentsHash(filePath: String) = File(filePath).sha256()
internal fun CValue<CXSourceLocation>.getContainingFile(): CXFile? = memScoped {
val fileVar = alloc<CXFileVar>()
clang_getFileLocation(this@getContainingFile, fileVar.ptr, null, null, null)
fileVar.value
}
@JvmName("getFileContainingCursor")
internal fun getContainingFile(cursor: CValue<CXCursor>): CXFile? {
return clang_getCursorLocation(cursor).getContainingFile()
}
internal val CXFile.path: String get() = clang_getFileName(this).convertAndDispose()
internal val CXFile.canonicalPath: String get() = File(this.path).canonicalPath
private fun createVfsOverlayFileContents(virtualPathToReal: Map<Path, Path>): ByteArray {
val overlay = clang_VirtualFileOverlay_create(0)
try {
fun addFileMapping(realPath: Path, virtualPath: Path) {
clang_VirtualFileOverlay_addFileMapping(
overlay,
virtualPath = virtualPath.toAbsolutePath().toString(),
realPath = realPath.toAbsolutePath().toString()
)
}
virtualPathToReal.forEach { virtualPath, realPath ->
if (Files.isDirectory(realPath)) {
realPath.toFile().walkTopDown().forEach {
if (!it.isDirectory) {
addFileMapping(
realPath = it.toPath(),
virtualPath = virtualPath.resolve(realPath.relativize(it.toPath()))
)
}
}
} else {
addFileMapping(realPath = realPath, virtualPath = virtualPath)
}
}
memScoped {
val bufferVar = alloc<CPointerVar<ByteVar>>().apply { value = null }
val bufferSizeVar = alloc<IntVar>()
val res = clang_VirtualFileOverlay_writeToBuffer(overlay, 0, bufferVar.ptr, bufferSizeVar.ptr)
if (res != CXErrorCode.CXError_Success) {
// TODO: shall we free the buffer in this case?
error(res)
}
return bufferVar.value!!.readBytes(bufferSizeVar.value)
}
} finally {
clang_VirtualFileOverlay_dispose(overlay)
}
}
fun createVfsOverlayFile(virtualPathToReal: Map<Path, Path>): Path {
val bytes = createVfsOverlayFileContents(virtualPathToReal)
return createTempFile(prefix = "konan", suffix = ".vfsoverlay").apply {
bufferedWriter().use {
writeBytes(bytes)
}
deleteOnExit()
}.toPath()
}
tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) {
this.def.aliased.unwrapTypedefs()
} else {
this
}
fun Type.canonicalIsPointerToChar(): Boolean {
val unwrappedType = this.unwrapTypedefs()
return unwrappedType is PointerType && unwrappedType.pointeeType.unwrapTypedefs() == CharType
}
internal interface Disposable {
fun dispose()
}
internal inline fun <T : Disposable, R> T.use(block: (T) -> R): R = try {
block(this)
} finally {
this.dispose()
}
@@ -0,0 +1,9 @@
#ifdef __linux__
namespace llvm {
/**
* http://lists.llvm.org/pipermail/llvm-dev/2017-January/109621.html
* We can't rebuild llvm, but we can define symbol missed in llvm build.
*/
int DisableABIBreakingChecks = 1;
}
#endif
@@ -0,0 +1,110 @@
#if defined(__linux__) || defined(__APPLE__)
#include <dlfcn.h>
#if defined(__linux__)
#include <link.h>
#endif
#include <stdio.h>
#include <signal.h>
extern "C" void clang_toggleCrashRecovery(unsigned isEnabled);
constexpr int signalsToCover[] = {
SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGUSR1, SIGUSR2
};
struct {
void* handler;
bool isSigaction;
} oldSignalHandlers[sizeof(signalsToCover)/sizeof(signalsToCover[0])] = { 0 };
static int mySigaction(int sig, const struct sigaction *act, struct sigaction * oact) {
for (int i = 0; i < sizeof(signalsToCover)/sizeof(signalsToCover[0]); i++) {
if (sig == signalsToCover[i]) return 0;
}
return sigaction(sig, act, oact);
}
static void checkSignalChaining() {
struct sigaction oact;
clang_toggleCrashRecovery(1);
for (int i = 0; i < sizeof(signalsToCover)/sizeof(signalsToCover[0]); i++) {
int sig = signalsToCover[i];
if (sigaction(sig, nullptr, &oact) != 0) continue;
if ((oact.sa_flags & SA_SIGINFO) == 0) {
if (oldSignalHandlers[i].isSigaction) {
fprintf(stderr, "ERROR: improperly changed signal flag for %d\n", sig);
continue;
}
if (oldSignalHandlers[i].handler != (void*)oact.sa_handler) {
fprintf(stderr, "ERROR: improperly changed signal handler for %d\n", sig);
continue;
}
} else {
if (!oldSignalHandlers[i].isSigaction) {
fprintf(stderr, "ERROR: improperly changed signal flag for %d\n", sig);
continue;
}
void* action = (void*)oact.sa_sigaction;
if (oldSignalHandlers[i].handler != action) {
Dl_info info;
const char* soname = "<unknown>";
if (dladdr(action, &info) != 0) {
soname = info.dli_fname;
}
fprintf(stderr, "ERROR: changed signal handler for %d from %p to %p: coming from %s\n",
sig, oldSignalHandlers[i].handler, action, soname);
}
}
}
clang_toggleCrashRecovery(0);
}
__attribute__((constructor))
static void initSignalChaining() {
void** base = 0;
Dl_info info;
for (int i = 0; i < sizeof(signalsToCover)/sizeof(signalsToCover[0]); i++) {
struct sigaction oact;
int sig = signalsToCover[i];
if (sigaction(signalsToCover[i], nullptr, &oact) == 0) {
if ((oact.sa_flags & SA_SIGINFO) == 0) {
oldSignalHandlers[i] = {(void*)oact.sa_handler, false};
} else {
oldSignalHandlers[i] = {(void*)oact.sa_sigaction, true};
}
}
}
if (dladdr((void*)&clang_toggleCrashRecovery, &info) == 0) return;
base = (void**)info.dli_fbase;
// Force resolving of lazy symbols.
clang_toggleCrashRecovery(1);
clang_toggleCrashRecovery(0);
// And then patch GOT.
#if defined(__linux__)
{
// On Linux we have to be a bit tricky, as there's unmapped gap between code and GOT.
struct link_map* linkmap = 0;
if (dladdr1((void*)&clang_toggleCrashRecovery, &info, (void**)&linkmap, RTLD_DL_LINKMAP) == 0) return;
base = (void**)linkmap->l_ld;
}
#endif
for (int index = 0, patched = 0; patched < 1; index++) {
void* value = base[index];
if (value == &sigaction) {
base[index] = (void*)mySigaction;
patched++;
}
if (value == mySigaction) {
patched++;
}
}
checkSignalChaining();
}
#endif // defined(__linux__) || defined(__APPLE__)
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
buildscript {
apply from: "$rootDir/gradle/kotlinGradlePlugin.gradle"
}
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
konan.libraries.push ({
arenas: new Map(),
nextArena: 0,
Konan_js_allocateArena: function (array) {
var index = konan_dependencies.env.nextArena++;
konan_dependencies.env.arenas.set(index, array || []);
return index;
},
Konan_js_freeArena: function(arenaIndex) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
arena.forEach(function(element, index) {
arena[index] = null;
});
konan_dependencies.env.arenas.delete(arenaIndex);
},
Konan_js_pushIntToArena: function (arenaIndex, value) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
arena.push(value);
return arena.length - 1;
},
Konan_js_addObjectToArena: function (arenaIndex, object) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
arena.push(object);
return arena.length - 1;
},
Konan_js_wrapLambda: function (functionArenaIndex, index) {
return (function () {
var functionArena = konan_dependencies.env.arenas.get(functionArenaIndex);
// convert Arguments to an array
// to be provided by launcher.js
var argumentArenaIndex = konan_dependencies.env.Konan_js_allocateArena(Array.prototype.slice.call(arguments));
var resultIndex = instance.exports.Konan_js_runLambda(index, argumentArenaIndex, arguments.length);
var result = kotlinObject(argumentArenaIndex, resultIndex);
konan_dependencies.env.Konan_js_freeArena(argumentArenaIndex);
return result;
});
},
Konan_js_getInt: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) {
// TODO: The toUTF16String() is to be resolved by launcher.js runtime.
var property = toUTF16String(propertyNamePtr, propertyNameLength);
var value = kotlinObject(arenaIndex, objIndex)[property];
return value;
},
Konan_js_getProperty: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) {
// TODO: The toUTF16String() is to be resolved by launcher.js runtime.
var property = toUTF16String(propertyNamePtr, propertyNameLength);
var arena = konan_dependencies.env.arenas.get(arenaIndex);
var value = arena[objIndex][property];
arena.push(value);
return arena.length - 1;
},
Konan_js_setFunction: function (arena, obj, propertyName, propertyNameLength, func) {
var name = toUTF16String(propertyName, propertyNameLength);
kotlinObject(arena, obj)[name] = konan_dependencies.env.Konan_js_wrapLambda(arena, func);
},
Konan_js_setString: function (arena, obj, propertyName, propertyNameLength, stringPtr, stringLength) {
var name = toUTF16String(propertyName, propertyNameLength);
var string = toUTF16String(stringPtr, stringLength);
kotlinObject(arena, obj)[name] = string;
},
});
// TODO: This is just a shorthand notation.
function kotlinObject(arenaIndex, objectIndex) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
if (typeof arena == "undefined") {
console.log("No arena index " + arenaIndex + "for object" + objectIndex);
console.trace()
}
return arena[objectIndex]
}
function toArena(arenaIndex, object) {
return konan_dependencies.env.Konan_js_addObjectToArena(arenaIndex, object);
}
@@ -0,0 +1,143 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.wasm.jsinterop
import kotlin.native.*
import kotlin.native.internal.ExportForCppRuntime
import kotlinx.cinterop.*
typealias Arena = Int
typealias Object = Int
typealias Pointer = Int
/**
* @Retain annotation is required to preserve functions from internalization and DCE.
*/
@RetainForTarget("wasm32")
@SymbolName("Konan_js_allocateArena")
external public fun allocateArena(): Arena
@RetainForTarget("wasm32")
@SymbolName("Konan_js_freeArena")
external public fun freeArena(arena: Arena)
@RetainForTarget("wasm32")
@SymbolName("Konan_js_pushIntToArena")
external public fun pushIntToArena(arena: Arena, value: Int)
const val upperWord = 0xffffffff.toLong() shl 32
@ExportForCppRuntime
fun doubleUpper(value: Double): Int =
((value.toBits() and upperWord) ushr 32) .toInt()
@ExportForCppRuntime
fun doubleLower(value: Double): Int =
(value.toBits() and 0x00000000ffffffff) .toInt()
@RetainForTarget("wasm32")
@SymbolName("ReturnSlot_getDouble")
external public fun ReturnSlot_getDouble(): Double
@RetainForTarget("wasm32")
@SymbolName("Kotlin_String_utf16pointer")
external public fun stringPointer(message: String): Pointer
@RetainForTarget("wasm32")
@SymbolName("Kotlin_String_utf16length")
external public fun stringLengthBytes(message: String): Int
typealias KtFunction <R> = ((ArrayList<JsValue>)->R)
fun <R> wrapFunction(func: KtFunction<R>): Int {
val ptr: Long = StableRef.create(func).asCPointer().toLong()
return ptr.toInt() // TODO: LP64 unsafe.
}
@RetainForTarget("wasm32")
@ExportForCppRuntime("Konan_js_runLambda")
fun runLambda(pointer: Int, argumentsArena: Arena, argumentsArenaSize: Int): Int {
val arguments = arrayListOf<JsValue>()
for (i in 0 until argumentsArenaSize) {
arguments.add(JsValue(argumentsArena, i));
}
val previousArena = ArenaManager.currentArena
ArenaManager.currentArena = argumentsArena
// TODO: LP64 unsafe: wasm32 passes Int, not Long.
val func = pointer.toLong().toCPointer<CPointed>()!!.asStableRef<KtFunction<JsValue>>().get()
val result = func(arguments)
ArenaManager.currentArena = previousArena
return result.index
}
open class JsValue(val arena: Arena, val index: Object) {
fun getInt(property: String): Int {
return getInt(ArenaManager.currentArena, index, stringPointer(property), stringLengthBytes(property))
}
fun getProperty(property: String): JsValue {
return JsValue(ArenaManager.currentArena, Konan_js_getProperty(ArenaManager.currentArena, index, stringPointer(property), stringLengthBytes(property)))
}
}
open class JsArray(arena: Arena, index: Object): JsValue(arena, index) {
constructor(jsValue: JsValue): this(jsValue.arena, jsValue.index)
operator fun get(index: Int): JsValue {
// TODO: we could pass an integer index to index arrays.
return getProperty(index.toString())
}
val size: Int
get() = this.getInt("length")
}
@RetainForTarget("wasm32")
@SymbolName("Konan_js_getInt")
external public fun getInt(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int;
@RetainForTarget("wasm32")
@SymbolName("Konan_js_getProperty")
external public fun Konan_js_getProperty(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int;
@RetainForTarget("wasm32")
@SymbolName("Konan_js_setFunction")
external public fun setFunction(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int , function: Int)
@RetainForTarget("wasm32")
@SymbolName("Konan_js_setString")
external public fun setString(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int, stringPtr: Pointer, stringLength: Int )
fun setter(obj: JsValue, property: String, string: String) {
setString(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), stringPointer(string), stringLengthBytes(string))
}
fun setter(obj: JsValue, property: String, lambda: KtFunction<Unit>) {
val pointer = wrapFunction(lambda);
setFunction(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), pointer)
}
fun JsValue.setter(property: String, lambda: KtFunction<Unit>) {
setter(this, property, lambda)
}
fun JsValue.setter(property: String, string: String) {
setter(this, property, string)
}
object ArenaManager {
val globalArena = allocateArena()
var currentArena = globalArena
}
+18
View File
@@ -0,0 +1,18 @@
# Kotlin-native interop
## Usage
Create file `../gradle.properties` with contents:
llvmInstallPath=/path/to/llvm
Create a Gradle subproject somewhere under `../`, using `../InteropExample` as a template.
To generate the interop stubs and libraries and build all sources you can run
the following command from `../`:
./gradlew InteropExample:build
To run the example (if 'application' plugin is enabled):
./gradlew InteropExample:run
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'kotlin'
apply plugin: 'c'
buildscript {
ext.rootBuildDirectory = file('../..')
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
dependencies {
classpath "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
}
}
import org.jetbrains.kotlin.konan.target.ClangArgs
model {
components {
callbacks(NativeLibrarySpec) {
sources.c.source {
srcDir 'src/callbacks/c'
include '**/*.c'
}
binaries.all {
def host = rootProject.ext.hostName
def hostLibffiDir = rootProject.ext.get("${host}LibffiDir")
cCompiler.args hostPlatform.clang.hostCompilerArgsForJni
cCompiler.args "-I$hostLibffiDir/include"
linker.args "$hostLibffiDir/lib/libffi.a"
}
}
}
toolChains {
clang(Clang) {
eachPlatform {
cCompiler.withArguments(ClangArgs.&filterGradleNativeSoftwareFlags)
}
}
}
}
repositories {
maven {
url buildKotlinCompilerRepo
}
}
dependencies {
compile project(":utilities:basic-utils")
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
}
sourceSets.main.kotlin.srcDirs += "src/jvm/kotlin"
compileKotlin {
kotlinOptions {
freeCompilerArgs = ['-Xuse-experimental=kotlin.ExperimentalUnsignedTypes', '-Xuse-experimental=kotlin.Experimental',
'-Xopt-in=kotlin.RequiresOptIn', "-XXLanguage:+InlineClasses"]
allWarningsAsErrors=true
}
}
task nativelibs(type: Copy) {
dependsOn 'callbacksSharedLibrary'
from "$buildDir/libs/callbacks/shared/"
into "$buildDir/nativelibs/"
}
classes.dependsOn nativelibs
@@ -0,0 +1,243 @@
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <jni.h>
#include <ffi.h>
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeVoid
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeVoid(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_void;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt8
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt8(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint8;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt8
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt8(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint8;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt16
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt16(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint16;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt16
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt16(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint16;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt32
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt32(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint32;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt32
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt32(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint32;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt64
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt64(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint64;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt64
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt64(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint64;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypePointer
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypePointer(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_pointer;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeStruct0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeStruct0(JNIEnv *env, jclass cls, jlong elements) {
ffi_type* res = malloc(sizeof(ffi_type));
if (res != NULL) {
res->size = 0;
res->alignment = 0;
res->elements = (ffi_type**) elements;
res->type = FFI_TYPE_STRUCT;
}
return (jlong) res;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiCreateCif0
* Signature: (IJJ)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiCreateCif0(JNIEnv *env, jclass cls, jint nArgs, jlong rType, jlong argTypes) {
ffi_cif* res = malloc(sizeof(ffi_cif));
if (res != NULL) {
ffi_status status = ffi_prep_cif(res, FFI_DEFAULT_ABI, nArgs, (ffi_type*)rType, (ffi_type**)argTypes);
if (status != FFI_OK) {
if (status == FFI_BAD_TYPEDEF) {
return -(jlong)1;
} else if (status == FFI_BAD_ABI) {
return -(jlong)2;
} else {
return -(jlong)3;
}
}
}
return (jlong) res;
}
static JavaVM *vm = NULL;
// Returns the JNI env which can be used by the caller.
// If current thread is not attached to JVM, then it gets attached as daemon.
static JNIEnv* getCurrentEnv() {
JNIEnv* env;
assert(vm != NULL);
jint res = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_1);
if (res != JNI_OK) {
assert(res == JNI_EDETACHED);
res = (*vm)->AttachCurrentThreadAsDaemon(vm, (void**)&env, NULL);
assert(res == JNI_OK);
}
return env;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm_, void *reserved) {
vm = vm_;
return JNI_VERSION_1_1;
}
// Checks for pending exception. If there is one, describes it and terminates the process.
static void checkException(JNIEnv *env) {
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
abort();
}
}
static void ffi_fun(ffi_cif *cif, void *ret, void **args, void *user_data) {
JNIEnv* env = getCurrentEnv();
static jmethodID acceptFun = NULL;
static jclass cls = NULL;
if (acceptFun == NULL) {
// Note: in some cases [FindClass] below may use a classloader different from the one loaded interop classes,
// so stick to JVM-provided class:
jclass clsLocal = (*env)->FindClass(env, "java/util/function/LongConsumer");
checkException(env);
assert(clsLocal != NULL);
cls = (jclass) (*env)->NewGlobalRef(env, clsLocal);
checkException(env);
assert(cls != NULL);
acceptFun = (*env)->GetMethodID(env, cls, "accept", "(J)V");
checkException(env);
assert(acceptFun != NULL);
}
jlong retAndArgs[2] = { (jlong)ret, (jlong)args }; // Unpacked in [ffiClosureImpl].
(*env)->CallVoidMethod(env, (jobject) user_data, acceptFun, (jlong)(intptr_t)&retAndArgs[0]);
checkException(env);
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiCreateClosure0
* Signature: (JLjava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiCreateClosure0(JNIEnv *env, jclass cls, jlong ffiCif, jobject userData) {
jobject userDataGlobalRef = (*env)->NewGlobalRef(env, userData);
if (userDataGlobalRef == NULL) {
return (jlong)0;
}
assert(sizeof(jobject) == sizeof(void*)); // TODO: check statically
void* userDataPtr = (void*) userDataGlobalRef;
void* res;
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), &res);
if (closure == NULL) {
return (jlong)0;
}
ffi_status status = ffi_prep_closure_loc(closure, (ffi_cif*)ffiCif, ffi_fun, userDataPtr, res);
if (status != FFI_OK) {
return -(jlong)1;
}
return (jlong) res;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: newGlobalRef
* Signature: (Ljava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_newGlobalRef(JNIEnv *env, jclass cls, jobject obj) {
jobject res = (*env)->NewGlobalRef(env, obj);
return (jlong) res;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: derefGlobalRef
* Signature: (J)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_derefGlobalRef(JNIEnv *env, jclass cls, jlong ref) {
return (jobject) ref;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: deleteGlobalRef
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_deleteGlobalRef(JNIEnv *env, jclass cls, jlong ref) {
(*env)->DeleteGlobalRef(env, (jobject) ref);
}
@@ -0,0 +1,461 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import java.util.concurrent.ConcurrentHashMap
import java.util.function.LongConsumer
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KType
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.reflect
internal fun createStablePointer(any: Any): COpaquePointer = newGlobalRef(any).toCPointer()!!
internal fun disposeStablePointer(pointer: COpaquePointer) = deleteGlobalRef(pointer.toLong())
@PublishedApi
internal fun derefStablePointer(pointer: COpaquePointer): Any = derefGlobalRef(pointer.toLong())
private fun getFieldCType(type: KType): CType<*> {
val classifier = type.classifier
if (classifier is KClass<*> && classifier.isSubclassOf(CStructVar::class)) {
return getStructCType(classifier)
}
return getArgOrRetValCType(type)
}
private fun getVariableCType(type: KType): CType<*>? {
val classifier = type.classifier
return when (classifier) {
!is KClass<*> -> null
ByteVarOf::class -> SInt8
ShortVarOf::class -> SInt16
IntVarOf::class -> SInt32
LongVarOf::class -> SInt64
CPointerVarOf::class -> Pointer
// TODO: floats, enums.
else -> if (classifier.isSubclassOf(CStructVar::class)) {
getStructCType(classifier)
} else {
null
}
}
}
private val structTypeCache = ConcurrentHashMap<Class<*>, CType<*>>()
private fun getStructCType(structClass: KClass<*>): CType<*> = structTypeCache.computeIfAbsent(structClass.java) {
// Note that struct classes are not supposed to be user-defined,
// so they don't require to be checked strictly.
val annotations = structClass.annotations
val cNaturalStruct = annotations.filterIsInstance<CNaturalStruct>().firstOrNull() ?:
error("struct ${structClass.simpleName} has custom layout")
val propertiesByName = structClass.declaredMemberProperties.groupBy { it.name }
val fields = cNaturalStruct.fieldNames.map {
propertiesByName[it]!!.single()
}
val fieldCTypes = mutableListOf<CType<*>>()
for (field in fields) {
val lengthAnnotation = field.annotations.filterIsInstance<CLength>().firstOrNull()
if (lengthAnnotation == null) {
val fieldType = getFieldCType(field.returnType)
fieldCTypes.add(fieldType)
} else {
assert(field.returnType.classifier == CPointer::class)
val length = lengthAnnotation.value
if (length != 0) {
val pointed = field.returnType.arguments.single().type!!
val pointedCType = getVariableCType(pointed) ?: TODO("array element type '$pointed'")
// Represent array field as repeated element-typed fields:
repeat(length) {
fieldCTypes.add(pointedCType)
}
}
}
}
@Suppress("DEPRECATION")
val structType = structClass.companionObjectInstance as CVariable.Type
Struct(structType.size, structType.align, fieldCTypes)
}
private fun getStructValueCType(type: KType): CType<*> {
val structClass = type.arguments.singleOrNull()?.type?.classifier as? KClass<*> ?:
error("'$type' type is incomplete")
return getStructCType(structClass)
}
private fun getEnumCType(classifier: KClass<*>): CEnumType? {
val rawValueType = classifier.declaredMemberProperties.single().returnType
val rawValueCType = when (rawValueType.classifier) {
Byte::class -> SInt8
Short::class -> SInt16
Int::class -> SInt32
Long::class -> SInt64
else -> error("'${classifier.simpleName}' has unexpected value type '$rawValueType'")
}
@Suppress("UNCHECKED_CAST")
return CEnumType(rawValueCType as CType<Any>)
}
private fun getArgOrRetValCType(type: KType): CType<*> {
val classifier = type.classifier
val result = when (classifier) {
!is KClass<*> -> null
Unit::class -> Void
Byte::class -> SInt8
Short::class -> SInt16
Int::class -> SInt32
Long::class -> SInt64
CPointer::class -> Pointer
// TODO: floats
CValue::class -> getStructValueCType(type)
else -> if (classifier.isSubclassOf(@Suppress("DEPRECATION") CEnum::class)) {
getEnumCType(classifier)
} else {
null
}
} ?: error("$type is not supported in callback signature")
if (type.isMarkedNullable != (classifier == CPointer::class)) {
if (type.isMarkedNullable) {
error("$type must not be nullable when used in callback signature")
} else {
error("$type must be nullable when used in callback signature")
}
}
return result
}
private fun createStaticCFunction(function: Function<*>): CPointer<CFunction<*>> {
val errorMessage = "staticCFunction must take an unbound, non-capturing function"
if (!isStatic(function)) {
throw IllegalArgumentException(errorMessage)
}
val kFunction = function as? KFunction<*> ?: function.reflect() ?:
throw IllegalArgumentException(errorMessage)
val returnType = getArgOrRetValCType(kFunction.returnType)
val paramTypes = kFunction.parameters.map { getArgOrRetValCType(it.type) }
@Suppress("UNCHECKED_CAST")
return interpretCPointer(createStaticCFunctionImpl(returnType as CType<Any?>, paramTypes, function))!!
}
/**
* Returns `true` if given function is *static* as defined in [staticCFunction].
*/
private fun isStatic(function: Function<*>): Boolean {
// TODO: revise
try {
with(function.javaClass.getDeclaredField("INSTANCE")) {
if (!java.lang.reflect.Modifier.isStatic(modifiers) || !java.lang.reflect.Modifier.isFinal(modifiers)) {
return false
}
isAccessible = true // TODO: undo
return get(null) == function
// If the class has static final "INSTANCE" field, and only the value of this field is accepted,
// then each class is handled at most once, so these checks prevent memory leaks.
}
} catch (e: NoSuchFieldException) {
return false
}
}
private val createdStaticFunctions = ConcurrentHashMap<Class<*>, CPointer<CFunction<*>>>()
@Suppress("UNCHECKED_CAST")
internal fun <F : Function<*>> staticCFunctionImpl(function: F) =
createdStaticFunctions.computeIfAbsent(function.javaClass) {
createStaticCFunction(function)
} as CPointer<CFunction<F>>
private val invokeMethods = (0 .. 22).map { arity ->
Class.forName("kotlin.jvm.functions.Function$arity").getMethod("invoke",
*Array<Class<*>>(arity) { java.lang.Object::class.java })
}
private fun createStaticCFunctionImpl(
returnType: CType<Any?>,
paramTypes: List<CType<*>>,
function: Function<*>
): NativePtr {
val ffiCif = ffiCreateCif(returnType.ffiType, paramTypes.map { it.ffiType })
val arity = paramTypes.size
val pt = paramTypes.toTypedArray()
@Suppress("UNCHECKED_CAST")
val impl: FfiClosureImpl = when (arity) {
0 -> {
val f = function as () -> Any?
ffiClosureImpl(returnType) { _ ->
f()
}
}
1 -> {
val f = function as (Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0))
}
}
2 -> {
val f = function as (Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1))
}
}
3 -> {
val f = function as (Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2))
}
}
4 -> {
val f = function as (Any?, Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2), pt.read(args, 3))
}
}
5 -> {
val f = function as (Any?, Any?, Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2), pt.read(args, 3), pt.read(args, 4))
}
}
else -> {
val invokeMethod = invokeMethods[arity]
ffiClosureImpl(returnType) { args ->
val arguments = Array(arity) { pt.read(args, it) }
invokeMethod.invoke(function, *arguments)
}
}
}
return ffiCreateClosure(ffiCif, impl)
}
@Suppress("NOTHING_TO_INLINE")
private inline fun Array<CType<*>>.read(args: CArrayPointer<COpaquePointerVar>, index: Int) =
this[index].read(args[index].rawValue)
private inline fun ffiClosureImpl(
returnType: CType<Any?>,
crossinline invoke: (args: CArrayPointer<COpaquePointerVar>) -> Any?
): FfiClosureImpl {
// Called through [ffi_fun] when a native function created with [ffiCreateClosure] is invoked.
return LongConsumer { retAndArgsRaw ->
val retAndArgs = retAndArgsRaw.toCPointer<CPointerVar<*>>()!!
// Pointer to memory to be filled with return value of the invoked native function:
val ret = retAndArgs[0]!!
// Pointer to array of pointers to arguments passed to the invoked native function:
val args = retAndArgs[1]!!.reinterpret<COpaquePointerVar>()
val result = invoke(args)
returnType.write(ret.rawValue, result)
}
}
/**
* Describes the bridge between Kotlin type `T` and the corresponding C type of a function's parameter or return value.
* It is supposed to be constructed using the primitive types (such as [SInt32]), the [Struct] combinator
* and the [CEnumType] wrapper.
*
* This description omits the details that are irrelevant for the ABI.
*/
private abstract class CType<T> internal constructor(val ffiType: ffi_type) {
internal constructor(ffiTypePtr: Long) : this(interpretPointed<ffi_type>(ffiTypePtr))
abstract fun read(location: NativePtr): T
abstract fun write(location: NativePtr, value: T): Unit
}
private object Void : CType<Any?>(ffiTypeVoid()) {
override fun read(location: NativePtr) = throw UnsupportedOperationException()
override fun write(location: NativePtr, value: Any?) {
// nothing to do.
}
}
private object SInt8 : CType<Byte>(ffiTypeSInt8()) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).value
override fun write(location: NativePtr, value: Byte) {
interpretPointed<ByteVar>(location).value = value
}
}
private object SInt16 : CType<Short>(ffiTypeSInt16()) {
override fun read(location: NativePtr) = interpretPointed<ShortVar>(location).value
override fun write(location: NativePtr, value: Short) {
interpretPointed<ShortVar>(location).value = value
}
}
private object SInt32 : CType<Int>(ffiTypeSInt32()) {
override fun read(location: NativePtr) = interpretPointed<IntVar>(location).value
override fun write(location: NativePtr, value: Int) {
interpretPointed<IntVar>(location).value = value
}
}
private object SInt64 : CType<Long>(ffiTypeSInt64()) {
override fun read(location: NativePtr) = interpretPointed<LongVar>(location).value
override fun write(location: NativePtr, value: Long) {
interpretPointed<LongVar>(location).value = value
}
}
private object Pointer : CType<CPointer<*>?>(ffiTypePointer()) {
override fun read(location: NativePtr) = interpretPointed<CPointerVar<*>>(location).value
override fun write(location: NativePtr, value: CPointer<*>?) {
interpretPointed<CPointerVar<*>>(location).value = value
}
}
private class Struct(val size: Long, val align: Int, elementTypes: List<CType<*>>) : CType<CValue<*>>(
ffiTypeStruct(
elementTypes.map { it.ffiType }
)
) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).readValue<CStructVar>(size, align)
override fun write(location: NativePtr, value: CValue<*>) = value.write(location)
}
@Suppress("DEPRECATION")
private class CEnumType(private val rawValueCType: CType<Any>) : CType<CEnum>(rawValueCType.ffiType) {
override fun read(location: NativePtr): CEnum {
TODO("enum-typed callback parameters")
}
override fun write(location: NativePtr, value: CEnum) {
rawValueCType.write(location, value.value)
}
}
private typealias FfiClosureImpl = LongConsumer
private typealias UserData = FfiClosureImpl
private val topLevelInitializer = loadKonanLibrary("callbacks")
/**
* Reference to `ffi_type` struct instance.
*/
internal class ffi_type(rawPtr: NativePtr) : COpaque(rawPtr)
/**
* Reference to `ffi_cif` struct instance.
*/
internal class ffi_cif(rawPtr: NativePtr) : COpaque(rawPtr)
private external fun ffiTypeVoid(): Long
private external fun ffiTypeUInt8(): Long
private external fun ffiTypeSInt8(): Long
private external fun ffiTypeUInt16(): Long
private external fun ffiTypeSInt16(): Long
private external fun ffiTypeUInt32(): Long
private external fun ffiTypeSInt32(): Long
private external fun ffiTypeUInt64(): Long
private external fun ffiTypeSInt64(): Long
private external fun ffiTypePointer(): Long
private external fun ffiTypeStruct0(elements: Long): Long
/**
* Allocates and initializes `ffi_type` describing the struct.
*
* @param elements types of the struct elements
*/
private fun ffiTypeStruct(elementTypes: List<ffi_type>): ffi_type {
val elements = nativeHeap.allocArrayOfPointersTo(*elementTypes.toTypedArray(), null)
val res = ffiTypeStruct0(elements.rawValue)
if (res == 0L) {
throw OutOfMemoryError()
}
return interpretPointed(res)
}
private external fun ffiCreateCif0(nArgs: Int, rType: Long, argTypes: Long): Long
/**
* Creates and prepares an `ffi_cif`.
*
* @param returnType native function return value type
* @param paramTypes native function parameter types
*
* @return the initialized `ffi_cif`
*/
private fun ffiCreateCif(returnType: ffi_type, paramTypes: List<ffi_type>): ffi_cif {
val nArgs = paramTypes.size
val argTypes = nativeHeap.allocArrayOfPointersTo(*paramTypes.toTypedArray(), null)
val res = ffiCreateCif0(nArgs, returnType.rawPtr, argTypes.rawValue)
when (res) {
0L -> throw OutOfMemoryError()
-1L -> throw Error("FFI_BAD_TYPEDEF")
-2L -> throw Error("FFI_BAD_ABI")
-3L -> throw Error("libffi error occurred")
}
return interpretPointed(res)
}
private external fun ffiCreateClosure0(ffiCif: Long, userData: Any): Long
/**
* Uses libffi to allocate a native function which will call [impl] when invoked.
*
* @param ffiCif describes the type of the function to create
*/
private fun ffiCreateClosure(ffiCif: ffi_cif, impl: FfiClosureImpl): NativePtr {
val res = ffiCreateClosure0(ffiCif.rawPtr, userData = impl)
when (res) {
0L -> throw OutOfMemoryError()
-1L -> throw Error("libffi error occurred")
}
return res
}
private external fun newGlobalRef(any: Any): Long
private external fun derefGlobalRef(ref: Long): Any
private external fun deleteGlobalRef(ref: Long)
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import sun.misc.Unsafe
private val NativePointed.address: Long
get() = this.rawPtr
private enum class DataModel(val pointerSize: Long) {
_32BIT(4),
_64BIT(8)
}
private val dataModel: DataModel = when (System.getProperty("sun.arch.data.model")) {
null -> TODO()
"32" -> DataModel._32BIT
"64" -> DataModel._64BIT
else -> throw IllegalStateException()
}
// Must be only used in interop, contains host pointer size, not target!
@PublishedApi
internal val pointerSize: Int = dataModel.pointerSize.toInt()
@PublishedApi
internal object nativeMemUtils {
fun getByte(mem: NativePointed) = unsafe.getByte(mem.address)
fun putByte(mem: NativePointed, value: Byte) = unsafe.putByte(mem.address, value)
fun getShort(mem: NativePointed) = unsafe.getShort(mem.address)
fun putShort(mem: NativePointed, value: Short) = unsafe.putShort(mem.address, value)
fun getInt(mem: NativePointed) = unsafe.getInt(mem.address)
fun putInt(mem: NativePointed, value: Int) = unsafe.putInt(mem.address, value)
fun getLong(mem: NativePointed) = unsafe.getLong(mem.address)
fun putLong(mem: NativePointed, value: Long) = unsafe.putLong(mem.address, value)
fun getFloat(mem: NativePointed) = unsafe.getFloat(mem.address)
fun putFloat(mem: NativePointed, value: Float) = unsafe.putFloat(mem.address, value)
fun getDouble(mem: NativePointed) = unsafe.getDouble(mem.address)
fun putDouble(mem: NativePointed, value: Double) = unsafe.putDouble(mem.address, value)
fun getNativePtr(mem: NativePointed): NativePtr = when (dataModel) {
DataModel._32BIT -> getInt(mem).toLong()
DataModel._64BIT -> getLong(mem)
}
fun putNativePtr(mem: NativePointed, value: NativePtr) = when (dataModel) {
DataModel._32BIT -> putInt(mem, value.toInt())
DataModel._64BIT -> putLong(mem, value)
}
fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) {
unsafe.copyMemory(null, source.address, dest, byteArrayBaseOffset, length.toLong())
}
fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) {
unsafe.copyMemory(source, byteArrayBaseOffset, null, dest.address, length.toLong())
}
fun getCharArray(source: NativePointed, dest: CharArray, length: Int) {
unsafe.copyMemory(null, source.address, dest, charArrayBaseOffset, length.toLong() * 2)
}
fun putCharArray(source: CharArray, dest: NativePointed, length: Int) {
unsafe.copyMemory(source, charArrayBaseOffset, null, dest.address, length.toLong() * 2)
}
fun zeroMemory(dest: NativePointed, length: Int): Unit =
unsafe.setMemory(dest.address, length.toLong(), 0)
fun copyMemory(dest: NativePointed, length: Int, src: NativePointed) =
unsafe.copyMemory(src.address, dest.address, length.toLong())
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T> allocateInstance(): T {
return unsafe.allocateInstance(T::class.java) as T
}
fun alloc(size: Long, align: Int): NativePointed {
val address = unsafe.allocateMemory(
if (size == 0L) 1L else size // It is a hack: `sun.misc.Unsafe` can't allocate zero bytes
)
if (address % align != 0L) TODO(align.toString())
return interpretOpaquePointed(address)
}
fun free(mem: NativePtr) {
unsafe.freeMemory(mem)
}
private val unsafe = with(Unsafe::class.java.getDeclaredField("theUnsafe")) {
isAccessible = true
return@with this.get(null) as Unsafe
}
private val byteArrayBaseOffset = unsafe.arrayBaseOffset(ByteArray::class.java).toLong()
private val charArrayBaseOffset = unsafe.arrayBaseOffset(CharArray::class.java).toLong()
}
@@ -0,0 +1,145 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.full.companionObjectInstance
typealias NativePtr = Long
internal typealias NonNullNativePtr = NativePtr
@PublishedApi internal fun NonNullNativePtr.toNativePtr() = this
internal fun NativePtr.toNonNull(): NonNullNativePtr = this
public val nativeNullPtr: NativePtr = 0L
// TODO: the functions below should eventually be intrinsified
@Suppress("DEPRECATION")
private val typeOfCache = ConcurrentHashMap<Class<*>, CVariable.Type>()
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T : CVariable> typeOf() =
@Suppress("DEPRECATION")
typeOfCache.computeIfAbsent(T::class.java) { T::class.companionObjectInstance as CVariable.Type }
/**
* Returns interpretation of entity with given pointer, or `null` if it is null.
*
* @param T must not be abstract
*/
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T : NativePointed> interpretNullablePointed(ptr: NativePtr): T? {
if (ptr == nativeNullPtr) {
return null
} else {
val result = nativeMemUtils.allocateInstance<T>()
result.rawPtr = ptr
return result
}
}
/**
* Creates a [CPointer] from the raw pointer of [NativePtr].
*
* @return a [CPointer] representation, or `null` if the [rawValue] represents native `nullptr`.
*/
fun <T : CPointed> interpretCPointer(rawValue: NativePtr) =
if (rawValue == nativeNullPtr) {
null
} else {
CPointer<T>(rawValue)
}
internal fun CPointer<*>.cPointerToString() = "CPointer(raw=0x%x)".format(rawValue)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class CLength(val value: Int)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CNaturalStruct(vararg val fieldNames: String)
fun <R> staticCFunction(function: () -> R): CPointer<CFunction<() -> R>> =
staticCFunctionImpl(function)
fun <P1, R> staticCFunction(function: (P1) -> R): CPointer<CFunction<(P1) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, R> staticCFunction(function: (P1, P2) -> R): CPointer<CFunction<(P1, P2) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, R> staticCFunction(function: (P1, P2, P3) -> R): CPointer<CFunction<(P1, P2, P3) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, R> staticCFunction(function: (P1, P2, P3, P4) -> R): CPointer<CFunction<(P1, P2, P3, P4) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, R> staticCFunction(function: (P1, P2, P3, P4, P5) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>> =
staticCFunctionImpl(function)
@@ -0,0 +1,109 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import org.jetbrains.kotlin.konan.util.KonanHomeProvider
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
internal fun decodeFromUtf8(bytes: ByteArray) = String(bytes)
internal fun encodeToUtf8(str: String) = str.toByteArray()
fun bitsToFloat(bits: Int): Float = java.lang.Float.intBitsToFloat(bits)
fun bitsToDouble(bits: Long): Double = java.lang.Double.longBitsToDouble(bits)
// TODO: the functions below should eventually be intrinsified
inline fun <reified R : Number> Byte.signExtend(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Short.signExtend(): R = when (R::class.java) {
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Int.signExtend(): R = when (R::class.java) {
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Long.signExtend(): R = when (R::class.java) {
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Number.invalidSignExtension(): R {
throw Error("unable to sign extend ${this.javaClass.simpleName} \"${this}\" to ${R::class.java.simpleName}")
}
inline fun <reified R : Number> Byte.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Short.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Int.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Long.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Number.invalidNarrowing(): R {
throw Error("unable to narrow ${this.javaClass.simpleName} \"${this}\" to ${R::class.java.simpleName}")
}
fun loadKonanLibrary(name: String) {
try {
System.loadLibrary(name)
} catch (e: UnsatisfiedLinkError) {
val fullLibraryName = System.mapLibraryName(name)
val dir = "${KonanHomeProvider.determineKonanHome()}/konan/nativelib"
try {
System.load("$dir/$fullLibraryName")
} catch (e: UnsatisfiedLinkError) {
val tempDir = createTempDir(directory = File(dir)).absolutePath
Files.createLink(Paths.get(tempDir, fullLibraryName), Paths.get(dir, fullLibraryName))
// TODO: Does not work on Windows. May be use FILE_FLAG_DELETE_ON_CLOSE?
File(tempDir).deleteOnExit()
File("$tempDir/$fullLibraryName").deleteOnExit()
System.load("$tempDir/$fullLibraryName")
}
}
}
@@ -0,0 +1,316 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("FINAL_UPPER_BOUND", "NOTHING_TO_INLINE")
package kotlinx.cinterop
@JvmName("plus\$Byte")
inline operator fun <T : ByteVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 1)
@JvmName("plus\$Byte")
inline operator fun <T : ByteVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Short")
inline operator fun <T : ShortVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 2)
@JvmName("plus\$Short")
inline operator fun <T : ShortVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Int")
inline operator fun <T : IntVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 4)
@JvmName("plus\$Int")
inline operator fun <T : IntVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Long")
inline operator fun <T : LongVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 8)
@JvmName("plus\$Long")
inline operator fun <T : LongVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$UByte")
inline operator fun <T : UByteVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 1)
@JvmName("plus\$UByte")
inline operator fun <T : UByteVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$UByte")
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$UShort")
inline operator fun <T : UShortVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 2)
@JvmName("plus\$UShort")
inline operator fun <T : UShortVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$UShort")
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$UShort")
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$UInt")
inline operator fun <T : UIntVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 4)
@JvmName("plus\$UInt")
inline operator fun <T : UIntVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$UInt")
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$UInt")
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$ULong")
inline operator fun <T : ULongVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 8)
@JvmName("plus\$ULong")
inline operator fun <T : ULongVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$ULong")
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$ULong")
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Float")
inline operator fun <T : FloatVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 4)
@JvmName("plus\$Float")
inline operator fun <T : FloatVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Double")
inline operator fun <T : DoubleVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 8)
@JvmName("plus\$Double")
inline operator fun <T : DoubleVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
/* Generated by:
#!/bin/bash
function gen {
echo "@JvmName(\"plus\\\$$1\")"
echo "inline operator fun <T : ${1}VarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? ="
echo " interpretCPointer(this.rawValue + index * ${2})"
echo
echo "@JvmName(\"plus\\\$$1\")"
echo "inline operator fun <T : ${1}VarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? ="
echo " this + index.toLong()"
echo
echo "@JvmName(\"get\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.get(index: Int): T ="
echo " (this + index)!!.pointed.value"
echo
echo "@JvmName(\"set\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.set(index: Int, value: T) {"
echo " (this + index)!!.pointed.value = value"
echo '}'
echo
echo "@JvmName(\"get\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.get(index: Long): T ="
echo " (this + index)!!.pointed.value"
echo
echo "@JvmName(\"set\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.set(index: Long, value: T) {"
echo " (this + index)!!.pointed.value = value"
echo '}'
echo
}
gen Byte 1
gen Short 2
gen Int 4
gen Long 8
gen UByte 1
gen UShort 2
gen UInt 4
gen ULong 8
gen Float 4
gen Double 8
*/
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
@Deprecated("Use StableRef<T> instead", ReplaceWith("StableRef<T>"), DeprecationLevel.ERROR)
typealias StableObjPtr = StableRef<*>
/**
* This class provides a way to create a stable handle to any Kotlin object.
* After [converting to CPointer][asCPointer] it can be safely passed to native code e.g. to be received
* in a Kotlin callback.
*
* Any [StableRef] should be manually [disposed][dispose]
*/
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
public inline class StableRef<out T : Any> @PublishedApi internal constructor(
private val stablePtr: COpaquePointer
) {
companion object {
/**
* Creates a handle for given object.
*/
fun <T : Any> create(any: T) = StableRef<T>(createStablePointer(any))
/**
* Creates [StableRef] from given raw value.
*
* @param value must be a [value] of some [StableRef]
*/
@Deprecated("Use CPointer<*>.asStableRef<T>() instead", ReplaceWith("ptr.asStableRef<T>()"),
DeprecationLevel.ERROR)
fun fromValue(value: COpaquePointer) = value.asStableRef<Any>()
}
@Deprecated("Use .asCPointer() instead", ReplaceWith("this.asCPointer()"), DeprecationLevel.ERROR)
val value: COpaquePointer get() = this.asCPointer()
/**
* Converts the handle to C pointer.
* @see [asStableRef]
*/
fun asCPointer(): COpaquePointer = this.stablePtr
/**
* Disposes the handle. It must not be used after that.
*/
fun dispose() {
disposeStablePointer(this.stablePtr)
}
/**
* Returns the object this handle was [created][StableRef.create] for.
*/
@Suppress("UNCHECKED_CAST")
fun get() = derefStablePointer(this.stablePtr) as T
}
/**
* Converts to [StableRef] this opaque pointer produced by [StableRef.asCPointer].
*/
inline fun <reified T : Any> CPointer<*>.asStableRef(): StableRef<T> = StableRef<T>(this).also { it.get() }
@@ -0,0 +1,506 @@
/*
* Copyright 2010-2019 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
/**
* The entity which has an associated native pointer.
* Subtypes are supposed to represent interpretations of the pointed data or code.
*
* This interface is likely to be handled by compiler magic and shouldn't be subtyped by arbitrary classes.
*
* TODO: the behavior of [equals], [hashCode] and [toString] differs on Native and JVM backends.
*/
public open class NativePointed internal constructor(rawPtr: NonNullNativePtr) {
var rawPtr = rawPtr.toNativePtr()
internal set
}
// `null` value of `NativePointed?` is mapped to `nativeNullPtr`.
public val NativePointed?.rawPtr: NativePtr
get() = if (this != null) this.rawPtr else nativeNullPtr
/**
* Returns interpretation of entity with given pointer.
*
* @param T must not be abstract
*/
public inline fun <reified T : NativePointed> interpretPointed(ptr: NativePtr): T = interpretNullablePointed<T>(ptr)!!
private class OpaqueNativePointed(rawPtr: NativePtr) : NativePointed(rawPtr.toNonNull())
public fun interpretOpaquePointed(ptr: NativePtr): NativePointed = interpretPointed<OpaqueNativePointed>(ptr)
public fun interpretNullableOpaquePointed(ptr: NativePtr): NativePointed? = interpretNullablePointed<OpaqueNativePointed>(ptr)
/**
* Changes the interpretation of the pointed data or code.
*/
public inline fun <reified T : NativePointed> NativePointed.reinterpret(): T = interpretPointed(this.rawPtr)
/**
* C data or code.
*/
public abstract class CPointed(rawPtr: NativePtr) : NativePointed(rawPtr.toNonNull())
/**
* Represents a reference to (possibly empty) sequence of C values.
* It can be either a stable pointer [CPointer] or a sequence of immutable values [CValues].
*
* [CValuesRef] is designed to be used as Kotlin representation of pointer-typed parameters of C functions.
* When passing [CPointer] as [CValuesRef] to the Kotlin binding method, the C function receives exactly this pointer.
* Passing [CValues] has nearly the same semantics as passing by value: the C function receives
* the pointer to the temporary copy of these values, and the caller can't observe the modifications to this copy.
* The copy is valid until the C function returns.
* There are also other implementations of [CValuesRef] that provide temporary pointer,
* e.g. Kotlin Native specific [refTo] functions to pass primitive arrays directly to native.
*/
public abstract class CValuesRef<T : CPointed> {
/**
* If this reference is [CPointer], returns this pointer, otherwise
* allocate storage value in the scope and return it.
*/
public abstract fun getPointer(scope: AutofreeScope): CPointer<T>
}
/**
* The (possibly empty) sequence of immutable C values.
* It is self-contained and doesn't depend on native memory.
*/
public abstract class CValues<T : CVariable> : CValuesRef<T>() {
/**
* Copies the values to [placement] and returns the pointer to the copy.
*/
public override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
// TODO: optimize
public override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CValues<*>) return false
val thisBytes = this.getBytes()
val otherBytes = other.getBytes()
if (thisBytes.size != otherBytes.size) {
return false
}
for (index in 0 .. thisBytes.size - 1) {
if (thisBytes[index] != otherBytes[index]) {
return false
}
}
return true
}
public override fun hashCode(): Int {
var result = 0
for (byte in this.getBytes()) {
result = result * 31 + byte
}
return result
}
public abstract val size: Int
public abstract val align: Int
/**
* Copy the referenced values to [placement] and return placement pointer.
*/
public abstract fun place(placement: CPointer<T>): CPointer<T>
}
public fun <T : CVariable> CValues<T>.placeTo(scope: AutofreeScope) = this.getPointer(scope)
/**
* The single immutable C value.
* It is self-contained and doesn't depend on native memory.
*
* TODO: consider providing an adapter instead of subtyping [CValues].
*/
public abstract class CValue<T : CVariable> : CValues<T>()
/**
* C pointer.
*/
public class CPointer<T : CPointed> internal constructor(@PublishedApi internal val value: NonNullNativePtr) : CValuesRef<T>() {
// TODO: replace by [value].
@Suppress("NOTHING_TO_INLINE")
public inline val rawValue: NativePtr get() = value.toNativePtr()
public override fun equals(other: Any?): Boolean {
if (this === other) {
return true // fast path
}
return (other is CPointer<*>) && (rawValue == other.rawValue)
}
public override fun hashCode(): Int {
return rawValue.hashCode()
}
public override fun toString() = this.cPointerToString()
public override fun getPointer(scope: AutofreeScope) = this
}
/**
* Returns the pointer to this data or code.
*/
public val <T : CPointed> T.ptr: CPointer<T>
get() = interpretCPointer(this.rawPtr)!!
/**
* Returns the corresponding [CPointed].
*
* @param T must not be abstract
*/
public inline val <reified T : CPointed> CPointer<T>.pointed: T
get() = interpretPointed<T>(this.rawValue)
// `null` value of `CPointer?` is mapped to `nativeNullPtr`
public val CPointer<*>?.rawValue: NativePtr
get() = if (this != null) this.rawValue else nativeNullPtr
public fun <T : CPointed> CPointer<*>.reinterpret(): CPointer<T> = interpretCPointer(this.rawValue)!!
public fun <T : CPointed> CPointer<T>?.toLong() = this.rawValue.toLong()
public fun <T : CPointed> Long.toCPointer(): CPointer<T>? = interpretCPointer(nativeNullPtr + this)
/**
* The [CPointed] without any specified interpretation.
*/
public abstract class COpaque(rawPtr: NativePtr) : CPointed(rawPtr) // TODO: should it correspond to COpaquePointer?
/**
* The pointer with an opaque type.
*/
public typealias COpaquePointer = CPointer<out CPointed> // FIXME
/**
* The variable containing a [COpaquePointer].
*/
public typealias COpaquePointerVar = CPointerVarOf<COpaquePointer>
/**
* The C data variable located in memory.
*
* The non-abstract subclasses should represent the (complete) C data type and thus specify size and alignment.
* Each such subclass must have a companion object which is a [Type].
*/
public abstract class CVariable(rawPtr: NativePtr) : CPointed(rawPtr) {
/**
* The (complete) C data type.
*
* @param size the size in bytes of data of this type
* @param align the alignments in bytes that is enough for this data type.
* It may be greater than actually required for simplicity.
*/
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
public open class Type(val size: Long, val align: Int) {
init {
require(size % align == 0L)
}
}
}
@Suppress("DEPRECATION")
public inline fun <reified T : CVariable> sizeOf() = typeOf<T>().size
@Suppress("DEPRECATION")
public inline fun <reified T : CVariable> alignOf() = typeOf<T>().align
/**
* Returns the member of this [CStructVar] which is located by given offset in bytes.
*/
public inline fun <reified T : CPointed> CStructVar.memberAt(offset: Long): T {
return interpretPointed<T>(this.rawPtr + offset)
}
public inline fun <reified T : CVariable> CStructVar.arrayMemberAt(offset: Long): CArrayPointer<T> {
return interpretCPointer<T>(this.rawPtr + offset)!!
}
/**
* The C struct-typed variable located in memory.
*/
public abstract class CStructVar(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
open class Type(size: Long, align: Int) : CVariable.Type(size, align)
}
/**
* The C primitive-typed variable located in memory.
*/
sealed class CPrimitiveVar(rawPtr: NativePtr) : CVariable(rawPtr) {
// aligning by size is obviously enough
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
open class Type(size: Int) : CVariable.Type(size.toLong(), align = size)
}
@Deprecated("Will be removed.")
public interface CEnum {
public val value: Any
}
public abstract class CEnumVar(rawPtr: NativePtr) : CPrimitiveVar(rawPtr)
// generics below are used for typedef support
// these classes are not supposed to be used directly, instead the typealiases are provided.
@Suppress("FINAL_UPPER_BOUND")
public class BooleanVarOf<T : Boolean>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
public class ByteVarOf<T : Byte>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
public class ShortVarOf<T : Short>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(2)
}
@Suppress("FINAL_UPPER_BOUND")
public class IntVarOf<T : Int>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(4)
}
@Suppress("FINAL_UPPER_BOUND")
public class LongVarOf<T : Long>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(8)
}
@Suppress("FINAL_UPPER_BOUND")
public class UByteVarOf<T : UByte>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
public class UShortVarOf<T : UShort>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(2)
}
@Suppress("FINAL_UPPER_BOUND")
public class UIntVarOf<T : UInt>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(4)
}
@Suppress("FINAL_UPPER_BOUND")
public class ULongVarOf<T : ULong>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(8)
}
@Suppress("FINAL_UPPER_BOUND")
public class FloatVarOf<T : Float>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(4)
}
@Suppress("FINAL_UPPER_BOUND")
public class DoubleVarOf<T : Double>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(8)
}
public typealias BooleanVar = BooleanVarOf<Boolean>
public typealias ByteVar = ByteVarOf<Byte>
public typealias ShortVar = ShortVarOf<Short>
public typealias IntVar = IntVarOf<Int>
public typealias LongVar = LongVarOf<Long>
public typealias UByteVar = UByteVarOf<UByte>
public typealias UShortVar = UShortVarOf<UShort>
public typealias UIntVar = UIntVarOf<UInt>
public typealias ULongVar = ULongVarOf<ULong>
public typealias FloatVar = FloatVarOf<Float>
public typealias DoubleVar = DoubleVarOf<Double>
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Boolean> BooleanVarOf<T>.value: T
get() {
val byte = nativeMemUtils.getByte(this)
return byte.toBoolean() as T
}
set(value) = nativeMemUtils.putByte(this, value.toByte())
@Suppress("NOTHING_TO_INLINE")
public inline fun Boolean.toByte(): Byte = if (this) 1 else 0
@Suppress("NOTHING_TO_INLINE")
public inline fun Byte.toBoolean() = (this.toInt() != 0)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Byte> ByteVarOf<T>.value: T
get() = nativeMemUtils.getByte(this) as T
set(value) = nativeMemUtils.putByte(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Short> ShortVarOf<T>.value: T
get() = nativeMemUtils.getShort(this) as T
set(value) = nativeMemUtils.putShort(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Int> IntVarOf<T>.value: T
get() = nativeMemUtils.getInt(this) as T
set(value) = nativeMemUtils.putInt(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Long> LongVarOf<T>.value: T
get() = nativeMemUtils.getLong(this) as T
set(value) = nativeMemUtils.putLong(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : UByte> UByteVarOf<T>.value: T
get() = nativeMemUtils.getByte(this).toUByte() as T
set(value) = nativeMemUtils.putByte(this, value.toByte())
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : UShort> UShortVarOf<T>.value: T
get() = nativeMemUtils.getShort(this).toUShort() as T
set(value) = nativeMemUtils.putShort(this, value.toShort())
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : UInt> UIntVarOf<T>.value: T
get() = nativeMemUtils.getInt(this).toUInt() as T
set(value) = nativeMemUtils.putInt(this, value.toInt())
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : ULong> ULongVarOf<T>.value: T
get() = nativeMemUtils.getLong(this).toULong() as T
set(value) = nativeMemUtils.putLong(this, value.toLong())
// TODO: ensure native floats have the appropriate binary representation
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Float> FloatVarOf<T>.value: T
get() = nativeMemUtils.getFloat(this) as T
set(value) = nativeMemUtils.putFloat(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Double> DoubleVarOf<T>.value: T
get() = nativeMemUtils.getDouble(this) as T
set(value) = nativeMemUtils.putDouble(this, value)
public class CPointerVarOf<T : CPointer<*>>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
/**
* The C data variable containing the pointer to `T`.
*/
public typealias CPointerVar<T> = CPointerVarOf<CPointer<T>>
/**
* The value of this variable.
*/
@Suppress("UNCHECKED_CAST")
public inline var <P : CPointer<*>> CPointerVarOf<P>.value: P?
get() = interpretCPointer<CPointed>(nativeMemUtils.getNativePtr(this)) as P?
set(value) = nativeMemUtils.putNativePtr(this, value.rawValue)
/**
* The code or data pointed by the value of this variable.
*
* @param T must not be abstract
*/
public inline var <reified T : CPointed, reified P : CPointer<T>> CPointerVarOf<P>.pointed: T?
get() = this.value?.pointed
set(value) {
this.value = value?.ptr as P?
}
public inline operator fun <reified T : CVariable> CPointer<T>.get(index: Long): T {
val offset = if (index == 0L) {
0L // optimization for JVM impl which uses reflection for now.
} else {
index * sizeOf<T>()
}
return interpretPointed(this.rawValue + offset)
}
public inline operator fun <reified T : CVariable> CPointer<T>.get(index: Int): T = this.get(index.toLong())
@Suppress("NOTHING_TO_INLINE")
@JvmName("plus\$CPointer")
public inline operator fun <T : CPointerVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * pointerSize)
@Suppress("NOTHING_TO_INLINE")
@JvmName("plus\$CPointer")
public inline operator fun <T : CPointerVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.get(index: Int): T? =
(this + index)!!.pointed.value
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.set(index: Int, value: T?) {
(this + index)!!.pointed.value = value
}
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.get(index: Long): T? =
(this + index)!!.pointed.value
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.set(index: Long, value: T?) {
(this + index)!!.pointed.value = value
}
public typealias CArrayPointer<T> = CPointer<T>
public typealias CArrayPointerVar<T> = CPointerVar<T>
/**
* The C function.
*/
public class CFunction<T : Function<*>>(rawPtr: NativePtr) : CPointed(rawPtr)
@@ -0,0 +1,643 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
public interface NativePlacement {
public fun alloc(size: Long, align: Int): NativePointed
public fun alloc(size: Int, align: Int): NativePointed = alloc(size.toLong(), align)
}
public interface NativeFreeablePlacement : NativePlacement {
public fun free(mem: NativePtr)
}
public fun NativeFreeablePlacement.free(pointer: CPointer<*>) = this.free(pointer.rawValue)
public fun NativeFreeablePlacement.free(pointed: NativePointed) = this.free(pointed.rawPtr)
public object nativeHeap : NativeFreeablePlacement {
override fun alloc(size: Long, align: Int) = nativeMemUtils.alloc(size, align)
override fun free(mem: NativePtr) = nativeMemUtils.free(mem)
}
private typealias Deferred = () -> Unit
public open class DeferScope {
@PublishedApi
internal var topDeferred: Deferred? = null
internal fun executeAllDeferred() {
topDeferred?.let {
it.invoke()
topDeferred = null
}
}
inline fun defer(crossinline block: () -> Unit) {
val currentTop = topDeferred
topDeferred = {
try {
block()
} finally {
// TODO: it is possible to implement chaining without recursion,
// but it would require using an anonymous object here
// which is not yet supported in Kotlin Native inliner.
currentTop?.invoke()
}
}
}
}
public abstract class AutofreeScope : DeferScope(), NativePlacement {
abstract override fun alloc(size: Long, align: Int): NativePointed
}
public open class ArenaBase(private val parent: NativeFreeablePlacement = nativeHeap) : AutofreeScope() {
private var lastChunk: NativePointed? = null
final override fun alloc(size: Long, align: Int): NativePointed {
// Reserve space for a pointer:
val gapForPointer = maxOf(pointerSize, align)
val chunk = parent.alloc(size = gapForPointer + size, align = gapForPointer)
nativeMemUtils.putNativePtr(chunk, lastChunk.rawPtr)
lastChunk = chunk
return interpretOpaquePointed(chunk.rawPtr + gapForPointer.toLong())
}
@PublishedApi
internal fun clearImpl() {
this.executeAllDeferred()
var chunk = lastChunk
while (chunk != null) {
val nextChunk = nativeMemUtils.getNativePtr(chunk)
parent.free(chunk)
chunk = interpretNullableOpaquePointed(nextChunk)
}
}
}
public class Arena(parent: NativeFreeablePlacement = nativeHeap) : ArenaBase(parent) {
fun clear() = this.clearImpl()
}
/**
* Allocates variable of given type.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.alloc(): T =
@Suppress("DEPRECATION")
alloc(typeOf<T>()).reinterpret()
@PublishedApi
@Suppress("DEPRECATION")
internal fun NativePlacement.alloc(type: CVariable.Type): NativePointed =
alloc(type.size, type.align)
/**
* Allocates variable of given type and initializes it applying given block.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.alloc(initialize: T.() -> Unit): T =
alloc<T>().also { it.initialize() }
/**
* Allocates C array of given elements type and length.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long): CArrayPointer<T> =
alloc(sizeOf<T>() * length, alignOf<T>()).reinterpret<T>().ptr
/**
* Allocates C array of given elements type and length.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Int): CArrayPointer<T> =
allocArray(length.toLong())
/**
* Allocates C array of given elements type and length, and initializes its elements applying given block.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long,
initializer: T.(index: Long)->Unit): CArrayPointer<T> {
val res = allocArray<T>(length)
(0 .. length - 1).forEach { index ->
res[index].initializer(index)
}
return res
}
/**
* Allocates C array of given elements type and length, and initializes its elements applying given block.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(
length: Int, initializer: T.(index: Int)->Unit): CArrayPointer<T> = allocArray(length.toLong()) { index ->
this.initializer(index.toInt())
}
/**
* Allocates C array of pointers to given elements.
*/
public fun <T : CPointed> NativePlacement.allocArrayOfPointersTo(elements: List<T?>): CArrayPointer<CPointerVar<T>> {
val res = allocArray<CPointerVar<T>>(elements.size)
elements.forEachIndexed { index, value ->
res[index] = value?.ptr
}
return res
}
/**
* Allocates C array of pointers to given elements.
*/
public fun <T : CPointed> NativePlacement.allocArrayOfPointersTo(vararg elements: T?) =
allocArrayOfPointersTo(listOf(*elements))
/**
* Allocates C array of given values.
*/
public inline fun <reified T : CPointer<*>>
NativePlacement.allocArrayOf(vararg elements: T?): CArrayPointer<CPointerVarOf<T>> {
return allocArrayOf(listOf(*elements))
}
/**
* Allocates C array of given values.
*/
public inline fun <reified T : CPointer<*>>
NativePlacement.allocArrayOf(elements: List<T?>): CArrayPointer<CPointerVarOf<T>> {
val res = allocArray<CPointerVarOf<T>>(elements.size)
var index = 0
while (index < elements.size) {
res[index] = elements[index]
++index
}
return res
}
public fun NativePlacement.allocArrayOf(elements: ByteArray): CArrayPointer<ByteVar> {
val result = allocArray<ByteVar>(elements.size)
nativeMemUtils.putByteArray(elements, result.pointed, elements.size)
return result
}
public fun NativePlacement.allocArrayOf(vararg elements: Float): CArrayPointer<FloatVar> {
val res = allocArray<FloatVar>(elements.size)
var index = 0
while (index < elements.size) {
res[index] = elements[index]
++index
}
return res
}
public fun <T : CPointed> NativePlacement.allocPointerTo() = alloc<CPointerVar<T>>()
@PublishedApi
internal class ZeroValue<T: CVariable>(private val sizeBytes: Int, private val alignBytes: Int): CValue<T>() {
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.zeroMemory(interpretPointed(placement.rawValue), sizeBytes)
return placement
}
override val size get() = sizeBytes
override val align get() = alignBytes
}
@Suppress("NOTHING_TO_INLINE")
public inline fun <T : CVariable> zeroValue(size: Int, align: Int): CValue<T> = ZeroValue(size, align)
public inline fun <reified T : CVariable> zeroValue(): CValue<T> = zeroValue<T>(sizeOf<T>().toInt(), alignOf<T>())
public inline fun <reified T : CVariable> cValue(): CValue<T> = zeroValue<T>()
public fun <T : CVariable> CPointed.readValues(size: Int, align: Int): CValues<T> {
val bytes = ByteArray(size)
nativeMemUtils.getByteArray(this, bytes, size)
return object : CValue<T>() {
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.putByteArray(bytes, interpretPointed(placement.rawValue), bytes.size)
return placement
}
override val size get() = size
override val align get() = align
}
}
public inline fun <reified T : CVariable> T.readValues(count: Int): CValues<T> =
this.readValues<T>(size = count * sizeOf<T>().toInt(), align = alignOf<T>())
public fun <T : CVariable> CPointed.readValue(size: Long, align: Int): CValue<T> {
val bytes = ByteArray(size.toInt())
nativeMemUtils.getByteArray(this, bytes, size.toInt())
return object : CValue<T>() {
override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.putByteArray(bytes, interpretPointed(placement.rawValue), bytes.size)
return placement
}
// Optimization to avoid unneeded virtual calls in base class implementation.
public override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override val size get() = size.toInt()
override val align get() = align
}
}
@Suppress("DEPRECATION")
@PublishedApi internal fun <T : CVariable> CPointed.readValue(type: CVariable.Type): CValue<T> =
readValue(type.size, type.align)
// Note: can't be declared as property due to possible clash with a struct field.
// TODO: find better name.
@Suppress("DEPRECATION")
public inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(typeOf<T>())
public fun <T: CVariable> CValue<T>.write(location: NativePtr) {
this.place(interpretCPointer(location)!!)
}
// TODO: optimize
public fun <T : CVariable> CValues<T>.getBytes(): ByteArray = memScoped {
val result = ByteArray(size)
nativeMemUtils.getByteArray(
source = this@getBytes.placeTo(memScope).reinterpret<ByteVar>().pointed,
dest = result,
length = result.size
)
result
}
/**
* Calls the [block] with temporary copy of this value as receiver.
*/
public inline fun <reified T : CStructVar, R> CValue<T>.useContents(block: T.() -> R): R = memScoped {
this@useContents.placeTo(memScope).pointed.block()
}
public inline fun <reified T : CStructVar> CValue<T>.copy(modify: T.() -> Unit): CValue<T> = useContents {
this.modify()
this.readValue()
}
public inline fun <reified T : CStructVar> cValue(initialize: T.() -> Unit): CValue<T> =
zeroValue<T>().copy(modify = initialize)
public inline fun <reified T : CVariable> createValues(count: Int, initializer: T.(index: Int) -> Unit) = memScoped {
val array = allocArray<T>(count, initializer)
array[0].readValues(count)
}
// TODO: optimize other [cValuesOf] methods:
/**
* Returns sequence of immutable values [CValues] to pass them to C code.
*/
fun cValuesOf(vararg elements: Byte): CValues<ByteVar> = object : CValues<ByteVar>() {
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
nativeMemUtils.putByteArray(elements, interpretPointed(placement.rawValue), elements.size)
return placement
}
override val size get() = 1 * elements.size
override val align get() = 1
}
public fun cValuesOf(vararg elements: Short): CValues<ShortVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Int): CValues<IntVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Long): CValues<LongVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Float): CValues<FloatVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Double): CValues<DoubleVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun <T : CPointed> cValuesOf(vararg elements: CPointer<T>?): CValues<CPointerVar<T>> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun ByteArray.toCValues() = cValuesOf(*this)
public fun ShortArray.toCValues() = cValuesOf(*this)
public fun IntArray.toCValues() = cValuesOf(*this)
public fun LongArray.toCValues() = cValuesOf(*this)
public fun FloatArray.toCValues() = cValuesOf(*this)
public fun DoubleArray.toCValues() = cValuesOf(*this)
public fun <T : CPointed> Array<CPointer<T>?>.toCValues() = cValuesOf(*this)
public fun <T : CPointed> List<CPointer<T>?>.toCValues() = this.toTypedArray().toCValues()
private class CString(val bytes: ByteArray): CValues<ByteVar>() {
override val size get() = bytes.size + 1
override val align get() = 1
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
nativeMemUtils.putByteArray(bytes, placement.pointed, bytes.size)
placement[bytes.size] = 0.toByte()
return placement
}
}
/**
* @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String].
*/
public val String.cstr: CValues<ByteVar>
get() = CString(encodeToUtf8(this))
/**
* @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String].
*/
public val String.utf8: CValues<ByteVar>
get() = CString(encodeToUtf8(this))
/**
* Convert this list of Kotlin strings to C array of C strings,
* allocating memory for the array and C strings with given [AutofreeScope].
*/
public fun List<String>.toCStringArray(autofreeScope: AutofreeScope): CPointer<CPointerVar<ByteVar>> =
autofreeScope.allocArrayOf(this.map { it.cstr.getPointer(autofreeScope) })
/**
* Convert this array of Kotlin strings to C array of C strings,
* allocating memory for the array and C strings with given [AutofreeScope].
*/
public fun Array<String>.toCStringArray(autofreeScope: AutofreeScope): CPointer<CPointerVar<ByteVar>> =
autofreeScope.allocArrayOf(this.map { it.cstr.getPointer(autofreeScope) })
private class U16CString(val chars: CharArray): CValues<UShortVar>() {
override val size get() = 2 * (chars.size + 1)
override val align get() = 2
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<UShortVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<UShortVar>): CPointer<UShortVar> {
nativeMemUtils.putCharArray(chars, placement.pointed, chars.size)
// TODO: fix, after KT-29627 is fixed.
nativeMemUtils.putShort((placement + chars.size)!!.pointed, 0)
return placement
}
}
/**
* @return the value of zero-terminated UTF-16-encoded C string constructed from given [kotlin.String].
*/
public val String.wcstr: CValues<UShortVar>
get() = U16CString(this.toCharArray())
/**
* @return the value of zero-terminated UTF-16-encoded C string constructed from given [kotlin.String].
*/
public val String.utf16: CValues<UShortVar>
get() = U16CString(this.toCharArray())
private class U32CString(val chars: CharArray): CValues<IntVar>() {
override val size get() = 4 * (chars.size + 1)
override val align get() = 4
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<IntVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<IntVar>): CPointer<IntVar> {
var indexIn = 0
var indexOut = 0
while (indexIn < chars.size) {
var value = chars[indexIn++].toInt()
if (value >= 0xd800 && value < 0xdc00) {
// Surrogate pair.
if (indexIn >= chars.size - 1) throw IllegalArgumentException()
indexIn++
val next = chars[indexIn].toInt()
if (next < 0xdc00 || next >= 0xe000) throw IllegalArgumentException()
value = 0x10000 + ((value and 0x3ff) shl 10) + (next and 0x3ff)
}
nativeMemUtils.putInt((placement + indexOut)!!.pointed, value)
indexOut++
}
nativeMemUtils.putInt((placement + indexOut)!!.pointed, 0)
return placement
}
}
/**
* @return the value of zero-terminated UTF-32-encoded C string constructed from given [kotlin.String].
*/
public val String.utf32: CValues<IntVar>
get() = U32CString(this.toCharArray())
// TODO: optimize
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string.
*/
public fun CPointer<ByteVar>.toKStringFromUtf8(): String {
val nativeBytes = this
var length = 0
while (nativeBytes[length] != 0.toByte()) {
++length
}
val bytes = ByteArray(length)
nativeMemUtils.getByteArray(nativeBytes.pointed, bytes, length)
return decodeFromUtf8(bytes)
}
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string.
*/
public fun CPointer<ByteVar>.toKString(): String = this.toKStringFromUtf8()
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-16-encoded C string.
*/
public fun CPointer<ShortVar>.toKStringFromUtf16(): String {
val nativeBytes = this
var length = 0
while (nativeBytes[length] != 0.toShort()) {
++length
}
val chars = CharArray(length)
var index = 0
while (index < length) {
chars[index] = nativeBytes[index].toChar()
++index
}
return String(chars)
}
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-32-encoded C string.
*/
public fun CPointer<IntVar>.toKStringFromUtf32(): String {
val nativeBytes = this
var fromIndex = 0
var toIndex = 0
while (true) {
val value = nativeBytes[fromIndex++]
if (value == 0) break
toIndex++
if (value >= 0x10000 && value <= 0x10ffff) {
toIndex++
}
}
val length = toIndex
val chars = CharArray(length)
fromIndex = 0
toIndex = 0
while (toIndex < length) {
var value = nativeBytes[fromIndex++]
if (value >= 0x10000 && value <= 0x10ffff) {
chars[toIndex++] = (((value - 0x10000) shr 10) or 0xd800).toChar()
chars[toIndex++] = (((value - 0x10000) and 0x3ff) or 0xdc00).toChar()
} else {
chars[toIndex++] = value.toChar()
}
}
return String(chars)
}
/**
* Decodes a string from the bytes in UTF-8 encoding in this array.
* Bytes following the first occurrence of `0` byte, if it occurs, are not decoded.
*
* Malformed byte sequences are replaced by the replacement char `\uFFFD`.
*/
@OptIn(ExperimentalStdlibApi::class)
@SinceKotlin("1.3")
public fun ByteArray.toKString() : String {
val realEndIndex = realEndIndex(this, 0, this.size)
return decodeToString(0, realEndIndex)
}
/**
* Decodes a string from the bytes in UTF-8 encoding in this array or its subrange.
* Bytes following the first occurrence of `0` byte, if it occurs, are not decoded.
*
* @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default.
* @param endIndex the end (exclusive) of the subrange to decode, size of this array by default.
* @param throwOnInvalidSequence specifies whether to throw an exception on malformed byte sequence or replace it by the replacement char `\uFFFD`.
*
* @throws IndexOutOfBoundsException if [startIndex] is less than zero or [endIndex] is greater than the size of this array.
* @throws IllegalArgumentException if [startIndex] is greater than [endIndex].
* @throws CharacterCodingException if the byte array contains malformed UTF-8 byte sequence and [throwOnInvalidSequence] is true.
*/
@OptIn(ExperimentalStdlibApi::class)
@SinceKotlin("1.3")
public fun ByteArray.toKString(
startIndex: Int = 0,
endIndex: Int = this.size,
throwOnInvalidSequence: Boolean = false
) : String {
checkBoundsIndexes(startIndex, endIndex, this.size)
val realEndIndex = realEndIndex(this, startIndex, endIndex)
return decodeToString(startIndex, realEndIndex, throwOnInvalidSequence)
}
private fun realEndIndex(byteArray: ByteArray, startIndex: Int, endIndex: Int): Int {
var index = startIndex
while (index < endIndex && byteArray[index] != 0.toByte()) {
index++
}
return index
}
private fun checkBoundsIndexes(startIndex: Int, endIndex: Int, size: Int) {
if (startIndex < 0 || endIndex > size) {
throw IndexOutOfBoundsException("startIndex: $startIndex, endIndex: $endIndex, size: $size")
}
if (startIndex > endIndex) {
throw IllegalArgumentException("startIndex: $startIndex > endIndex: $endIndex")
}
}
public class MemScope : ArenaBase() {
val memScope: MemScope
get() = this
val <T: CVariable> CValues<T>.ptr: CPointer<T>
get() = this@ptr.getPointer(this@MemScope)
}
// TODO: consider renaming `memScoped` because it now supports `defer`.
/**
* Runs given [block] providing allocation of memory
* which will be automatically disposed at the end of this scope.
*/
public inline fun <R> memScoped(block: MemScope.()->R): R {
val memScope = MemScope()
try {
return memScope.block()
} finally {
memScope.clearImpl()
}
}
public fun COpaquePointer.readBytes(count: Int): ByteArray {
val result = ByteArray(count)
nativeMemUtils.getByteArray(this.reinterpret<ByteVar>().pointed, result, count)
return result
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* This package contains API and runtime support for calling C code from Kotlin (aka Kotlin C interop).
*
* TODO: decide about package location.
*/
package kotlinx.cinterop;
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cinterop
import kotlin.native.internal.ExportForCppRuntime
public class ForeignException internal constructor(val nativeException: Any?): Exception() {
override val message: String = nativeException?.let {
kotlin_ObjCExport_ExceptionDetails(nativeException)
}?: ""
// Current implementation expects NSException type only, which is ensured by CodeGenerator.
@SymbolName("Kotlin_ObjCExport_ExceptionDetails")
private external fun kotlin_ObjCExport_ExceptionDetails(nativeException: Any): String?
}
@ExportForCppRuntime
internal fun CreateForeignException(payload: NativePtr): Throwable
= ForeignException(interpretObjCPointerOrNull<Any?>(payload))
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
import kotlin.native.internal.ExportForCompiler
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <R> CPointer<CFunction<() -> R>>.invoke(): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, R> CPointer<CFunction<(P1) -> R>>.invoke(p1: P1): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, R> CPointer<CFunction<(P1, P2) -> R>>.invoke(p1: P1, p2: P2): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, R> CPointer<CFunction<(P1, P2, P3) -> R>>.invoke(p1: P1, p2: P2, p3: P3): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, R> CPointer<CFunction<(P1, P2, P3, P4) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, R> CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
@@ -0,0 +1,162 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import kotlin.native.*
import kotlin.native.internal.Intrinsic
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
@PublishedApi
internal inline val pointerSize: Int
get() = getPointerSize()
@PublishedApi
@TypedIntrinsic(IntrinsicType.INTEROP_GET_POINTER_SIZE)
internal external fun getPointerSize(): Int
// TODO: do not use singleton because it leads to init-check on any access.
@PublishedApi
internal object nativeMemUtils {
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getByte(mem: NativePointed): Byte
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putByte(mem: NativePointed, value: Byte)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getShort(mem: NativePointed): Short
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putShort(mem: NativePointed, value: Short)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getInt(mem: NativePointed): Int
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putInt(mem: NativePointed, value: Int)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getLong(mem: NativePointed): Long
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putLong(mem: NativePointed, value: Long)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getFloat(mem: NativePointed): Float
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putFloat(mem: NativePointed, value: Float)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getDouble(mem: NativePointed): Double
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putDouble(mem: NativePointed, value: Double)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getNativePtr(mem: NativePointed): NativePtr
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putNativePtr(mem: NativePointed, value: NativePtr)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getVector(mem: NativePointed): Vector128
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putVector(mem: NativePointed, value: Vector128)
// TODO: optimize
fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) {
val sourceArray = source.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
dest[index] = sourceArray[index]
++index
}
}
// TODO: optimize
fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) {
val destArray = dest.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
destArray[index] = source[index]
++index
}
}
// TODO: optimize
fun getCharArray(source: NativePointed, dest: CharArray, length: Int) {
val sourceArray = source.reinterpret<ShortVar>().ptr
var index = 0
while (index < length) {
dest[index] = sourceArray[index].toChar()
++index
}
}
// TODO: optimize
fun putCharArray(source: CharArray, dest: NativePointed, length: Int) {
val destArray = dest.reinterpret<ShortVar>().ptr
var index = 0
while (index < length) {
destArray[index] = source[index].toShort()
++index
}
}
// TODO: optimize
fun zeroMemory(dest: NativePointed, length: Int): Unit {
val destArray = dest.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
destArray[index] = 0
++index
}
}
// TODO: optimize
fun copyMemory(dest: NativePointed, length: Int, src: NativePointed): Unit {
val destArray = dest.reinterpret<ByteVar>().ptr
val srcArray = src.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
destArray[index] = srcArray[index]
++index
}
}
fun alloc(size: Long, align: Int): NativePointed {
val ptr = malloc(size, align)
if (ptr == nativeNullPtr) {
throw OutOfMemoryError("unable to allocate native memory")
}
return interpretOpaquePointed(ptr)
}
fun free(mem: NativePtr) {
cfree(mem)
}
}
public fun CPointer<UShortVar>.toKStringFromUtf16(): String {
val nativeBytes = this
var length = 0
while (nativeBytes[length] != 0.toUShort()) {
++length
}
val chars = kotlin.CharArray(length)
var index = 0
while (index < length) {
chars[index] = nativeBytes[index].toShort().toChar()
++index
}
return String(chars)
}
public fun CPointer<ShortVar>.toKString(): String = this.toKStringFromUtf16()
public fun CPointer<UShortVar>.toKString(): String = this.toKStringFromUtf16()
@SymbolName("Kotlin_interop_malloc")
private external fun malloc(size: Long, align: Int): NativePtr
@SymbolName("Kotlin_interop_free")
private external fun cfree(ptr: NativePtr)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_BITS)
external fun readBits(ptr: NativePtr, offset: Long, size: Int, signed: Boolean): Long
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_BITS)
external fun writeBits(ptr: NativePtr, offset: Long, size: Int, value: Long)
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import kotlin.native.*
@SymbolName("Kotlin_Interop_createStablePointer")
internal external fun createStablePointer(any: Any): COpaquePointer
@SymbolName("Kotlin_Interop_disposeStablePointer")
internal external fun disposeStablePointer(pointer: COpaquePointer)
@PublishedApi
@SymbolName("Kotlin_Interop_derefStablePointer")
internal external fun derefStablePointer(pointer: COpaquePointer): Any
@@ -0,0 +1,123 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import kotlin.native.internal.getNativeNullPtr
import kotlin.native.internal.reinterpret
import kotlin.native.internal.Intrinsic
import kotlin.native.internal.VolatileLambda
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
typealias NativePtr = kotlin.native.internal.NativePtr
internal typealias NonNullNativePtr = kotlin.native.internal.NonNullNativePtr
@Suppress("NOTHING_TO_INLINE")
internal inline fun NativePtr.toNonNull() = this.reinterpret<NativePtr, NonNullNativePtr>()
inline val nativeNullPtr: NativePtr
get() = getNativeNullPtr()
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
fun <T : CVariable> typeOf(): CVariable.Type = throw Error("typeOf() is called with erased argument")
/**
* Performs type cast of the native pointer to given interop type, including null values.
*
* @param T must not be abstract
*/
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun <T : NativePointed> interpretNullablePointed(ptr: NativePtr): T?
/**
* Performs type cast of the [CPointer] from the given raw pointer.
*/
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun <T : CPointed> interpretCPointer(rawValue: NativePtr): CPointer<T>?
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun NativePointed.getRawPointer(): NativePtr
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun CPointer<*>.getRawValue(): NativePtr
internal fun CPointer<*>.cPointerToString() = "CPointer(raw=$rawValue)"
public class Vector128VarOf<T : Vector128>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(size = 16, align = 16)
}
public typealias Vector128Var = Vector128VarOf<Vector128>
public var <T : Vector128> Vector128VarOf<T>.value: T
get() = nativeMemUtils.getVector(this) as T
set(value) = nativeMemUtils.putVector(this, value)
/**
* Returns a pointer to C function which calls given Kotlin *static* function.
*
* @param function must be *static*, i.e. an (unbound) reference to a Kotlin function or
* a closure which doesn't capture any variable
*/
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <R> staticCFunction(@VolatileLambda function: () -> R): CPointer<CFunction<() -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, R> staticCFunction(@VolatileLambda function: (P1) -> R): CPointer<CFunction<(P1) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, R> staticCFunction(@VolatileLambda function: (P1, P2) -> R): CPointer<CFunction<(P1, P2) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, R> staticCFunction(@VolatileLambda function: (P1, P2, P3) -> R): CPointer<CFunction<(P1, P2, P3) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4) -> R): CPointer<CFunction<(P1, P2, P3, P4) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>>
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import kotlin.native.internal.Intrinsic
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
internal fun decodeFromUtf8(bytes: ByteArray): String = bytes.decodeToString()
internal fun encodeToUtf8(str: String): ByteArray = str.encodeToByteArray()
@TypedIntrinsic(IntrinsicType.INTEROP_BITS_TO_FLOAT)
external fun bitsToFloat(bits: Int): Float
@TypedIntrinsic(IntrinsicType.INTEROP_BITS_TO_DOUBLE)
external fun bitsToDouble(bits: Long): Double
// TODO: deprecate.
@TypedIntrinsic(IntrinsicType.INTEROP_SIGN_EXTEND)
external inline fun <reified R : Number> Number.signExtend(): R
// TODO: deprecate.
@TypedIntrinsic(IntrinsicType.INTEROP_NARROW)
external inline fun <reified R : Number> Number.narrow(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Byte.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Short.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Int.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Long.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> UByte.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> UShort.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> UInt.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> ULong.convert(): R
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
internal annotation class JvmName(val name: String)
fun cValuesOf(vararg elements: UByte): CValues<UByteVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun cValuesOf(vararg elements: UShort): CValues<UShortVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun cValuesOf(vararg elements: UInt): CValues<UIntVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun cValuesOf(vararg elements: ULong): CValues<ULongVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun UByteArray.toCValues() = cValuesOf(*this)
fun UShortArray.toCValues() = cValuesOf(*this)
fun UIntArray.toCValues() = cValuesOf(*this)
fun ULongArray.toCValues() = cValuesOf(*this)
@@ -0,0 +1,227 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("NOTHING_TO_INLINE")
package kotlinx.cinterop
import kotlin.native.*
import kotlin.native.internal.ExportTypeInfo
import kotlin.native.internal.ExportForCppRuntime
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
import kotlin.native.internal.FilterExceptions
interface ObjCObject
interface ObjCClass : ObjCObject
interface ObjCClassOf<T : ObjCObject> : ObjCClass // TODO: T should be added to ObjCClass and all meta-classes instead.
typealias ObjCObjectMeta = ObjCClass
interface ObjCProtocol : ObjCObject
@ExportTypeInfo("theForeignObjCObjectTypeInfo")
@kotlin.native.internal.Frozen
internal open class ForeignObjCObject : kotlin.native.internal.ObjCObjectWrapper
abstract class ObjCObjectBase protected constructor() : ObjCObject {
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
annotation class OverrideInit
}
abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {}
fun optional(): Nothing = throw RuntimeException("Do not call me!!!")
@Deprecated(
"Add @OverrideInit to constructor to make it override Objective-C initializer",
level = DeprecationLevel.ERROR
)
@TypedIntrinsic(IntrinsicType.OBJC_INIT_BY)
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
@kotlin.native.internal.ExportForCompiler
private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) {
if (superInitCallResult == null)
throw RuntimeException("Super initialization failed")
if (superInitCallResult.objcPtr() != this.objcPtr())
throw UnsupportedOperationException("Super initializer has replaced object")
}
internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T)
// Note: if this is called for non-frozen object on a wrong worker, the program will terminate.
@SymbolName("Kotlin_Interop_refFromObjC")
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
@ExportForCppRuntime
inline fun <T : Any> interpretObjCPointer(objcPtr: NativePtr): T = interpretObjCPointerOrNull<T>(objcPtr)!!
@SymbolName("Kotlin_Interop_refToObjC")
external fun Any?.objcPtr(): NativePtr
@SymbolName("Kotlin_Interop_createKotlinObjectHolder")
external fun createKotlinObjectHolder(any: Any?): NativePtr
// Note: if this is called for non-frozen underlying ref on a wrong worker, the program will terminate.
inline fun <reified T : Any> unwrapKotlinObjectHolder(holder: Any?): T {
return unwrapKotlinObjectHolderImpl(holder!!.objcPtr()) as T
}
@PublishedApi
@SymbolName("Kotlin_Interop_unwrapKotlinObjectHolder")
external internal fun unwrapKotlinObjectHolderImpl(ptr: NativePtr): Any
class ObjCObjectVar<T>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
class ObjCNotImplementedVar<T : Any?>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
var <T : Any?> ObjCNotImplementedVar<T>.value: T
get() = TODO()
set(value) = TODO()
typealias ObjCStringVarOf<T> = ObjCNotImplementedVar<T>
typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
@TypedIntrinsic(IntrinsicType.OBJC_CREATE_SUPER_STRUCT)
@PublishedApi
internal external fun createObjCSuperStruct(receiver: NativePtr, superClass: NativePtr): NativePtr
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryName: String = "")
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCConstructor(val initSelector: String, val designated: Boolean)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCFactory(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
annotation class InteropStubs()
@PublishedApi
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
internal annotation class ObjCMethodImp(val selector: String, val encoding: String)
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_SELECTOR)
internal external fun objCGetSelector(selector: String): COpaquePointer
@kotlin.native.internal.ExportForCppRuntime("Kotlin_Interop_getObjCClass")
private fun getObjCClassByName(name: NativePtr): NativePtr {
val result = objc_lookUpClass(name)
if (result == nativeNullPtr) {
val className = interpretCPointer<ByteVar>(name)!!.toKString()
val message = """Objective-C class '$className' not found.
|Ensure that the containing framework or library was linked.""".trimMargin()
throw RuntimeException(message)
}
return result
}
@kotlin.native.internal.ExportForCompiler
private fun allocObjCObject(clazz: NativePtr): NativePtr {
val rawResult = objc_allocWithZone(clazz)
if (rawResult == nativeNullPtr) {
throw OutOfMemoryError("Unable to allocate Objective-C object")
}
// Note: `objc_allocWithZone` returns retained pointer, and thus it must be balanced by the caller.
return rawResult
}
@TypedIntrinsic(IntrinsicType.OBJC_GET_OBJC_CLASS)
@kotlin.native.internal.ExportForCompiler
private external fun <T : ObjCObject> getObjCClass(): NativePtr
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER)
internal external fun getMessenger(superClass: NativePtr): COpaquePointer?
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER_STRET)
internal external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
internal class ObjCWeakReferenceImpl : kotlin.native.ref.WeakReferenceImpl() {
@SymbolName("Konan_ObjCInterop_getWeakReference")
external override fun get(): Any?
}
@SymbolName("Konan_ObjCInterop_initWeakReference")
private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
@kotlin.native.internal.ExportForCppRuntime internal fun makeObjCWeakReferenceImpl(objcPtr: NativePtr): ObjCWeakReferenceImpl {
val result = ObjCWeakReferenceImpl()
result.init(objcPtr)
return result
}
// Konan runtme:
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.ERROR)
@SymbolName("Kotlin_Interop_CreateNSStringFromKString")
external fun CreateNSStringFromKString(str: String?): NativePtr
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.ERROR)
@SymbolName("Kotlin_Interop_CreateKStringFromNSString")
external fun CreateKStringFromNSString(ptr: NativePtr): String?
@PublishedApi
@SymbolName("Kotlin_Interop_CreateObjCObjectHolder")
internal external fun createObjCObjectHolder(ptr: NativePtr): Any?
// Objective-C runtime:
@SymbolName("objc_retainAutoreleaseReturnValue")
external fun objc_retainAutoreleaseReturnValue(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_objc_autoreleasePoolPush")
external fun objc_autoreleasePoolPush(): NativePtr
@SymbolName("Kotlin_objc_autoreleasePoolPop")
external fun objc_autoreleasePoolPop(ptr: NativePtr)
@SymbolName("Kotlin_objc_allocWithZone")
@FilterExceptions
private external fun objc_allocWithZone(clazz: NativePtr): NativePtr
@SymbolName("Kotlin_objc_retain")
external fun objc_retain(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_objc_release")
external fun objc_release(ptr: NativePtr)
@SymbolName("Kotlin_objc_lookUpClass")
external fun objc_lookUpClass(name: NativePtr): NativePtr
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cinterop
import kotlin.native.internal.KClassImpl
import kotlin.reflect.KClass
/**
* If [objCClass] is a class generated to Objective-C header for Kotlin class,
* returns [KClass] for that original Kotlin class.
*
* Otherwise returns `null`.
*/
fun getOriginalKotlinClass(objCClass: ObjCClass): KClass<*>? {
val typeInfo = getTypeInfoForClass(objCClass.objcPtr())
if (typeInfo.isNull()) return null
return KClassImpl<Any>(typeInfo)
}
/**
* If [objCProtocol] is a protocol generated to Objective-C header for Kotlin class,
* returns [KClass] for that original Kotlin class.
*
* Otherwise returns `null`.
*/
fun getOriginalKotlinClass(objCProtocol: ObjCProtocol): KClass<*>? {
val typeInfo = getTypeInfoForProtocol(objCProtocol.objcPtr())
if (typeInfo.isNull()) return null
return KClassImpl<Any>(typeInfo)
}
@SymbolName("Kotlin_ObjCInterop_getTypeInfoForClass")
private external fun getTypeInfoForClass(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_ObjCInterop_getTypeInfoForProtocol")
private external fun getTypeInfoForProtocol(ptr: NativePtr): NativePtr
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
inline fun <R> autoreleasepool(block: () -> R): R {
val pool = objc_autoreleasePoolPush()
return try {
block()
} finally {
objc_autoreleasePoolPop(pool)
}
}
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.ERROR)
fun <T : ObjCObject> ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast<T>()
// TODO: null checks
var <T> ObjCObjectVar<T>.value: T
@Suppress("DEPRECATION") get() =
interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
set(value) = nativeMemUtils.putNativePtr(this, value.objcPtr())
/**
* Makes Kotlin method in Objective-C class accessible through Objective-C dispatch
* to be used as action sent by control in UIKit or AppKit.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class ObjCAction
/**
* Makes Kotlin property in Objective-C class settable through Objective-C dispatch
* to be used as IB outlet.
*/
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
annotation class ObjCOutlet
/**
* Makes Kotlin subclass of Objective-C class visible for runtime lookup
* after Kotlin `main` function gets invoked.
*
* Note: runtime lookup can be forced even when the class is referenced statically from
* Objective-C source code by adding `__attribute__((objc_runtime_visible))` to its `@interface`.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class ExportObjCClass(val name: String = "")
@@ -0,0 +1,129 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.cinterop
import kotlin.native.*
data class Pinned<out T : Any> internal constructor(private val stablePtr: COpaquePointer) {
/**
* Disposes the handle. It must not be [used][get] after that.
*/
fun unpin() {
disposeStablePointer(this.stablePtr)
}
/**
* Returns the underlying pinned object.
*/
fun get(): T = @Suppress("UNCHECKED_CAST") (derefStablePointer(stablePtr) as T)
}
fun <T : Any> T.pin() = Pinned<T>(createStablePointer(this))
inline fun <T : Any, R> T.usePinned(block: (Pinned<T>) -> R): R {
val pinned = this.pin()
return try {
block(pinned)
} finally {
pinned.unpin()
}
}
fun Pinned<ByteArray>.addressOf(index: Int): CPointer<ByteVar> = this.get().addressOfElement(index)
fun ByteArray.refTo(index: Int): CValuesRef<ByteVar> = this.usingPinned { addressOf(index) }
fun Pinned<String>.addressOf(index: Int): CPointer<COpaque> = this.get().addressOfElement(index)
fun String.refTo(index: Int): CValuesRef<COpaque> = this.usingPinned { addressOf(index) }
fun Pinned<CharArray>.addressOf(index: Int): CPointer<COpaque> = this.get().addressOfElement(index)
fun CharArray.refTo(index: Int): CValuesRef<COpaque> = this.usingPinned { addressOf(index) }
fun Pinned<ShortArray>.addressOf(index: Int): CPointer<ShortVar> = this.get().addressOfElement(index)
fun ShortArray.refTo(index: Int): CValuesRef<ShortVar> = this.usingPinned { addressOf(index) }
fun Pinned<IntArray>.addressOf(index: Int): CPointer<IntVar> = this.get().addressOfElement(index)
fun IntArray.refTo(index: Int): CValuesRef<IntVar> = this.usingPinned { addressOf(index) }
fun Pinned<LongArray>.addressOf(index: Int): CPointer<LongVar> = this.get().addressOfElement(index)
fun LongArray.refTo(index: Int): CValuesRef<LongVar> = this.usingPinned { addressOf(index) }
// TODO: pinning of unsigned arrays involves boxing as they are inline classes wrapping signed arrays.
fun Pinned<UByteArray>.addressOf(index: Int): CPointer<UByteVar> = this.get().addressOfElement(index)
fun UByteArray.refTo(index: Int): CValuesRef<UByteVar> = this.usingPinned { addressOf(index) }
fun Pinned<UShortArray>.addressOf(index: Int): CPointer<UShortVar> = this.get().addressOfElement(index)
fun UShortArray.refTo(index: Int): CValuesRef<UShortVar> = this.usingPinned { addressOf(index) }
fun Pinned<UIntArray>.addressOf(index: Int): CPointer<UIntVar> = this.get().addressOfElement(index)
fun UIntArray.refTo(index: Int): CValuesRef<UIntVar> = this.usingPinned { addressOf(index) }
fun Pinned<ULongArray>.addressOf(index: Int): CPointer<ULongVar> = this.get().addressOfElement(index)
fun ULongArray.refTo(index: Int): CValuesRef<ULongVar> = this.usingPinned { addressOf(index) }
fun Pinned<FloatArray>.addressOf(index: Int): CPointer<FloatVar> = this.get().addressOfElement(index)
fun FloatArray.refTo(index: Int): CValuesRef<FloatVar> = this.usingPinned { addressOf(index) }
fun Pinned<DoubleArray>.addressOf(index: Int): CPointer<DoubleVar> = this.get().addressOfElement(index)
fun DoubleArray.refTo(index: Int): CValuesRef<DoubleVar> = this.usingPinned { addressOf(index) }
private inline fun <T : Any, P : CPointed> T.usingPinned(
crossinline block: Pinned<T>.() -> CPointer<P>
) = object : CValuesRef<P>() {
override fun getPointer(scope: AutofreeScope): CPointer<P> {
val pinned = this@usingPinned.pin()
scope.defer { pinned.unpin() }
return pinned.block()
}
}
@SymbolName("Kotlin_Arrays_getByteArrayAddressOfElement")
private external fun ByteArray.addressOfElement(index: Int): CPointer<ByteVar>
@SymbolName("Kotlin_Arrays_getStringAddressOfElement")
private external fun String.addressOfElement(index: Int): CPointer<COpaque>
@SymbolName("Kotlin_Arrays_getCharArrayAddressOfElement")
private external fun CharArray.addressOfElement(index: Int): CPointer<COpaque>
@SymbolName("Kotlin_Arrays_getShortArrayAddressOfElement")
private external fun ShortArray.addressOfElement(index: Int): CPointer<ShortVar>
@SymbolName("Kotlin_Arrays_getIntArrayAddressOfElement")
private external fun IntArray.addressOfElement(index: Int): CPointer<IntVar>
@SymbolName("Kotlin_Arrays_getLongArrayAddressOfElement")
private external fun LongArray.addressOfElement(index: Int): CPointer<LongVar>
@SymbolName("Kotlin_Arrays_getByteArrayAddressOfElement")
private external fun UByteArray.addressOfElement(index: Int): CPointer<UByteVar>
@SymbolName("Kotlin_Arrays_getShortArrayAddressOfElement")
private external fun UShortArray.addressOfElement(index: Int): CPointer<UShortVar>
@SymbolName("Kotlin_Arrays_getIntArrayAddressOfElement")
private external fun UIntArray.addressOfElement(index: Int): CPointer<UIntVar>
@SymbolName("Kotlin_Arrays_getLongArrayAddressOfElement")
private external fun ULongArray.addressOfElement(index: Int): CPointer<ULongVar>
@SymbolName("Kotlin_Arrays_getFloatArrayAddressOfElement")
private external fun FloatArray.addressOfElement(index: Int): CPointer<FloatVar>
@SymbolName("Kotlin_Arrays_getDoubleArrayAddressOfElement")
private external fun DoubleArray.addressOfElement(index: Int): CPointer<DoubleVar>
@@ -0,0 +1,97 @@
package kotlinx.cinterop.internal
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class CStruct(val spelling: String) {
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
annotation class MemberAt(val offset: Long)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class ArrayMemberAt(val offset: Long)
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
annotation class BitField(val offset: Long, val size: Int)
@Retention(AnnotationRetention.BINARY)
annotation class VarType(val size: Long, val align: Int)
}
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.BINARY)
public annotation class CCall(val id: String) {
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class CString
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class WCString
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ReturnsRetained
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ConsumesReceiver
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class Consumed
}
/**
* Collection of annotations that allow to store
* constant values.
*/
public object ConstantValue {
@Retention(AnnotationRetention.BINARY)
annotation class Byte(val value: kotlin.Byte)
@Retention(AnnotationRetention.BINARY)
annotation class Short(val value: kotlin.Short)
@Retention(AnnotationRetention.BINARY)
annotation class Int(val value: kotlin.Int)
@Retention(AnnotationRetention.BINARY)
annotation class Long(val value: kotlin.Long)
@Retention(AnnotationRetention.BINARY)
annotation class UByte(val value: kotlin.UByte)
@Retention(AnnotationRetention.BINARY)
annotation class UShort(val value: kotlin.UShort)
@Retention(AnnotationRetention.BINARY)
annotation class UInt(val value: kotlin.UInt)
@Retention(AnnotationRetention.BINARY)
annotation class ULong(val value: kotlin.ULong)
@Retention(AnnotationRetention.BINARY)
annotation class Float(val value: kotlin.Float)
@Retention(AnnotationRetention.BINARY)
annotation class Double(val value: kotlin.Double)
@Retention(AnnotationRetention.BINARY)
annotation class String(val value: kotlin.String)
}
/**
* Denotes property that is an alias to some enum entry.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class CEnumEntryAlias(val entryName: String)
/**
* Stores instance size of the type T: CEnumVar.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class CEnumVarTypeSize(val size: Int)
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
buildscript {
apply from: "$rootDir/gradle/kotlinGradlePlugin.gradle"
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
repositories {
maven {
url buildKotlinCompilerRepo
}
}
dependencies {
implementation project(":Interop:Indexer")
implementation project(":utilities:basic-utils")
api project(path: ":endorsedLibraries:kotlinx.cli", configuration: "jvmRuntimeElements")
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
api "org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion"
api "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
api "org.jetbrains.kotlinx:kotlinx-metadata-klib:$metadataVersion"
testImplementation "junit:junit:4.12"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$buildKotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test:$buildKotlinVersion"
}
compileKotlin {
kotlinOptions {
freeCompilerArgs = ['-Xopt-in=kotlin.ExperimentalUnsignedTypes', '-Xskip-metadata-version-check']
allWarningsAsErrors=true
}
}
@@ -0,0 +1,134 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* objc_msgSend*_stret functions must be used when return value is returned through memory
* pointed by implicit argument, which is passed on the register that would otherwise be used for receiver.
*
* The entire implementation is just the real ABI approximation which is enough for practical cases.
*/
internal fun Type.isStret(target: KonanTarget): Boolean {
val unwrappedType = this.unwrapTypedefs()
val abiInfo: ObjCAbiInfo = when (target) {
KonanTarget.IOS_ARM64,
KonanTarget.TVOS_ARM64 -> DarwinArm64AbiInfo()
KonanTarget.IOS_X64,
KonanTarget.MACOS_X64,
KonanTarget.WATCHOS_X64,
KonanTarget.TVOS_X64 -> DarwinX64AbiInfo()
KonanTarget.WATCHOS_X86 -> DarwinX86AbiInfo()
KonanTarget.IOS_ARM32,
KonanTarget.WATCHOS_ARM32 -> DarwinArm32AbiInfo(target)
else -> error("Cannot generate ObjC stubs for $target.")
}
return abiInfo.shouldUseStret(unwrappedType)
}
/**
* Provides ABI-specific information about target for Objective C interop.
*/
interface ObjCAbiInfo {
fun shouldUseStret(returnType: Type): Boolean
}
class DarwinX64AbiInfo : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean {
return when (returnType) {
is RecordType -> returnType.decl.def!!.size > 16 || returnType.hasUnalignedMembers()
else -> false
}
}
}
class DarwinX86AbiInfo : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean {
// https://github.com/llvm/llvm-project/blob/6c8a34ed9b49704bdd60838143047c62ba9f2502/clang/lib/CodeGen/TargetInfo.cpp#L1243
return when (returnType) {
is RecordType -> {
val size = returnType.decl.def!!.size
val canBePassedInRegisters = (size == 1L || size == 2L || size == 4L || size == 8L)
return !canBePassedInRegisters
}
else -> false
}
}
}
class DarwinArm32AbiInfo(private val target: KonanTarget) : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean = when (target) {
KonanTarget.IOS_ARM32 -> when (returnType) {
is RecordType -> !returnType.isIntegerLikeType()
else -> false
}
// 32-bit watchOS uses armv7k which is effectively Cortex-A7 and
// uses AAPCS16 VPF.
KonanTarget.WATCHOS_ARM32 -> when (returnType) {
is RecordType -> {
// https://github.com/llvm/llvm-project/blob/6c8a34ed9b49704bdd60838143047c62ba9f2502/clang/lib/CodeGen/TargetInfo.cpp#L6165
when {
returnType.decl.def!!.size <= 16 -> false
else -> true
}
}
else -> false
}
else -> error("Unexpected target")
}
}
class DarwinArm64AbiInfo : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean {
// On aarch64 stret is never the case, since an implicit argument gets passed on x8.
return false
}
}
private fun Type.isIntegerLikeType(): Boolean = when (this) {
is RecordType -> {
val def = this.decl.def
if (def == null) {
false
} else {
def.size <= 4 &&
def.members.all {
when (it) {
is BitField -> it.type.isIntegerLikeType()
is Field -> it.offset == 0L && it.type.isIntegerLikeType()
is IncompleteField -> false
}
}
}
}
is ObjCPointer, is PointerType, CharType, is BoolType -> true
is IntegerType -> this.size <= 4
is Typedef -> this.def.aliased.isIntegerLikeType()
is EnumType -> this.def.baseType.isIntegerLikeType()
else -> false
}
private fun Type.hasUnalignedMembers(): Boolean = when (this) {
is Typedef -> this.def.aliased.hasUnalignedMembers()
is RecordType -> this.decl.def!!.let { def ->
def.fields.any {
!it.isAligned ||
// Check members of fields too:
it.type.hasUnalignedMembers()
}
}
is ArrayType -> this.elemType.hasUnalignedMembers()
else -> false
// TODO: should the recursive checks be made in indexer when computing `hasUnalignedFields`?
}
@@ -0,0 +1,94 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.FunctionDecl
import org.jetbrains.kotlin.native.interop.indexer.GlobalDecl
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
internal data class CCalleeWrapper(val lines: List<String>)
/**
* Some functions don't have an address (e.g. macros-based or builtins).
* To solve this problem we generate a wrapper function.
*/
internal class CWrappersGenerator(private val context: StubIrContext) {
private var currentFunctionWrapperId = 0
private val packageName =
context.configuration.pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_")
private fun generateFunctionWrapperName(functionName: String): String {
return "${packageName}_${functionName}_wrapper${currentFunctionWrapperId++}"
}
private fun bindSymbolToFunction(symbol: String, function: String): List<String> = listOf(
"const void* $symbol __asm(${symbol.quoteAsKotlinLiteral()});",
"const void* $symbol = &$function;"
)
private data class Parameter(val type: String, val name: String)
private fun createWrapper(
symbolName: String,
wrapperName: String,
returnType: String,
parameters: List<Parameter>,
body: String
): List<String> = listOf(
"__attribute__((always_inline))",
"$returnType $wrapperName(${parameters.joinToString { "${it.type} ${it.name}" }}) {",
body,
"}",
*bindSymbolToFunction(symbolName, wrapperName).toTypedArray()
)
fun generateCCalleeWrapper(function: FunctionDecl, symbolName: String): CCalleeWrapper =
if (function.isVararg) {
CCalleeWrapper(bindSymbolToFunction(symbolName, function.name))
} else {
val wrapperName = generateFunctionWrapperName(function.name)
val returnType = function.returnType.getStringRepresentation()
val parameters = function.parameters.mapIndexed { index, parameter ->
Parameter(parameter.type.getStringRepresentation(), "p$index")
}
val callExpression = "${function.name}(${parameters.joinToString { it.name }});"
val wrapperBody = if (function.returnType.unwrapTypedefs() is VoidType) {
callExpression
} else {
"return $callExpression"
}
val wrapper = createWrapper(symbolName, wrapperName, returnType, parameters, wrapperBody)
CCalleeWrapper(wrapper)
}
fun generateCGlobalGetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_getter")
val returnType = globalDecl.type.getStringRepresentation()
val wrapperBody = "return ${globalDecl.name};"
val wrapper = createWrapper(symbolName, wrapperName, returnType, emptyList(), wrapperBody)
return CCalleeWrapper(wrapper)
}
fun generateCGlobalByPointerGetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_getter")
val returnType = "void*"
val wrapperBody = "return &${globalDecl.name};"
val wrapper = createWrapper(symbolName, wrapperName, returnType, emptyList(), wrapperBody)
return CCalleeWrapper(wrapper)
}
fun generateCGlobalSetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_setter")
val globalType = globalDecl.type.getStringRepresentation()
val parameter = Parameter(globalType, "p1")
val wrapperBody = "${globalDecl.name} = ${parameter.name};"
val wrapper = createWrapper(symbolName, wrapperName, "void", listOf(parameter), wrapperBody)
return CCalleeWrapper(wrapper)
}
}
@@ -0,0 +1,103 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
interface NativeScope {
val mappingBridgeGenerator: MappingBridgeGenerator
}
class NativeCodeBuilder(val scope: NativeScope) {
val lines = mutableListOf<String>()
fun out(line: String): Unit {
lines.add(line)
}
}
inline fun buildNativeCodeLines(scope: NativeScope, block: NativeCodeBuilder.() -> Unit): List<String> {
val builder = NativeCodeBuilder(scope)
builder.block()
return builder.lines
}
private class Block(val nesting: Int, val start: String, val end: String) {
val prologue = mutableListOf<String>()
val body = mutableListOf<String>()
val epilogue = mutableListOf<String>()
fun indent(line: String) = " ".repeat(nesting) + line
fun indentBraces(line: String) = " ".repeat(nesting - 1) + line
}
class KotlinCodeBuilder(val scope: KotlinScope) {
private val blocks = mutableListOf<Block>()
init {
pushBlock("", "")
}
fun out(line: String) {
currentBlock().body += line
}
private var memScoped = false
fun pushMemScoped() {
if (!memScoped) {
memScoped = true
pushBlock("memScoped {")
}
}
fun getNativePointer(name: String): String {
return "$name?.getPointer(memScope)"
}
fun returnResult(result: String) {
currentBlock().body += "return $result"
}
private fun currentBlock() = blocks.last()
fun pushBlock(start: String, end: String = "}") {
val block = Block(blocks.size, start = start, end = end)
blocks += block
}
private fun emitBlockAndNested(position: Int, lines: MutableList<String>) {
if (position >= blocks.size) return
val block = blocks[position]
if (block.start.isNotEmpty()) lines += block.indentBraces(block.start)
lines += block.prologue.map { block.indent(it) }
lines += block.body.map { block.indent(it) }
emitBlockAndNested(position + 1, lines)
lines += block.epilogue.map { block.indent(it) }
if (block.end.isNotEmpty()) lines += block.indentBraces(block.end)
}
fun build(): List<String> {
val lines = mutableListOf<String>()
emitBlockAndNested(0, lines)
return lines.toList()
}
}
inline fun buildKotlinCodeLines(scope: KotlinScope, block: KotlinCodeBuilder.() -> Unit): List<String> {
val builder = KotlinCodeBuilder(scope)
builder.block()
return builder.build()
}
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
val kotlinKeywords = setOf(
"as", "break", "class", "continue", "do", "dynamic", "else", "false", "for", "fun", "if", "in",
"interface", "is", "null", "object", "package", "return", "super", "this", "throw",
"true", "try", "typealias", "val", "var", "when", "while",
// While not technically keywords, those shall be escaped as well.
"_", "__", "___"
)
/**
* The expression written in native language.
*/
typealias NativeExpression = String
/**
* The expression written in Kotlin.
*/
typealias KotlinExpression = String
/**
* For this identifier constructs the string to be parsed by Kotlin as `SimpleName`
* defined [here](https://kotlinlang.org/docs/reference/grammar.html#SimpleName).
*/
fun String.asSimpleName(): String = if (this in kotlinKeywords || this.contains("$")) {
"`$this`"
} else {
this
}
/**
* Yet another mangler, particularly to avoid secondary clash, e.g. when a property
* in prototype (interface) is mangled and that will cause another clash in the class
* which implements this interface.
* Rationale: keep algorithm simple but use the mangling characters which are rare
* in normal code, and keep mangling easy readable.
*/
internal fun mangleSimple(name: String): String {
val reserved = setOf("Companion")
val postfix = "\$"
return if (name in reserved)
"$name$postfix"
else
name
}
/**
* Returns the expression to be parsed by Kotlin as string literal with given contents,
* i.e. transforms `foo$bar` to `"foo\$bar"`.
*/
fun String.quoteAsKotlinLiteral(): KotlinExpression = buildString {
append('"')
this@quoteAsKotlinLiteral.forEach { c ->
when (c) {
in charactersAllowedInKotlinStringLiterals -> append(c)
'$' -> append("\\$")
else -> append("\\u" + "%04X".format(c.toInt()))
}
}
append('"')
}
// TODO: improve literal readability by preserving more characters.
private val charactersAllowedInKotlinStringLiterals: Set<Char> = mutableSetOf<Char>().apply {
addAll('a' .. 'z')
addAll('A' .. 'Z')
addAll('0' .. '9')
addAll(listOf('_', '@', ':', ';', '.', ',', '{', '}', '=', '[', ']', '^', '#', '*', ' ', '(', ')'))
}
val annotationForUnableToImport
get() = "@Deprecated(${"Unable to import this declaration".quoteAsKotlinLiteral()}, level = DeprecationLevel.ERROR)"
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.native.interop.indexer.*
interface Imports {
fun getPackage(location: Location): String?
}
class PackageInfo(val name: String, val library: KonanLibrary)
class ImportsImpl(internal val headerIdToPackage: Map<HeaderId, PackageInfo>) : Imports {
override fun getPackage(location: Location): String? {
val packageInfo = headerIdToPackage[location.headerId]
?: return null
accessedLibraries += packageInfo.library
return packageInfo.name
}
private val accessedLibraries = mutableSetOf<KonanLibrary>()
val requiredLibraries: Set<KonanLibrary>
get() = accessedLibraries.toSet()
}
class HeaderInclusionPolicyImpl(private val nameGlobs: List<String>) : HeaderInclusionPolicy {
override fun excludeUnused(headerName: String?): Boolean {
if (nameGlobs.isEmpty()) {
return false
}
if (headerName == null) {
// Builtins; included only if no globs are specified:
return true
}
return nameGlobs.all { !headerName.matchesToGlob(it) }
}
}
class HeaderExclusionPolicyImpl(
private val importsImpl: ImportsImpl
) : HeaderExclusionPolicy {
override fun excludeAll(headerId: HeaderId): Boolean {
return headerId in importsImpl.headerIdToPackage
}
}
private fun String.matchesToGlob(glob: String): Boolean =
java.nio.file.FileSystems.getDefault()
.getPathMatcher("glob:$glob").matches(java.nio.file.Paths.get(this))
@@ -0,0 +1,346 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import kotlin.reflect.KProperty
interface KotlinScope {
/**
* @return the string to be used to reference the classifier in current scope.
*/
fun reference(classifier: Classifier): String
/**
* @return the string to be used as a name in the declaration of the classifier in current scope.
*/
fun declare(classifier: Classifier): String
/**
* @return the string to be used as a name in the declaration of the property in current scope,
* or `null` if the property with given name can't be declared.
*/
fun declareProperty(receiver: String?, name: String): String?
val mappingBridgeGenerator: MappingBridgeGenerator
}
data class Classifier(
val pkg: String,
val topLevelName: String,
private val nestedNames: List<String> = emptyList()
) {
companion object {
fun topLevel(pkg: String, name: String): Classifier {
assert(!name.contains('.'))
assert(!name.contains('`'))
return Classifier(pkg, name)
}
}
val isTopLevel: Boolean get() = this.nestedNames.isEmpty()
fun nested(name: String): Classifier {
assert(!name.contains('.'))
assert(!name.contains('`'))
return this.copy(nestedNames = nestedNames + name)
}
fun getRelativeFqName(asSimpleName: Boolean = true): String = buildString {
append(topLevelName.run { if (asSimpleName) asSimpleName() else this })
nestedNames.forEach {
append('.')
append(it.run { if (asSimpleName) asSimpleName() else this })
}
}
val fqName: String get() = buildString {
if (pkg.isNotEmpty()) {
append(pkg)
append('.')
}
append(getRelativeFqName())
}
}
val Classifier.type
get() = KotlinClassifierType(this, arguments = emptyList(), nullable = false, underlyingType = null)
fun Classifier.typeWith(vararg arguments: KotlinTypeArgument) =
KotlinClassifierType(this, arguments.toList(), nullable = false, underlyingType = null)
fun Classifier.typeAbbreviation(expandedType: KotlinType) =
KotlinClassifierType(this, arguments = emptyList(), nullable = false, underlyingType = expandedType)
interface KotlinTypeArgument {
/**
* @return the string to be used in the given scope to denote this.
*/
fun render(scope: KotlinScope): String
}
object StarProjection : KotlinTypeArgument {
override fun render(scope: KotlinScope) = "*"
}
interface KotlinType : KotlinTypeArgument {
val classifier: Classifier
fun makeNullableAsSpecified(nullable: Boolean): KotlinType
}
/**
* @property underlyingType is non-null if this type is an alias to another type.
*/
data class KotlinClassifierType(
override val classifier: Classifier,
val arguments: List<KotlinTypeArgument>,
val nullable: Boolean,
val underlyingType: KotlinType?
) : KotlinType {
override fun makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
this
} else {
this.copy(nullable = nullable)
}
override fun render(scope: KotlinScope): String = buildString {
append(scope.reference(classifier))
if (arguments.isNotEmpty()) {
append('<')
arguments.joinTo(this) { it.render(scope) }
append('>')
}
if (nullable) {
append('?')
}
}
}
fun KotlinType.makeNullable() = this.makeNullableAsSpecified(true)
data class KotlinFunctionType(
val parameterTypes: List<KotlinType>,
val returnType: KotlinType,
val nullable: Boolean = false
) : KotlinType {
override fun makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
this
} else {
this.copy(nullable = nullable)
}
override val classifier by lazy {
Classifier.topLevel("kotlin", "Function${parameterTypes.size}")
}
override fun render(scope: KotlinScope) = buildString {
if (nullable) append("(")
append('(')
parameterTypes.joinTo(this) { it.render(scope) }
append(") -> ")
append(returnType.render(scope))
if (nullable) append(")?")
}
}
internal val cnamesStructsPackageName = "cnames.structs"
object KotlinTypes {
val independent = Classifier.topLevel("kotlin.native.internal", "Independent")
val boolean by BuiltInType
val byte by BuiltInType
val short by BuiltInType
val int by BuiltInType
val long by BuiltInType
val uByte by BuiltInType
val uShort by BuiltInType
val uInt by BuiltInType
val uLong by BuiltInType
val float by BuiltInType
val double by BuiltInType
val unit by BuiltInType
val string by BuiltInType
val any by BuiltInType
val list by CollectionClassifier
val mutableList by CollectionClassifier
val set by CollectionClassifier
val map by CollectionClassifier
val nativePtr by InteropType
val vector128 by KotlinNativeType
val cOpaque by InteropType
val cOpaquePointer by InteropType
val cOpaquePointerVar by InteropType
val booleanVarOf by InteropClassifier
val objCObject by InteropClassifier
val objCObjectMeta by InteropClassifier
val objCClass by InteropClassifier
val objCClassOf by InteropClassifier
val objCProtocol by InteropClassifier
val cValuesRef by InteropClassifier
val cPointed by InteropClassifier
val cPointer by InteropClassifier
val cPointerVar by InteropClassifier
val cArrayPointer by InteropClassifier
val cArrayPointerVar by InteropClassifier
val cPointerVarOf by InteropClassifier
val cFunction by InteropClassifier
val objCObjectVar by InteropClassifier
val objCObjectBase by InteropClassifier
val objCObjectBaseMeta by InteropClassifier
val objCBlockVar by InteropClassifier
val objCNotImplementedVar by InteropClassifier
val cValue by InteropClassifier
private open class ClassifierAtPackage(val pkg: String) {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): Classifier =
Classifier.topLevel(pkg, property.name.capitalize())
}
private open class TypeAtPackage(val pkg: String) {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): KotlinClassifierType =
Classifier.topLevel(pkg, property.name.capitalize()).type
}
private object BuiltInType : TypeAtPackage("kotlin")
private object CollectionClassifier : ClassifierAtPackage("kotlin.collections")
private object InteropClassifier : ClassifierAtPackage("kotlinx.cinterop")
private object InteropType : TypeAtPackage("kotlinx.cinterop")
private object KotlinNativeType : TypeAtPackage("kotlin.native")
}
abstract class KotlinFile(
val pkg: String,
namesToBeDeclared: List<String>
) : KotlinScope {
// Note: all names are related to classifiers currently.
private val namesToBeDeclared: Set<String>
init {
this.namesToBeDeclared = mutableSetOf()
namesToBeDeclared.forEach {
if (it in this.namesToBeDeclared) {
throw IllegalArgumentException("'$it' is going to be declared twice")
} else {
this.namesToBeDeclared.add(it)
}
}
}
private val importedNameToPkg = mutableMapOf<String, String>()
private val declaredProperties = mutableSetOf<String>()
override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) {
if (classifier.pkg == this.pkg) {
classifier.getRelativeFqName()
} else {
// Don't import if would clash with own declaration:
classifier.fqName
}
} else if (classifier.pkg == this.pkg) {
throw IllegalArgumentException(
"'${classifier.topLevelName}' from the file package was not reserved for declaration"
)
} else {
if (tryImport(classifier)) {
// Is successfully imported:
classifier.getRelativeFqName()
} else {
classifier.fqName
}
}
private fun tryImport(classifier: Classifier): Boolean {
if (classifier.topLevelName in declaredProperties) {
return false
}
return importedNameToPkg.getOrPut(classifier.topLevelName) { classifier.pkg } == classifier.pkg
}
private val alreadyDeclared = mutableSetOf<String>()
override fun declare(classifier: Classifier): String {
if (classifier.pkg != this.pkg) {
throw IllegalArgumentException("wrong package for classifier ${classifier.fqName}; expected '$pkg', got '${classifier.pkg}'")
}
if (!classifier.isTopLevel) {
throw IllegalArgumentException(
"'${classifier.getRelativeFqName()}' is not top-level thus can't be declared at file scope"
)
}
val topLevelName = classifier.topLevelName
if (topLevelName in alreadyDeclared) {
throw IllegalStateException("'$topLevelName' is already declared")
}
alreadyDeclared.add(topLevelName)
return topLevelName
}
override fun declareProperty(receiver: String?, name: String): String? {
val fullName = receiver?.let { "$it.${name}" } ?: name
return if (fullName in declaredProperties || name in namesToBeDeclared || name in importedNameToPkg) {
null
// TODO: using original global name should be preferred to importing the clashed name.
} else {
declaredProperties.add(fullName)
name
}
}
fun buildImports(): List<String> = importedNameToPkg.mapNotNull { (name, pkg) ->
if (pkg == "kotlin" || pkg == "kotlinx.cinterop") {
// Is already imported either by default or with '*':
null
} else {
"import $pkg.${name.asSimpleName()}"
}
}.sorted()
}
internal fun getTopLevelPropertyDeclarationName(scope: KotlinScope, property: PropertyStub): String {
val receiverName = property.receiverType?.underlyingTypeFqName
return getTopLevelPropertyDeclarationName(scope, receiverName, property.name)
}
// Try to use the provided name. If failed, mangle it with underscore and try again:
private tailrec fun getTopLevelPropertyDeclarationName(scope: KotlinScope, receiver: String?, name: String): String =
scope.declareProperty(receiver, name) ?: getTopLevelPropertyDeclarationName(scope, receiver, name + "_")
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.file.File
internal fun resolveLibraries(staticLibraries: List<String>, libraryPaths: List<String>): List<String> {
val result = mutableListOf<String>()
staticLibraries.forEach { library ->
val resolution = libraryPaths.map { "$it/$library" }
.find { File(it).exists }
if (resolution != null) {
result.add(resolution)
} else {
error("Could not find '$library' binary in neither of $libraryPaths")
}
}
return result
}
internal fun argsToCompiler(staticLibraries: Array<String>, libraryPaths: Array<String>) = argsToCompiler(staticLibraries.toList(), libraryPaths.toList())
internal fun argsToCompiler(staticLibraries: List<String>, libraryPaths: List<String>) =
resolveLibraries(staticLibraries, libraryPaths)
.map { it -> listOf("-include-binary", it) }
.flatten()
.toTypedArray()
@@ -0,0 +1,46 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.Type
data class TypedKotlinValue(val type: Type, val value: KotlinExpression)
data class TypedNativeValue(val type: Type, val value: NativeExpression)
/**
* Generates bridges between Kotlin and native, passing arbitrary native-typed values.
*
* It does the same as [SimpleBridgeGenerator] except that it supports any native types, e.g. struct values.
*/
interface MappingBridgeGenerator {
fun kotlinToNative(
builder: KotlinCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
kotlinValues: List<TypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression
fun nativeToKotlin(
builder: NativeCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
nativeValues: List<TypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression
}
@@ -0,0 +1,207 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
* maps the type using [mirror].
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator
) : MappingBridgeGenerator {
override fun kotlinToNative(
builder: KotlinCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
kotlinValues: List<TypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression {
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
kotlinValues.forEach { (type, value) ->
if (type.unwrapTypedefs() is RecordType) {
builder.pushMemScoped()
val bridgeArgument = "$value.getPointer(memScope).rawValue"
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, bridgeArgument))
} else {
val info = mirror(declarationMapper, type).info
bridgeArguments.add(BridgeTypedKotlinValue(info.bridgedType, info.argToBridged(value)))
}
}
val unwrappedReturnType = returnType.unwrapTypedefs()
val kniRetVal = "kniRetVal"
val bridgeReturnType = when (unwrappedReturnType) {
VoidType -> BridgedType.VOID
is RecordType -> {
val mirror = mirror(declarationMapper, returnType)
val tmpVarName = kniRetVal
// We clear in the finally block.
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedType.render(builder.scope)}>()")
builder.pushBlock(start = "try {", end = "} finally { nativeHeap.free($tmpVarName) }")
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr"))
BridgedType.VOID
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.bridgedType
}
}
val callExpr = simpleBridgeGenerator.kotlinToNative(
nativeBacked, bridgeReturnType, bridgeArguments, independent
) { bridgeNativeValues ->
val nativeValues = mutableListOf<String>()
kotlinValues.forEachIndexed { index, (type, _) ->
val unwrappedType = type.unwrapTypedefs()
if (unwrappedType is RecordType) {
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
} else {
nativeValues.add(
mirror(declarationMapper, type).info.cFromBridged(
bridgeNativeValues[index], scope, nativeBacked
)
)
}
}
val nativeResult = block(nativeValues)
when (unwrappedReturnType) {
is VoidType -> {
out(nativeResult + ";")
""
}
is RecordType -> {
val kniStructResult = "kniStructResult"
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
""
}
else -> {
nativeResult
}
}
}
val result = when (unwrappedReturnType) {
is VoidType -> callExpr
is RecordType -> {
builder.out(callExpr)
"$kniRetVal.readValue()"
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.argFromBridged(callExpr, builder.scope, nativeBacked)
}
}
return result
}
override fun nativeToKotlin(
builder: NativeCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
nativeValues: List<TypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression {
val bridgeArguments = mutableListOf<BridgeTypedNativeValue>()
nativeValues.forEachIndexed { _, (type, value) ->
val bridgeArgument = if (type.unwrapTypedefs() is RecordType) {
BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$value")
} else {
val info = mirror(declarationMapper, type).info
BridgeTypedNativeValue(info.bridgedType, value)
}
bridgeArguments.add(bridgeArgument)
}
val unwrappedReturnType = returnType.unwrapTypedefs()
val kniRetVal = "kniRetVal"
val bridgeReturnType = when (unwrappedReturnType) {
VoidType -> BridgedType.VOID
is RecordType -> {
val tmpVarName = kniRetVal
builder.out("${unwrappedReturnType.decl.spelling} $tmpVarName;")
bridgeArguments.add(BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$tmpVarName"))
BridgedType.VOID
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.bridgedType
}
}
val callExpr = simpleBridgeGenerator.nativeToKotlin(
nativeBacked,
bridgeReturnType,
bridgeArguments
) { bridgeKotlinValues ->
val kotlinValues = mutableListOf<String>()
nativeValues.forEachIndexed { index, (type, _) ->
val mirror = mirror(declarationMapper, type)
if (type.unwrapTypedefs() is RecordType) {
val pointedTypeName = mirror.pointedType.render(this.scope)
kotlinValues.add(
"interpretPointed<$pointedTypeName>(${bridgeKotlinValues[index]}).readValue()"
)
} else {
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index], this.scope, nativeBacked))
}
}
val kotlinResult = block(kotlinValues)
when (unwrappedReturnType) {
is RecordType -> {
"$kotlinResult.write(${bridgeKotlinValues.last()})"
}
is VoidType -> {
kotlinResult
}
else -> {
mirror(declarationMapper, returnType).info.argToBridged(kotlinResult)
}
}
}
val result = when (unwrappedReturnType) {
is VoidType -> callExpr
is RecordType -> {
builder.out("$callExpr;")
kniRetVal
}
else -> {
mirror(declarationMapper, returnType).info.cFromBridged(callExpr, builder.scope, nativeBacked)
}
}
return result
}
}
@@ -0,0 +1,578 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
interface DeclarationMapper {
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
fun isMappedToStrict(enumDef: EnumDef): Boolean
fun getKotlinNameForValue(enumDef: EnumDef): String
fun getPackageFor(declaration: TypeDeclaration): String
val useUnsignedTypes: Boolean
}
fun DeclarationMapper.isMappedToSigned(integerType: IntegerType): Boolean = integerType.isSigned || !useUnsignedTypes
fun DeclarationMapper.getKotlinClassFor(
objCClassOrProtocol: ObjCClassOrProtocol,
isMeta: Boolean = false
): Classifier {
val pkg = if (objCClassOrProtocol.isForwardDeclaration) {
when (objCClassOrProtocol) {
is ObjCClass -> "objcnames.classes"
is ObjCProtocol -> "objcnames.protocols"
}
} else {
this.getPackageFor(objCClassOrProtocol)
}
val className = objCClassOrProtocol.kotlinClassName(isMeta)
return Classifier.topLevel(pkg, className)
}
fun PrimitiveType.getKotlinType(declarationMapper: DeclarationMapper): KotlinClassifierType = when (this) {
is CharType -> KotlinTypes.byte
is BoolType -> KotlinTypes.boolean
// TODO: C primitive types should probably be generated as type aliases for Kotlin types.
is IntegerType -> if (declarationMapper.isMappedToSigned(this)) {
when (this.size) {
1 -> KotlinTypes.byte
2 -> KotlinTypes.short
4 -> KotlinTypes.int
8 -> KotlinTypes.long
else -> TODO(this.toString())
}
} else {
when (this.size) {
1 -> KotlinTypes.uByte
2 -> KotlinTypes.uShort
4 -> KotlinTypes.uInt
8 -> KotlinTypes.uLong
else -> TODO(this.toString())
}
}
is FloatingType -> when (this.size) {
4 -> KotlinTypes.float
8 -> KotlinTypes.double
else -> TODO(this.toString())
}
is VectorType -> {
/// @todo assert elementType and size here
KotlinTypes.vector128
}
else -> throw NotImplementedError()
}
private fun PrimitiveType.getBridgedType(declarationMapper: DeclarationMapper): BridgedType {
val kotlinType = this.getKotlinType(declarationMapper)
return BridgedType.values().single {
it.kotlinType == kotlinType
}
}
internal val ObjCPointer.isNullable: Boolean
get() = this.nullability != ObjCPointer.Nullability.NonNull
/**
* Describes the Kotlin types used to represent some C type.
*/
sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInfo) {
/**
* Type to be used in bindings for argument or return value.
*/
abstract val argType: KotlinType
/**
* Mirror for C type to be represented in Kotlin as by-value type.
*/
class ByValue(
pointedType: KotlinClassifierType,
info: TypeInfo,
val valueType: KotlinType,
val nullable: Boolean = (info is TypeInfo.Pointer)
) : TypeMirror(pointedType, info) {
override val argType: KotlinType
get() = valueType.makeNullableAsSpecified(nullable)
}
/**
* Mirror for C type to be represented in Kotlin as by-ref type.
*/
class ByRef(pointedType: KotlinClassifierType, info: TypeInfo) : TypeMirror(pointedType, info) {
override val argType: KotlinType get() = KotlinTypes.cValue.typeWith(pointedType)
}
}
/**
* Describes various type conversions for [TypeMirror].
*/
sealed class TypeInfo {
/**
* The conversion from [TypeMirror.argType] to [bridgedType].
*/
abstract fun argToBridged(expr: KotlinExpression): KotlinExpression
/**
* The conversion from [bridgedType] to [TypeMirror.argType].
*/
abstract fun argFromBridged(
expr: KotlinExpression,
scope: KotlinScope,
nativeBacked: NativeBacked
): KotlinExpression
abstract val bridgedType: BridgedType
open fun cFromBridged(
expr: NativeExpression,
scope: NativeScope,
nativeBacked: NativeBacked
): NativeExpression = expr
open fun cToBridged(expr: NativeExpression): NativeExpression = expr
/**
* If this info is for [TypeMirror.ByValue], then this method describes how to
* construct pointed-type from value type.
*/
abstract fun constructPointedType(valueType: KotlinType): KotlinClassifierType
class Primitive(override val bridgedType: BridgedType, val varClass: Classifier) : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = expr
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) = expr
override fun constructPointedType(valueType: KotlinType) = varClass.typeWith(valueType)
}
class Boolean : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = "$expr.toByte()"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
"$expr.toBoolean()"
override val bridgedType: BridgedType get() = BridgedType.BYTE
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
"($expr) ? 1 : 0"
override fun cToBridged(expr: NativeExpression) = "($expr) ? 1 : 0"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.booleanVarOf.typeWith(valueType)
}
class Enum(val clazz: Classifier, override val bridgedType: BridgedType) : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = "$expr.value"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
scope.reference(clazz) + ".byValue($expr)"
override fun constructPointedType(valueType: KotlinType) =
clazz.nested("Var").type // TODO: improve
}
class Pointer(val pointee: KotlinType, val cPointee: Type) : TypeInfo() {
override fun argToBridged(expr: String) = "$expr.rawValue"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
"interpretCPointer<${pointee.render(scope)}>($expr)"
override val bridgedType: BridgedType
get() = BridgedType.NATIVE_PTR
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
"(${getPointerTypeStringRepresentation(cPointee)})$expr"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.cPointerVarOf.typeWith(valueType)
}
class ObjCPointerInfo(val kotlinType: KotlinType, val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(expr: String) = "$expr.objcPtr()"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
"interpretObjCPointerOrNull<${kotlinType.render(scope)}>($expr)" +
if (type.isNullable) "" else "!!"
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.objCObjectVar.typeWith(valueType)
}
class ObjCBlockPointerInfo(val kotlinType: KotlinFunctionType, val type: ObjCBlockPointer) : TypeInfo() {
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
// When passing Kotlin function as block pointer from Kotlin to native,
// it first gets wrapped by a holder in [argToBridged],
// and then converted to block in [cFromBridged].
override fun argToBridged(expr: KotlinExpression): KotlinExpression = "createKotlinObjectHolder($expr)"
override fun cFromBridged(
expr: NativeExpression,
scope: NativeScope,
nativeBacked: NativeBacked
): NativeExpression {
val mappingBridgeGenerator = scope.mappingBridgeGenerator
val blockParameters = type.parameterTypes.mapIndexed { index, it ->
"p$index" to it.getStringRepresentation()
}.joinToString { "${it.second} ${it.first}" }
val blockReturnType = type.returnType.getStringRepresentation()
val kniFunction = "kniFunction"
val codeBuilder = NativeCodeBuilder(scope)
return buildString {
append("({ ") // Statement expression begins.
append("id $kniFunction = $expr; ") // Note: it gets captured below.
append("($kniFunction == nil) ? nil : ")
append("(id)") // Cast the block to `id`.
append("^$blockReturnType($blockParameters) {") // Block begins.
// As block body, generate the code which simply bridges to Kotlin and calls the Kotlin function:
mappingBridgeGenerator.nativeToKotlin(
codeBuilder,
nativeBacked,
type.returnType,
type.parameterTypes.mapIndexed { index, it ->
TypedNativeValue(it, "p$index")
} + TypedNativeValue(ObjCIdType(ObjCPointer.Nullability.Nullable, emptyList()), kniFunction)
) { kotlinValues ->
val kotlinFunctionType = kotlinType.render(this.scope)
val kotlinFunction = "unwrapKotlinObjectHolder<$kotlinFunctionType>(${kotlinValues.last()})"
"$kotlinFunction(${kotlinValues.dropLast(1).joinToString()})"
}.let {
codeBuilder.out("return $it;")
}
codeBuilder.lines.joinTo(this, separator = " ")
append(" };") // Block ends.
append(" })") // Statement expression ends.
}
}
// When passing block pointer as Kotlin function from native to Kotlin,
// it is converted to Kotlin function in [cFromBridged].
override fun cToBridged(expr: NativeExpression): NativeExpression = expr
override fun argFromBridged(
expr: KotlinExpression,
scope: KotlinScope,
nativeBacked: NativeBacked
): KotlinExpression {
val mappingBridgeGenerator = scope.mappingBridgeGenerator
val funParameters = type.parameterTypes.mapIndexed { index, _ ->
"p$index" to kotlinType.parameterTypes[index]
}.joinToString { "${it.first}: ${it.second.render(scope)}" }
val funReturnType = kotlinType.returnType.render(scope)
val codeBuilder = KotlinCodeBuilder(scope)
val kniBlockPtr = "kniBlockPtr"
// Build the anonymous function expression:
val anonymousFun = buildString {
append("fun($funParameters): $funReturnType {\n") // Anonymous function begins.
// As function body, generate the code which simply bridges to native and calls the block:
mappingBridgeGenerator.kotlinToNative(
codeBuilder,
nativeBacked,
type.returnType,
type.parameterTypes.mapIndexed { index, it ->
TypedKotlinValue(it, "p$index")
} + TypedKotlinValue(PointerType(VoidType), "interpretCPointer<COpaque>($kniBlockPtr)"),
independent = true
) { nativeValues ->
val type = type
val blockType = blockTypeStringRepresentation(type)
val objCBlock = "((__bridge $blockType)${nativeValues.last()})"
"$objCBlock(${nativeValues.dropLast(1).joinToString()})"
}.let {
codeBuilder.returnResult(it)
}
codeBuilder.build().joinTo(this, separator = "\n")
append("}") // Anonymous function ends.
}
val nullOutput = if (type.isNullable) "null" else "throw NullPointerException()"
return "$expr.let { $kniBlockPtr -> if (kniBlockPtr == nativeNullPtr) $nullOutput else $anonymousFun }"
}
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType {
return Classifier.topLevel("kotlinx.cinterop", "ObjCBlockVar").typeWith(valueType)
}
}
class ByRef(val pointed: KotlinType) : TypeInfo() {
override fun argToBridged(expr: String) = error(pointed)
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
error(pointed)
override val bridgedType: BridgedType get() = error(pointed)
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
error(pointed)
override fun cToBridged(expr: String) = error(pointed)
// TODO: this method must not exist.
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType = error(pointed)
}
}
fun mirrorPrimitiveType(type: PrimitiveType, declarationMapper: DeclarationMapper): TypeMirror.ByValue {
val varClassName = when (type) {
is CharType -> "ByteVar"
is BoolType -> "BooleanVar"
is IntegerType -> if (declarationMapper.isMappedToSigned(type)) {
when (type.size) {
1 -> "ByteVar"
2 -> "ShortVar"
4 -> "IntVar"
8 -> "LongVar"
else -> TODO(type.toString())
}
} else {
when (type.size) {
1 -> "UByteVar"
2 -> "UShortVar"
4 -> "UIntVar"
8 -> "ULongVar"
else -> TODO(type.toString())
}
}
is FloatingType -> when (type.size) {
4 -> "FloatVar"
8 -> "DoubleVar"
else -> TODO(type.toString())
}
is VectorType -> {
"Vector128Var"
}
else -> TODO(type.toString())
}
val varClass = Classifier.topLevel("kotlinx.cinterop", varClassName)
val varClassOf = Classifier.topLevel("kotlinx.cinterop", "${varClassName}Of")
val info = if (type is BoolType) {
TypeInfo.Boolean()
} else {
TypeInfo.Primitive(type.getBridgedType(declarationMapper), varClassOf)
}
return TypeMirror.ByValue(varClass.type, info, type.getKotlinType(declarationMapper))
}
private fun byRefTypeMirror(pointedType: KotlinClassifierType) : TypeMirror.ByRef {
val info = TypeInfo.ByRef(pointedType)
return TypeMirror.ByRef(pointedType, info)
}
fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when (type) {
is PrimitiveType -> mirrorPrimitiveType(type, declarationMapper)
is RecordType -> byRefTypeMirror(declarationMapper.getKotlinClassForPointed(type.decl).type)
is EnumType -> {
val pkg = declarationMapper.getPackageFor(type.def)
val kotlinName = declarationMapper.getKotlinNameForValue(type.def)
.let { mangleSimple(it) } // enum class requires additional mangling
when {
declarationMapper.isMappedToStrict(type.def) -> {
val bridgedType = (type.def.baseType.unwrapTypedefs() as PrimitiveType).getBridgedType(declarationMapper)
val clazz = Classifier.topLevel(pkg, kotlinName)
val info = TypeInfo.Enum(clazz, bridgedType)
TypeMirror.ByValue(clazz.nested("Var").type, info, clazz.type)
}
!type.def.isAnonymous -> {
val baseTypeMirror = mirror(declarationMapper, type.def.baseType)
TypeMirror.ByValue(
Classifier.topLevel(pkg, kotlinName + "Var").typeAbbreviation(baseTypeMirror.pointedType),
baseTypeMirror.info,
Classifier.topLevel(pkg, kotlinName).typeAbbreviation(baseTypeMirror.argType)
)
}
else -> mirror(declarationMapper, type.def.baseType)
}
}
is PointerType -> {
val pointeeType = type.pointeeType
val unwrappedPointeeType = pointeeType.unwrapTypedefs()
if (unwrappedPointeeType is VoidType) {
val info = TypeInfo.Pointer(KotlinTypes.cOpaque, pointeeType)
TypeMirror.ByValue(KotlinTypes.cOpaquePointerVar, info, KotlinTypes.cOpaquePointer)
} else if (unwrappedPointeeType is ArrayType) {
mirror(declarationMapper, pointeeType)
} else {
val pointeeMirror = mirror(declarationMapper, pointeeType)
val info = TypeInfo.Pointer(pointeeMirror.pointedType, pointeeType)
TypeMirror.ByValue(
KotlinTypes.cPointerVar.typeWith(pointeeMirror.pointedType),
info,
KotlinTypes.cPointer.typeWith(pointeeMirror.pointedType)
)
}
}
is ArrayType -> {
// TODO: array type doesn't exactly correspond neither to pointer nor to value.
val elemTypeMirror = mirror(declarationMapper, type.elemType)
if (type.elemType.unwrapTypedefs() is ArrayType) {
elemTypeMirror
} else {
val info = TypeInfo.Pointer(elemTypeMirror.pointedType, type.elemType)
TypeMirror.ByValue(
KotlinTypes.cArrayPointerVar.typeWith(elemTypeMirror.pointedType),
info,
KotlinTypes.cArrayPointer.typeWith(elemTypeMirror.pointedType)
)
}
}
is FunctionType -> byRefTypeMirror(KotlinTypes.cFunction.typeWith(getKotlinFunctionType(declarationMapper, type)))
is Typedef -> {
val baseType = mirror(declarationMapper, type.def.aliased)
val pkg = declarationMapper.getPackageFor(type.def)
val name = type.def.name
when (baseType) {
is TypeMirror.ByValue -> {
val valueType = Classifier.topLevel(pkg, name).typeAbbreviation(baseType.valueType)
val underlyingPointedType = if (baseType.info is TypeInfo.Pointer) {
KotlinTypes.cPointerVarOf.typeWith(valueType)
} else {
baseType.pointedType
}
val pointedType = Classifier.topLevel(pkg, "${name}Var").typeAbbreviation(underlyingPointedType)
TypeMirror.ByValue(
pointedType,
baseType.info,
valueType,
nullable = baseType.nullable)
}
is TypeMirror.ByRef -> TypeMirror.ByRef(
Classifier.topLevel(pkg, name).typeAbbreviation(baseType.pointedType),
baseType.info
)
}
}
is ObjCPointer -> objCPointerMirror(declarationMapper, type)
else -> TODO(type.toString())
}
internal tailrec fun ObjCClass.isNSStringOrSubclass(): Boolean = when (this.name) {
"NSMutableString", // fast path and handling for forward declarations.
"NSString" -> true
else -> {
val baseClass = this.baseClass
if (baseClass != null) {
baseClass.isNSStringOrSubclass()
} else {
false
}
}
}
internal fun ObjCClass.isNSStringSubclass(): Boolean = this.baseClass?.isNSStringOrSubclass() == true
private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue {
if (type is ObjCObjectPointer && type.def.isNSStringOrSubclass()) {
val valueType = KotlinTypes.string
return objCMirror(valueType, TypeInfo.ObjCPointerInfo(valueType, type), type.isNullable)
}
val valueType = when (type) {
is ObjCIdType -> {
type.protocols.firstOrNull()?.let { declarationMapper.getKotlinClassFor(it) }?.type
?: KotlinTypes.any
}
is ObjCClassPointer -> KotlinTypes.objCClass.type
is ObjCObjectPointer -> {
when (type.def.name) {
"NSArray" -> KotlinTypes.list.typeWith(StarProjection)
"NSMutableArray" -> KotlinTypes.mutableList.typeWith(KotlinTypes.any.makeNullable())
"NSSet" -> KotlinTypes.set.typeWith(StarProjection)
"NSDictionary" -> KotlinTypes.map.typeWith(KotlinTypes.any.makeNullable(), StarProjection)
else -> declarationMapper.getKotlinClassFor(type.def).type
}
}
is ObjCInstanceType -> TODO(type.toString()) // Must have already been handled.
is ObjCBlockPointer -> return objCBlockPointerMirror(declarationMapper, type)
}
return objCMirror(valueType, TypeInfo.ObjCPointerInfo(valueType, type), type.isNullable)
}
private fun objCBlockPointerMirror(declarationMapper: DeclarationMapper, type: ObjCBlockPointer): TypeMirror.ByValue {
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit
} else {
mirror(declarationMapper, type.returnType).argType
}
val kotlinType = KotlinFunctionType(
type.parameterTypes.map { mirror(declarationMapper, it).argType },
returnType
)
val info = TypeInfo.ObjCBlockPointerInfo(kotlinType, type)
return objCMirror(kotlinType, info, type.isNullable)
}
private fun objCMirror(valueType: KotlinType, info: TypeInfo, nullable: Boolean) = TypeMirror.ByValue(
info.constructPointedType(valueType.makeNullableAsSpecified(nullable)),
info,
valueType.makeNullable(), // All typedefs to Objective-C pointers would be nullable for simplicity
nullable
)
fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): KotlinFunctionType {
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit
} else {
mirror(declarationMapper, type.returnType).argType
}
return KotlinFunctionType(
type.parameterTypes.map { mirror(declarationMapper, it).argType },
returnType,
nullable = false
)
}
@@ -0,0 +1,602 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
internal fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
val selectorParts = this.selector.split(":")
val result = mutableListOf<String>()
fun String.mangled(): String {
var mangled = this
while (mangled in result) {
mangled = "_$mangled"
}
return mangled
}
// The names of all parameters except first must depend only on the selector:
this.parameters.forEachIndexed { index, _ ->
if (index > 0) {
val name = selectorParts[index].takeIf { it.isNotEmpty() } ?: "_$index"
result.add(name.mangled())
}
}
this.parameters.firstOrNull()?.let {
val name = this.getFirstKotlinParameterNameCandidate(forConstructorOrFactory)
result.add(0, name.mangled())
}
if (this.isVariadic) {
result.add("args".mangled())
}
return result
}
private fun ObjCMethod.getFirstKotlinParameterNameCandidate(forConstructorOrFactory: Boolean): String {
if (forConstructorOrFactory) {
val selectorPart = this.selector.takeWhile { it != ':' }.trimStart('_')
if (selectorPart.startsWith("init")) {
selectorPart.removePrefix("init").removePrefix("With")
.takeIf { it.isNotEmpty() }?.let { return it.decapitalize() }
}
}
return this.parameters.first().name?.takeIf { it.isNotEmpty() } ?: "_0"
}
private fun ObjCMethod.getKotlinParameters(
stubIrBuilder: StubsBuildingContext,
forConstructorOrFactory: Boolean
): List<FunctionParameterStub> {
if (this.isInit && this.parameters.isEmpty() && this.selector != "init") {
// Create synthetic Unit parameter, just like Swift does in this case:
val parameterName = this.selector.removePrefix("init").removePrefix("With").decapitalize()
return listOf(FunctionParameterStub(parameterName, KotlinTypes.unit.toStubIrType()))
// Note: this parameter is explicitly handled in compiler.
}
val names = getKotlinParameterNames(forConstructorOrFactory) // TODO: consider refactoring.
val result = mutableListOf<FunctionParameterStub>()
this.parameters.mapIndexedTo(result) { index, it ->
val kotlinType = stubIrBuilder.mirror(it.type).argType
val name = names[index]
val annotations = if (it.nsConsumed) listOf(AnnotationStub.ObjC.Consumed) else emptyList()
FunctionParameterStub(name, kotlinType.toStubIrType(), isVararg = false, annotations = annotations)
}
if (this.isVariadic) {
result += FunctionParameterStub(
names.last(),
KotlinTypes.any.makeNullable().toStubIrType(),
isVararg = true,
annotations = emptyList()
)
}
return result
}
private class ObjCMethodStubBuilder(
private val method: ObjCMethod,
private val container: ObjCContainer,
private val isDesignatedInitializer: Boolean,
override val context: StubsBuildingContext
) : StubElementBuilder {
private val isStret: Boolean
private val stubReturnType: StubType
val annotations = mutableListOf<AnnotationStub>()
private val kotlinMethodParameters: List<FunctionParameterStub>
private val external: Boolean
private val receiver: ReceiverParameterStub?
private val name: String = method.kotlinName
private val origin = StubOrigin.ObjCMethod(method, container)
private val modality: MemberStubModality
private val isOverride: Boolean =
container is ObjCClassOrProtocol && method.isOverride(container)
init {
val returnType = method.getReturnType(container.classOrProtocol)
isStret = returnType.isStret(context.configuration.target)
stubReturnType = if (returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit
} else {
context.mirror(returnType).argType
}.toStubIrType()
val methodAnnotation = AnnotationStub.ObjC.Method(
method.selector,
method.encoding,
isStret
)
annotations += buildObjCMethodAnnotations(methodAnnotation)
kotlinMethodParameters = method.getKotlinParameters(context, forConstructorOrFactory = false)
external = (container !is ObjCProtocol)
modality = when (container) {
is ObjCClass -> MemberStubModality.OPEN
is ObjCProtocol -> if (method.isOptional) MemberStubModality.OPEN else MemberStubModality.ABSTRACT
is ObjCCategory -> MemberStubModality.FINAL
}
receiver = if (container is ObjCCategory) {
val receiverType = ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = method.isClass))
ReceiverParameterStub(receiverType)
} else null
}
private fun buildObjCMethodAnnotations(main: AnnotationStub): List<AnnotationStub> = listOfNotNull(
main,
AnnotationStub.ObjC.ConsumesReceiver.takeIf { method.nsConsumesSelf },
AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained }
)
fun isDefaultConstructor(): Boolean =
method.isInit && method.parameters.isEmpty()
override fun build(): List<FunctionalStub> {
val replacement = if (method.isInit) {
val parameters = method.getKotlinParameters(context, forConstructorOrFactory = true)
when (container) {
is ObjCClass -> {
annotations.add(0, deprecatedInit(
container.kotlinClassName(method.isClass),
kotlinMethodParameters.map { it.name },
factory = false
))
val designated = isDesignatedInitializer ||
context.configuration.disableDesignatedInitializerChecks
val annotations = listOf(AnnotationStub.ObjC.Constructor(method.selector, designated))
val constructor = ConstructorStub(parameters, annotations, isPrimary = false, origin = origin)
constructor
}
is ObjCCategory -> {
assert(!method.isClass)
val clazz = context.getKotlinClassFor(container.clazz, isMeta = false).type
annotations.add(0, deprecatedInit(
clazz.classifier.getRelativeFqName(),
kotlinMethodParameters.map { it.name },
factory = true
))
val factoryAnnotation = AnnotationStub.ObjC.Factory(
method.selector,
method.encoding,
isStret
)
val annotations = buildObjCMethodAnnotations(factoryAnnotation)
val originalReturnType = method.getReturnType(container.clazz)
val typeParameter = TypeParameterStub("T", clazz.toStubIrType())
val returnType = if (originalReturnType is ObjCPointer) {
typeParameter.getStubType(originalReturnType.isNullable)
} else {
// This shouldn't happen actually.
this.stubReturnType
}
val typeArgument = TypeArgumentStub(typeParameter.getStubType(false))
val receiverType = ClassifierStubType(KotlinTypes.objCClassOf, listOf(typeArgument))
val receiver = ReceiverParameterStub(receiverType)
val createMethod = FunctionStub(
"create",
returnType,
parameters,
receiver = receiver,
typeParameters = listOf(typeParameter),
external = true,
origin = StubOrigin.ObjCCategoryInitMethod(method),
annotations = annotations,
modality = MemberStubModality.FINAL
)
createMethod
}
is ObjCProtocol -> null
}
} else {
null
}
return listOfNotNull(
FunctionStub(
name,
stubReturnType,
kotlinMethodParameters.toList(),
origin,
annotations.toList(),
external,
receiver,
modality,
emptyList(),
isOverride),
replacement
)
}
}
internal val ObjCContainer.classOrProtocol: ObjCClassOrProtocol
get() = when (this) {
is ObjCClassOrProtocol -> this
is ObjCCategory -> this.clazz
}
private fun deprecatedInit(className: String, initParameterNames: List<String>, factory: Boolean): AnnotationStub {
val replacement = if (factory) "$className.create" else className
val replacementKind = if (factory) "factory method" else "constructor"
val replaceWith = "$replacement(${initParameterNames.joinToString { it.asSimpleName() }})"
return AnnotationStub.Deprecated("Use $replacementKind instead", replaceWith, DeprecationLevel.ERROR)
}
internal val ObjCMethod.kotlinName: String
get() {
val candidate = selector.split(":").first()
val trimmed = candidate.trimEnd('_')
return if (trimmed == "equals" && parameters.size == 1
|| (trimmed == "hashCode" || trimmed == "toString") && parameters.size == 0) {
candidate + "_"
} else {
candidate
}
}
internal val ObjCClassOrProtocol.protocolsWithSupers: Sequence<ObjCProtocol>
get() = this.protocols.asSequence().flatMap { sequenceOf(it) + it.protocolsWithSupers }
internal val ObjCClassOrProtocol.immediateSuperTypes: Sequence<ObjCClassOrProtocol>
get() {
val baseClass = (this as? ObjCClass)?.baseClass
if (baseClass != null) {
return sequenceOf(baseClass) + this.protocols.asSequence()
}
return this.protocols.asSequence()
}
internal val ObjCClassOrProtocol.selfAndSuperTypes: Sequence<ObjCClassOrProtocol>
get() = sequenceOf(this) + this.superTypes
internal val ObjCClassOrProtocol.superTypes: Sequence<ObjCClassOrProtocol>
get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct()
internal fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence<ObjCMethod> =
this.methods.asSequence().filter { it.isClass == isClass }
@Suppress("UNUSED_PARAMETER")
internal fun Sequence<ObjCMethod>.inheritedTo(container: ObjCClassOrProtocol, isMeta: Boolean): Sequence<ObjCMethod> =
this // TODO: exclude methods that are marked as unavailable in [container].
internal fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence<ObjCMethod> =
this.immediateSuperTypes.flatMap { it.methodsWithInherited(isClass) }
.distinctBy { it.selector }
.inheritedTo(this, isClass)
internal fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence<ObjCMethod> =
(this.declaredMethods(isClass) + this.inheritedMethods(isClass)).distinctBy { it.selector }
internal fun ObjCClass.getDesignatedInitializerSelectors(result: MutableSet<String>): Set<String> {
// Note: Objective-C initializers act as usual methods and thus are inherited by subclasses.
// Swift considers all super initializers to be available (unless otherwise specified explicitly),
// but seems to consider them as non-designated if class declares its own ones explicitly.
// Simulate the similar behaviour:
val explicitlyDesignatedInitializers = this.methods.filter { it.isExplicitlyDesignatedInitializer && !it.isClass }
if (explicitlyDesignatedInitializers.isNotEmpty()) {
explicitlyDesignatedInitializers.mapTo(result) { it.selector }
} else {
this.declaredMethods(isClass = false).filter { it.isInit }.mapTo(result) { it.selector }
this.baseClass?.getDesignatedInitializerSelectors(result)
}
this.superTypes.filterIsInstance<ObjCProtocol>()
.flatMap { it.declaredMethods(isClass = false) }.filter { it.isInit }
.mapTo(result) { it.selector }
return result
}
internal fun ObjCMethod.isOverride(container: ObjCClassOrProtocol): Boolean =
container.superTypes.any { superType -> superType.methods.any(this::replaces) }
internal abstract class ObjCContainerStubBuilder(
final override val context: StubsBuildingContext,
private val container: ObjCClassOrProtocol,
protected val metaContainerStub: ObjCContainerStubBuilder?
) : StubElementBuilder {
private val isMeta: Boolean get() = metaContainerStub == null
private val designatedInitializerSelectors = if (container is ObjCClass && !isMeta) {
container.getDesignatedInitializerSelectors(mutableSetOf())
} else {
emptySet()
}
private val methods: List<ObjCMethod>
private val properties: List<ObjCProperty>
private val protocolGetter: String?
init {
val superMethods = container.inheritedMethods(isMeta)
// Add all methods declared in the class or protocol:
var methods = container.declaredMethods(isMeta)
// Exclude those which are identically declared in super types:
methods -= superMethods
// Add some special methods from super types:
methods += superMethods.filter { it.returnsInstancetype() || it.isInit }
// Add methods from adopted protocols that must be implemented according to Kotlin rules:
if (container is ObjCClass) {
methods += container.protocolsWithSupers.flatMap { it.declaredMethods(isMeta) }.filter { !it.isOptional }
}
// Add methods inherited from multiple supertypes that must be defined according to Kotlin rules:
methods += container.immediateSuperTypes
.flatMap { superType ->
val methodsWithInherited = superType.methodsWithInherited(isMeta).inheritedTo(container, isMeta)
// Select only those which are represented as non-abstract in Kotlin:
when (superType) {
is ObjCClass -> methodsWithInherited
is ObjCProtocol -> methodsWithInherited.filter { it.isOptional }
}
}
.groupBy { it.selector }
.mapNotNull { (_, inheritedMethods) -> if (inheritedMethods.size > 1) inheritedMethods.first() else null }
this.methods = methods.distinctBy { it.selector }.toList()
this.properties = container.properties.filter { property ->
property.getter.isClass == isMeta &&
// Select only properties that don't override anything:
superMethods.none { property.getter.replaces(it) || property.setter?.replaces(it) ?: false }
}
}
private val methodToStub = methods.map {
it to ObjCMethodStubBuilder(it, container, it.selector in designatedInitializerSelectors, context)
}.toMap()
private val propertyBuilders = properties.mapNotNull {
createObjCPropertyBuilder(context, it, container, this.methodToStub)
}
private val modality = when (container) {
is ObjCClass -> ClassStubModality.OPEN
is ObjCProtocol -> ClassStubModality.INTERFACE
}
private val classifier = context.getKotlinClassFor(container, isMeta)
private val externalObjCAnnotation = when (container) {
is ObjCProtocol -> {
protocolGetter = if (metaContainerStub != null) {
metaContainerStub.protocolGetter!!
} else {
// TODO: handle the case when protocol getter stub can't be compiled.
context.generateNextUniqueId("kniprot_")
}
AnnotationStub.ObjC.ExternalClass(protocolGetter)
}
is ObjCClass -> {
protocolGetter = null
val binaryName = container.binaryName
AnnotationStub.ObjC.ExternalClass("", binaryName ?: "")
}
}
private val interfaces: List<StubType> by lazy {
val interfaces = mutableListOf<StubType>()
if (container is ObjCClass) {
val baseClass = container.baseClass
val baseClassifier = if (baseClass != null) {
context.getKotlinClassFor(baseClass, isMeta)
} else {
if (isMeta) KotlinTypes.objCObjectBaseMeta else KotlinTypes.objCObjectBase
}
interfaces += baseClassifier.type.toStubIrType()
}
container.protocols.forEach {
interfaces += context.getKotlinClassFor(it, isMeta).type.toStubIrType()
}
if (interfaces.isEmpty()) {
assert(container is ObjCProtocol)
val classifier = if (isMeta) KotlinTypes.objCObjectMeta else KotlinTypes.objCObject
interfaces += classifier.type.toStubIrType()
}
if (!isMeta && container.isProtocolClass()) {
// TODO: map Protocol type to ObjCProtocol instead.
interfaces += KotlinTypes.objCProtocol.type.toStubIrType()
}
interfaces
}
private fun buildBody(): Pair<List<PropertyStub>, List<FunctionalStub>> {
val defaultConstructor = if (container is ObjCClass && methodToStub.values.none { it.isDefaultConstructor() }) {
// Always generate default constructor.
// If it is not produced for an init method, then include it manually:
ConstructorStub(
isPrimary = false,
visibility = VisibilityModifier.PROTECTED,
origin = StubOrigin.Synthetic.DefaultConstructor)
} else null
return Pair(
propertyBuilders.flatMap { it.build() },
methodToStub.values.flatMap { it.build() } + listOfNotNull(defaultConstructor)
)
}
protected fun buildClassStub(origin: StubOrigin, companion: ClassStub.Companion? = null): ClassStub {
val (properties, methods) = buildBody()
return ClassStub.Simple(
classifier,
properties = properties,
methods = methods.filterIsInstance<FunctionStub>(),
constructors = methods.filterIsInstance<ConstructorStub>(),
origin = origin,
modality = modality,
annotations = listOf(externalObjCAnnotation),
interfaces = interfaces,
companion = companion
)
}
}
internal sealed class ObjCClassOrProtocolStubBuilder(
context: StubsBuildingContext,
private val container: ObjCClassOrProtocol
) : ObjCContainerStubBuilder(
context,
container,
metaContainerStub = object : ObjCContainerStubBuilder(context, container, metaContainerStub = null) {
override fun build(): List<StubIrElement> {
val origin = when (container) {
is ObjCProtocol -> StubOrigin.ObjCProtocol(container, isMeta = true)
is ObjCClass -> StubOrigin.ObjCClass(container, isMeta = true)
}
return listOf(buildClassStub(origin))
}
}
)
internal class ObjCProtocolStubBuilder(
context: StubsBuildingContext,
private val protocol: ObjCProtocol
) : ObjCClassOrProtocolStubBuilder(context, protocol), StubElementBuilder {
override fun build(): List<StubIrElement> {
val classStub = buildClassStub(StubOrigin.ObjCProtocol(protocol, isMeta = false))
return listOf(*metaContainerStub!!.build().toTypedArray(), classStub)
}
}
internal class ObjCClassStubBuilder(
context: StubsBuildingContext,
private val clazz: ObjCClass
) : ObjCClassOrProtocolStubBuilder(context, clazz), StubElementBuilder {
override fun build(): List<StubIrElement> {
val companionSuper = ClassifierStubType(context.getKotlinClassFor(clazz, isMeta = true))
val objCClassType = KotlinTypes.objCClassOf.typeWith(
context.getKotlinClassFor(clazz, isMeta = false).type
).toStubIrType()
val superClassInit = SuperClassInit(companionSuper)
val companionClassifier = context.getKotlinClassFor(clazz, isMeta = false).nested("Companion")
val companion = ClassStub.Companion(companionClassifier, emptyList(), superClassInit, listOf(objCClassType))
val classStub = buildClassStub(StubOrigin.ObjCClass(clazz, isMeta = false), companion)
return listOf(*metaContainerStub!!.build().toTypedArray(), classStub)
}
}
class GeneratedObjCCategoriesMembers {
private val propertyNames = mutableSetOf<String>()
private val instanceMethodSelectors = mutableSetOf<String>()
private val classMethodSelectors = mutableSetOf<String>()
fun register(method: ObjCMethod): Boolean =
(if (method.isClass) classMethodSelectors else instanceMethodSelectors).add(method.selector)
fun register(property: ObjCProperty): Boolean = propertyNames.add(property.name)
}
internal class ObjCCategoryStubBuilder(
override val context: StubsBuildingContext,
private val category: ObjCCategory
) : StubElementBuilder {
private val generatedMembers = context.generatedObjCCategoriesMembers
.getOrPut(category.clazz, { GeneratedObjCCategoriesMembers() })
private val methodToBuilder = category.methods.filter { generatedMembers.register(it) }.map {
it to ObjCMethodStubBuilder(it, category, isDesignatedInitializer = false, context = context)
}.toMap()
private val methodBuilders get() = methodToBuilder.values
private val propertyBuilders = category.properties.filter { generatedMembers.register(it) }.mapNotNull {
createObjCPropertyBuilder(context, it, category, methodToBuilder)
}
override fun build(): List<StubIrElement> {
val description = "${category.clazz.name} (${category.name})"
val meta = StubContainerMeta(
"// @interface $description",
"// @end; // $description"
)
val container = SimpleStubContainer(
meta = meta,
functions = methodBuilders.flatMap { it.build() },
properties = propertyBuilders.flatMap { it.build() }
)
return listOf(container)
}
}
private fun createObjCPropertyBuilder(
context: StubsBuildingContext,
property: ObjCProperty,
container: ObjCContainer,
methodToStub: Map<ObjCMethod, ObjCMethodStubBuilder>
): ObjCPropertyStubBuilder? {
// Note: the code below assumes that if the property is generated,
// then its accessors are also generated as explicit methods.
val getterStub = methodToStub[property.getter] ?: return null
val setterStub = property.setter?.let { methodToStub[it] ?: return null }
return ObjCPropertyStubBuilder(context, property, container, getterStub, setterStub)
}
private class ObjCPropertyStubBuilder(
override val context: StubsBuildingContext,
private val property: ObjCProperty,
private val container: ObjCContainer,
private val getterBuilder: ObjCMethodStubBuilder,
private val setterMethod: ObjCMethodStubBuilder?
) : StubElementBuilder {
override fun build(): List<PropertyStub> {
val type = property.getType(container.classOrProtocol)
val kotlinType = context.mirror(type).argType
val getter = PropertyAccessor.Getter.ExternalGetter(annotations = getterBuilder.annotations)
val setter = property.setter?.let { PropertyAccessor.Setter.ExternalSetter(annotations = setterMethod!!.annotations) }
val kind = setter?.let { PropertyStub.Kind.Var(getter, it) } ?: PropertyStub.Kind.Val(getter)
val modality = MemberStubModality.FINAL
val receiver = when (container) {
is ObjCClassOrProtocol -> null
is ObjCCategory -> ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = property.getter.isClass))
}
val origin = StubOrigin.ObjCProperty(property, container)
return listOf(PropertyStub(mangleSimple(property.name), kotlinType.toStubIrType(), kind, modality, receiver, origin = origin))
}
}
fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String {
val baseClassName = when (this) {
is ObjCClass -> this.name
is ObjCProtocol -> "${this.name}Protocol"
}
return if (isMeta) "${baseClassName}Meta" else baseClassName
}
internal fun ObjCClassOrProtocol.isProtocolClass(): Boolean = when (this) {
is ObjCClass -> (name == "Protocol" || binaryName == "Protocol")
is ObjCProtocol -> false
}
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
/**
* The type which has exact counterparts on both Kotlin and native side and can be directly passed through bridges.
*/
enum class BridgedType(val kotlinType: KotlinClassifierType, val convertor: String? = null) {
BYTE(KotlinTypes.byte, "toByte"),
SHORT(KotlinTypes.short, "toShort"),
INT(KotlinTypes.int, "toInt"),
LONG(KotlinTypes.long, "toLong"),
UBYTE(KotlinTypes.uByte, "toUByte"),
USHORT(KotlinTypes.uShort, "toUShort"),
UINT(KotlinTypes.uInt, "toUInt"),
ULONG(KotlinTypes.uLong, "toULong"),
FLOAT(KotlinTypes.float, "toFloat"),
DOUBLE(KotlinTypes.double, "toDouble"),
VECTOR128(KotlinTypes.vector128),
NATIVE_PTR(KotlinTypes.nativePtr),
OBJC_POINTER(KotlinTypes.nativePtr),
VOID(KotlinTypes.unit)
}
data class BridgeTypedKotlinValue(val type: BridgedType, val value: KotlinExpression)
data class BridgeTypedNativeValue(val type: BridgedType, val value: NativeExpression)
/**
* The entity which depends on native bridges.
*/
interface NativeBacked
/**
* Generates simple bridges between Kotlin and native, passing [BridgedType] values.
*/
interface SimpleBridgeGenerator {
val topLevelNativeScope: NativeScope
/**
* Generates the expression to convert given Kotlin values to native counterparts, pass through the bridge,
* use inside the native code produced by [block] and then return the result back.
*
* @param block produces native code lines into the builder and returns the expression to be used as the result.
*/
fun kotlinToNative(
nativeBacked: NativeBacked,
returnType: BridgedType,
kotlinValues: List<BridgeTypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression
/**
* Generates the expression to convert given native values to Kotlin counterparts, pass through the bridge,
* use inside the Kotlin code produced by [block] and then return the result back.
*/
fun nativeToKotlin(
nativeBacked: NativeBacked,
returnType: BridgedType,
nativeValues: List<BridgeTypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression
fun insertNativeBridge(
nativeBacked: NativeBacked,
kotlinLines: List<String>,
nativeLines: List<String>
)
/**
* Prepares all requested native bridges.
*/
fun prepare(): NativeBridges
}
interface NativeBridges {
/**
* @return `true` iff given entity is supported by these bridges,
* i.e. all bridges it depends on can be successfully generated.
*/
fun isSupported(nativeBacked: NativeBacked): Boolean
val kotlinLines: Sequence<String>
val nativeLines: Sequence<String>
}
@@ -0,0 +1,253 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
import org.jetbrains.kotlin.native.interop.indexer.Language
import org.jetbrains.kotlin.native.interop.indexer.mapFragmentIsCompilable
internal val INVALID_CLANG_IDENTIFIER_REGEX = "[^a-zA-Z1-9_]".toRegex()
class SimpleBridgeGeneratorImpl(
private val platform: KotlinPlatform,
private val pkgName: String,
private val jvmFileClassName: String,
private val libraryForCStubs: CompilationWithPCH,
override val topLevelNativeScope: NativeScope,
private val topLevelKotlinScope: KotlinScope
) : SimpleBridgeGenerator {
private var nextUniqueId = 0
private val BridgedType.nativeType: String get() = when (platform) {
KotlinPlatform.JVM -> when (this) {
BridgedType.BYTE -> "jbyte"
BridgedType.SHORT -> "jshort"
BridgedType.INT -> "jint"
BridgedType.LONG -> "jlong"
BridgedType.UBYTE -> "jbyte"
BridgedType.USHORT -> "jshort"
BridgedType.UINT -> "jint"
BridgedType.ULONG -> "jlong"
BridgedType.FLOAT -> "jfloat"
BridgedType.DOUBLE -> "jdouble"
BridgedType.VECTOR128 -> TODO()
BridgedType.NATIVE_PTR -> "jlong"
BridgedType.OBJC_POINTER -> TODO()
BridgedType.VOID -> "void"
}
KotlinPlatform.NATIVE -> when (this) {
BridgedType.BYTE -> "int8_t"
BridgedType.SHORT -> "int16_t"
BridgedType.INT -> "int32_t"
BridgedType.LONG -> "int64_t"
BridgedType.UBYTE -> "uint8_t"
BridgedType.USHORT -> "uint16_t"
BridgedType.UINT -> "uint32_t"
BridgedType.ULONG -> "uint64_t"
BridgedType.FLOAT -> "float"
BridgedType.DOUBLE -> "double"
BridgedType.VECTOR128 -> TODO() // "float __attribute__ ((__vector_size__ (16)))"
BridgedType.NATIVE_PTR -> "void*"
BridgedType.OBJC_POINTER -> "id"
BridgedType.VOID -> "void"
}
}
private inner class NativeBridge(val kotlinLines: List<String>, val nativeLines: List<String>)
override fun kotlinToNative(
nativeBacked: NativeBacked,
returnType: BridgedType,
kotlinValues: List<BridgeTypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression {
val kotlinLines = mutableListOf<String>()
val nativeLines = mutableListOf<String>()
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = kotlinValues.withIndex().joinToString {
"p${it.index}: ${it.value.type.kotlinType.render(topLevelKotlinScope)}"
}
val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})"
val cFunctionParameters = when (platform) {
KotlinPlatform.JVM -> mutableListOf(
"jniEnv" to "JNIEnv*",
"jclss" to "jclass"
)
KotlinPlatform.NATIVE -> mutableListOf()
}
kotlinValues.withIndex().mapTo(cFunctionParameters) {
"p${it.index}" to it.value.type.nativeType
}
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
val cReturnType = returnType.nativeType
val cFunctionHeader = when (platform) {
KotlinPlatform.JVM -> {
val funcFullName = buildString {
if (pkgName.isNotEmpty()) {
append(pkgName)
append('.')
}
append(jvmFileClassName)
append('.')
append(kotlinFunctionName)
}
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
}
KotlinPlatform.NATIVE -> {
val functionName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
if (independent) kotlinLines.add("@" + topLevelKotlinScope.reference(KotlinTypes.independent))
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
"$cReturnType $functionName ($joinedCParameters)"
}
}
nativeLines.add(cFunctionHeader + " {")
buildNativeCodeLines(topLevelNativeScope) {
val cExpr = block(cFunctionParameters.takeLast(kotlinValues.size).map { (name, _) -> name })
if (returnType != BridgedType.VOID) {
out("return ($cReturnType)$cExpr;")
}
}.forEach {
nativeLines.add(" $it")
}
if (libraryForCStubs.language == Language.OBJECTIVE_C) {
// Prevent Objective-C exceptions from passing to Kotlin:
nativeLines.add(1, "@try {")
nativeLines.add("} @catch (id e) { objc_terminate(); }")
// 'objc_terminate' will report the exception.
// TODO: consider implementing this in bitcode generator.
}
nativeLines.add("}")
val kotlinReturnType = returnType.kotlinType.render(topLevelKotlinScope)
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): $kotlinReturnType")
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
return callExpr
}
override fun nativeToKotlin(
nativeBacked: NativeBacked,
returnType: BridgedType,
nativeValues: List<BridgeTypedNativeValue>,
block: KotlinCodeBuilder.(arguments: List<KotlinExpression>) -> KotlinExpression
): NativeExpression {
if (platform != KotlinPlatform.NATIVE) TODO()
val kotlinLines = mutableListOf<String>()
val nativeLines = mutableListOf<String>()
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.kotlinType
}
val joinedKotlinParameters = kotlinParameters.joinToString {
"${it.first}: ${it.second.render(topLevelKotlinScope)}"
}
val cFunctionParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.nativeType
}
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
val cReturnType = returnType.nativeType
val symbolName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
kotlinLines.add("@kotlin.native.internal.ExportForCppRuntime(${symbolName.quoteAsKotlinLiteral()})")
val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)"
nativeLines.add("$cFunctionHeader;")
val kotlinReturnType = returnType.kotlinType.render(topLevelKotlinScope)
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): $kotlinReturnType {")
buildKotlinCodeLines(topLevelKotlinScope) {
var kotlinExpr = block(kotlinParameters.map { (name, _) -> name })
if (returnType == BridgedType.OBJC_POINTER) {
// The Kotlin code may lose the ownership on this pointer after returning from the bridge,
// so retain the pointer and autorelease it:
kotlinExpr = "objc_retainAutoreleaseReturnValue($kotlinExpr)"
// (Objective-C does the same for returned pointers).
}
returnResult(kotlinExpr)
}.forEach {
kotlinLines.add(" $it")
}
kotlinLines.add("}")
insertNativeBridge(nativeBacked, kotlinLines, nativeLines)
return "$symbolName(${nativeValues.joinToString { it.value }})"
}
override fun insertNativeBridge(nativeBacked: NativeBacked, kotlinLines: List<String>, nativeLines: List<String>) {
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
}
private val nativeBridges = mutableListOf<Pair<NativeBacked, NativeBridge>>()
override fun prepare(): NativeBridges {
val includedBridges = mutableListOf<NativeBridge>()
val excludedClients = mutableSetOf<NativeBacked>()
nativeBridges.map { it.second.nativeLines }
.mapFragmentIsCompilable(libraryForCStubs)
.forEachIndexed { index, isCompilable ->
if (!isCompilable) {
excludedClients.add(nativeBridges[index].first)
}
}
nativeBridges.mapNotNullTo(includedBridges) { (nativeBacked, nativeBridge) ->
if (nativeBacked in excludedClients) {
null
} else {
nativeBridge
}
}
// TODO: exclude unused bridges.
return object : NativeBridges {
override val kotlinLines: Sequence<String>
get() = includedBridges.asSequence().flatMap { it.kotlinLines.asSequence() }
override val nativeLines: Sequence<String>
get() = includedBridges.asSequence().flatMap { it.nativeLines.asSequence() }
override fun isSupported(nativeBacked: NativeBacked): Boolean =
nativeBacked !in excludedClients
}
}
}
@@ -0,0 +1,83 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
StructDef.Kind.STRUCT -> tryRenderStruct(def)
StructDef.Kind.UNION -> tryRenderUnion(def)
}
private fun tryRenderStruct(def: StructDef): String? {
val isPackedStruct = def.fields.any { !it.isAligned }
var offset = 0L
return buildString {
append("struct")
if (isPackedStruct) append(" __attribute__((packed))")
append(" { ")
def.members.forEachIndexed { index, it ->
val name = "p$index"
val decl = when (it) {
is Field -> {
val defaultAlignment = if (isPackedStruct) 1L else it.typeAlign
val alignment = guessAlignment(offset, it.offsetBytes, defaultAlignment) ?: return null
offset = it.offsetBytes + it.typeSize
tryRenderVar(it.type, name)
?.plus(if (alignment == defaultAlignment) "" else "__attribute__((aligned($alignment)))")
}
is BitField, // TODO: tryRenderVar(it.type, name)?.plus(" : ${it.size}")
is IncompleteField -> null // e.g. flexible array member.
} ?: return null
append("$decl; ")
}
append("}")
}
}
private fun guessAlignment(offset: Long, paddedOffset: Long, defaultAlignment: Long): Long? =
longArrayOf(defaultAlignment, 1L, 2L, 4L, 8L, 16L, 32L).firstOrNull {
alignUp(offset, it) == paddedOffset
}
private fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and ((alignment - 1).inv())
private fun tryRenderUnion(def: StructDef): String? =
if (def.members.any { it.offset != 0L }) null else buildString {
append("union { ")
def.members.forEachIndexed { index, it ->
val decl = when (it) {
is Field -> tryRenderVar(it.type, "p$index")
is BitField, is IncompleteField -> null
} ?: return null
append("$decl; ")
}
append("}")
}
private fun tryRenderVar(type: Type, name: String): String? = when (type) {
CharType, is BoolType -> "char $name"
is IntegerType -> "${type.spelling} $name"
is FloatingType -> "${type.spelling} $name"
is VectorType -> "${type.spelling} $name"
is RecordType -> "${tryRenderStructOrUnion(type.decl.def!!)} $name"
is EnumType -> tryRenderVar(type.def.baseType, name)
is PointerType -> "void* $name"
is ConstArrayType -> tryRenderVar(type.elemType, "$name[${type.length}]")
is IncompleteArrayType -> tryRenderVar(type.elemType, "$name[]")
is Typedef -> tryRenderVar(type.def.aliased, name)
is ObjCPointer -> "void* $name"
else -> null
}
private val Field.offsetBytes: Long get() {
require(this.offset % 8 == 0L)
return this.offset / 8
}
@@ -0,0 +1,529 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
// TODO: Replace all usages of these strings with constants.
const val cinteropPackage = "kotlinx.cinterop"
const val cinteropInternalPackage = "$cinteropPackage.internal"
interface StubIrElement {
fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T): R
}
sealed class StubContainer : StubIrElement {
abstract val meta: StubContainerMeta
abstract val classes: List<ClassStub>
abstract val functions: List<FunctionalStub>
abstract val properties: List<PropertyStub>
abstract val typealiases: List<TypealiasStub>
abstract val simpleContainers: List<SimpleStubContainer>
}
/**
* Meta information about [StubContainer].
* For example, can be used for comments in textual representation.
*/
class StubContainerMeta(
val textAtStart: String = "",
val textAtEnd: String = ""
)
class SimpleStubContainer(
override val meta: StubContainerMeta = StubContainerMeta(),
override val classes: List<ClassStub> = emptyList(),
override val functions: List<FunctionalStub> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val typealiases: List<TypealiasStub> = emptyList(),
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : StubContainer() {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T): R {
return visitor.visitSimpleStubContainer(this, data)
}
}
val StubContainer.children: List<StubIrElement>
get() = (classes as List<StubIrElement>) + properties + functions + typealiases
/**
* Marks that abstract value of such type can be passed as value.
*/
sealed class ValueStub
class TypeParameterStub(
val name: String,
val upperBound: StubType? = null
) {
fun getStubType(nullable: Boolean) =
TypeParameterType(name, nullable = nullable, typeParameterDeclaration = this)
}
interface TypeArgument {
object StarProjection : TypeArgument {
override fun toString(): String =
"*"
}
enum class Variance {
INVARIANT,
IN,
OUT
}
}
class TypeArgumentStub(
val type: StubType,
val variance: TypeArgument.Variance = TypeArgument.Variance.INVARIANT
) : TypeArgument {
override fun toString(): String =
type.toString()
}
/**
* Represents a source of StubIr element.
*/
sealed class StubOrigin {
/**
* Special case when element of IR was generated.
*/
sealed class Synthetic : StubOrigin() {
object CompanionObject : Synthetic()
/**
* Denotes default constructor that was generated and has no real origin.
*/
object DefaultConstructor : Synthetic()
/**
* CEnum.Companion.byValue.
*/
class EnumByValue(val enum: EnumDef) : Synthetic()
/**
* CEnum.value.
*/
class EnumValueField(val enum: EnumDef) : Synthetic()
/**
* E.CEnumVar.value.
*/
class EnumVarValueField(val enum: EnumDef) : Synthetic()
}
class ObjCCategoryInitMethod(
val method: org.jetbrains.kotlin.native.interop.indexer.ObjCMethod
) : StubOrigin()
class ObjCMethod(
val method: org.jetbrains.kotlin.native.interop.indexer.ObjCMethod,
val container: ObjCContainer
) : StubOrigin()
class ObjCProperty(
val property: org.jetbrains.kotlin.native.interop.indexer.ObjCProperty,
val container: ObjCContainer
) : StubOrigin()
class ObjCClass(
val clazz: org.jetbrains.kotlin.native.interop.indexer.ObjCClass,
val isMeta: Boolean
) : StubOrigin()
class ObjCProtocol(
val protocol: org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol,
val isMeta: Boolean
) : StubOrigin()
class Enum(val enum: EnumDef) : StubOrigin()
class EnumEntry(val constant: EnumConstant) : StubOrigin()
class Function(val function: FunctionDecl) : StubOrigin()
class Struct(val struct: StructDecl) : StubOrigin()
class StructMember(
val member: org.jetbrains.kotlin.native.interop.indexer.StructMember
) : StubOrigin()
class Constant(val constantDef: ConstantDef): StubOrigin()
class Global(val global: GlobalDecl) : StubOrigin()
class TypeDef(val typedefDef: TypedefDef) : StubOrigin()
class VarOf(val typeOrigin: StubOrigin) : StubOrigin()
}
interface StubElementWithOrigin : StubIrElement {
val origin: StubOrigin
}
interface AnnotationHolder {
val annotations: List<AnnotationStub>
}
sealed class AnnotationStub(val classifier: Classifier) {
sealed class ObjC(classifier: Classifier) : AnnotationStub(classifier) {
object ConsumesReceiver :
ObjC(cCallClassifier.nested("ConsumesReceiver"))
object ReturnsRetained :
ObjC(cCallClassifier.nested("ReturnsRetained"))
class Method(val selector: String, val encoding: String, val isStret: Boolean = false) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCMethod"))
class Factory(val selector: String, val encoding: String, val isStret: Boolean = false) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCFactory"))
object Consumed :
ObjC(cCallClassifier.nested("Consumed"))
class Constructor(val selector: String, val designated: Boolean) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCConstructor"))
class ExternalClass(val protocolGetter: String = "", val binaryName: String = "") :
ObjC(Classifier.topLevel(cinteropPackage, "ExternalObjCClass"))
}
sealed class CCall(classifier: Classifier) : AnnotationStub(classifier) {
object CString : CCall(cCallClassifier.nested("CString"))
object WCString : CCall(cCallClassifier.nested("WCString"))
class Symbol(val symbolName: String) : CCall(cCallClassifier)
}
class CStruct(val struct: String) : AnnotationStub(cStructClassifier) {
class MemberAt(val offset: Long) : AnnotationStub(cStructClassifier.nested("MemberAt"))
class ArrayMemberAt(val offset: Long) : AnnotationStub(cStructClassifier.nested("ArrayMemberAt"))
class BitField(val offset: Long, val size: Int) : AnnotationStub(cStructClassifier.nested("BitField"))
class VarType(val size: Long, val align: Int) : AnnotationStub(cStructClassifier.nested("VarType"))
}
class CNaturalStruct(val members: List<StructMember>) :
AnnotationStub(Classifier.topLevel(cinteropPackage, "CNaturalStruct"))
class CLength(val length: Long) :
AnnotationStub(Classifier.topLevel(cinteropPackage, "CLength"))
class Deprecated(val message: String, val replaceWith: String, val level: DeprecationLevel) :
AnnotationStub(Classifier.topLevel("kotlin", "Deprecated")) {
companion object {
val unableToImport = Deprecated(
"Unable to import this declaration",
"",
DeprecationLevel.ERROR
)
val deprecatedCVariableCompanion = Deprecated(
"Use sizeOf<T>() or alignOf<T>() instead.",
"",
DeprecationLevel.WARNING
)
val deprecatedCEnumByValue = Deprecated(
"Will be removed.",
"",
DeprecationLevel.WARNING
)
}
}
class CEnumEntryAlias(val entryName: String) :
AnnotationStub(Classifier.topLevel(cinteropInternalPackage, "CEnumEntryAlias"))
class CEnumVarTypeSize(val size: Int) :
AnnotationStub(Classifier.topLevel(cinteropInternalPackage, "CEnumVarTypeSize"))
private companion object {
val cCallClassifier = Classifier.topLevel(cinteropInternalPackage, "CCall")
val cStructClassifier = Classifier.topLevel(cinteropInternalPackage, "CStruct")
}
}
/**
* Compile-time known values.
*/
sealed class ConstantStub : ValueStub()
class StringConstantStub(val value: String) : ConstantStub()
data class IntegralConstantStub(val value: Long, val size: Int, val isSigned: Boolean) : ConstantStub()
data class DoubleConstantStub(val value: Double, val size: Int) : ConstantStub()
data class PropertyStub(
val name: String,
val type: StubType,
val kind: Kind,
val modality: MemberStubModality = MemberStubModality.FINAL,
val receiverType: StubType? = null,
override val annotations: List<AnnotationStub> = emptyList(),
val origin: StubOrigin,
val isOverride: Boolean = false
) : StubIrElement, AnnotationHolder {
sealed class Kind {
class Val(
val getter: PropertyAccessor.Getter
) : Kind()
class Var(
val getter: PropertyAccessor.Getter,
val setter: PropertyAccessor.Setter
) : Kind()
class Constant(val constant: ConstantStub) : Kind()
}
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T): R {
return visitor.visitProperty(this, data)
}
}
enum class ClassStubModality {
INTERFACE, OPEN, ABSTRACT, NONE
}
enum class VisibilityModifier {
PRIVATE, PROTECTED, INTERNAL, PUBLIC
}
class GetConstructorParameter(
val constructorParameterStub: FunctionParameterStub
) : ValueStub()
class SuperClassInit(
val type: StubType,
val arguments: List<ValueStub> = listOf()
)
// TODO: Consider unifying these classes.
sealed class ClassStub : StubContainer(), StubElementWithOrigin, AnnotationHolder {
abstract val superClassInit: SuperClassInit?
abstract val interfaces: List<StubType>
abstract val childrenClasses: List<ClassStub>
abstract val companion : Companion?
abstract val classifier: Classifier
class Simple(
override val classifier: Classifier,
val modality: ClassStubModality,
constructors: List<ConstructorStub> = emptyList(),
methods: List<FunctionStub> = emptyList(),
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val origin: StubOrigin,
override val annotations: List<AnnotationStub> = emptyList(),
override val childrenClasses: List<ClassStub> = emptyList(),
override val companion: Companion? = null,
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : ClassStub() {
override val functions: List<FunctionalStub> = constructors + methods
}
class Companion(
override val classifier: Classifier,
methods: List<FunctionStub> = emptyList(),
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val origin: StubOrigin = StubOrigin.Synthetic.CompanionObject,
override val annotations: List<AnnotationStub> = emptyList(),
override val childrenClasses: List<ClassStub> = emptyList(),
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : ClassStub() {
override val companion: Companion? = null
override val functions: List<FunctionalStub> = methods
}
class Enum(
override val classifier: Classifier,
val entries: List<EnumEntryStub>,
constructors: List<ConstructorStub>,
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val origin: StubOrigin,
override val annotations: List<AnnotationStub> = emptyList(),
override val childrenClasses: List<ClassStub> = emptyList(),
override val companion: Companion?= null,
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : ClassStub() {
override val functions: List<FunctionalStub> = constructors
}
override val meta: StubContainerMeta = StubContainerMeta()
override val classes: List<ClassStub>
get() = childrenClasses + listOfNotNull(companion)
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitClass(this, data)
override val typealiases: List<TypealiasStub> = emptyList()
}
class ReceiverParameterStub(
val type: StubType
)
class FunctionParameterStub(
val name: String,
val type: StubType,
override val annotations: List<AnnotationStub> = emptyList(),
val isVararg: Boolean = false
) : AnnotationHolder
enum class MemberStubModality {
OPEN,
FINAL,
ABSTRACT
}
interface FunctionalStub : AnnotationHolder, StubIrElement, NativeBacked {
val parameters: List<FunctionParameterStub>
}
sealed class PropertyAccessor : FunctionalStub {
sealed class Getter : PropertyAccessor() {
override val parameters: List<FunctionParameterStub> = emptyList()
class SimpleGetter(
override val annotations: List<AnnotationStub> = emptyList(),
val constant: ConstantStub? = null
) : Getter()
class GetConstructorParameter(
val constructorParameter: FunctionParameterStub,
override val annotations: List<AnnotationStub> = emptyList()
) : Getter()
class ExternalGetter(
override val annotations: List<AnnotationStub> = emptyList()
) : Getter()
class ArrayMemberAt(
val offset: Long
) : Getter() {
override val parameters: List<FunctionParameterStub> = emptyList()
override val annotations: List<AnnotationStub> = emptyList()
}
class MemberAt(
val offset: Long,
val typeArguments: List<TypeArgumentStub> = emptyList(),
val hasValueAccessor: Boolean
) : Getter() {
override val annotations: List<AnnotationStub> = emptyList()
}
class ReadBits(
val offset: Long,
val size: Int,
val signed: Boolean
) : Getter() {
override val annotations: List<AnnotationStub> = emptyList()
}
class InterpretPointed(val cGlobalName:String, pointedType: StubType) : Getter() {
override val annotations: List<AnnotationStub> = emptyList()
val typeParameters: List<StubType> = listOf(pointedType)
}
class GetEnumEntry(
val enumEntryStub: EnumEntryStub,
override val annotations: List<AnnotationStub> = emptyList()
) : Getter()
}
sealed class Setter : PropertyAccessor() {
override val parameters: List<FunctionParameterStub> = emptyList()
class SimpleSetter(
override val annotations: List<AnnotationStub> = emptyList()
) : Setter()
class ExternalSetter(
override val annotations: List<AnnotationStub> = emptyList()
) : Setter()
class MemberAt(
val offset: Long,
override val annotations: List<AnnotationStub> = emptyList(),
val typeArguments: List<TypeArgumentStub> = emptyList()
) : Setter()
class WriteBits(
val offset: Long,
val size: Int,
override val annotations: List<AnnotationStub> = emptyList()
) : Setter()
}
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitPropertyAccessor(this, data)
}
data class FunctionStub(
val name: String,
val returnType: StubType,
override val parameters: List<FunctionParameterStub>,
override val origin: StubOrigin,
override val annotations: List<AnnotationStub>,
val external: Boolean = false,
val receiver: ReceiverParameterStub?,
val modality: MemberStubModality,
val typeParameters: List<TypeParameterStub> = emptyList(),
val isOverride: Boolean = false,
val hasStableParameterNames: Boolean = true
) : StubElementWithOrigin, FunctionalStub {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitFunction(this, data)
}
// TODO: should we support non-trivial constructors?
class ConstructorStub(
override val parameters: List<FunctionParameterStub> = emptyList(),
override val annotations: List<AnnotationStub> = emptyList(),
val isPrimary: Boolean,
val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
val origin: StubOrigin
) : FunctionalStub {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitConstructor(this, data)
}
class EnumEntryStub(
val name: String,
val constant: IntegralConstantStub,
val origin: StubOrigin.EnumEntry,
val ordinal: Int
)
class TypealiasStub(
val alias: Classifier,
val aliasee: StubType,
val origin: StubOrigin
) : StubIrElement {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitTypealias(this, data)
}
@@ -0,0 +1,319 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class BridgeBuilderResult(
val kotlinFile: KotlinFile,
val nativeBridges: NativeBridges,
val propertyAccessorBridgeBodies: Map<PropertyAccessor, String>,
val functionBridgeBodies: Map<FunctionStub, List<String>>,
val excludedStubs: Set<StubIrElement>
)
/**
* Generates [NativeBridges] and corresponding function bodies and property accessors.
*/
class StubIrBridgeBuilder(
private val context: StubIrContext,
private val builderResult: StubIrBuilderResult) {
private val globalAddressExpressions = mutableMapOf<Pair<String, PropertyAccessor>, KotlinExpression>()
private val wrapperGenerator = CWrappersGenerator(context)
private fun getGlobalAddressExpression(cGlobalName: String, accessor: PropertyAccessor) =
globalAddressExpressions.getOrPut(Pair(cGlobalName, accessor)) {
simpleBridgeGenerator.kotlinToNative(
nativeBacked = accessor,
returnType = BridgedType.NATIVE_PTR,
kotlinValues = emptyList(),
independent = false
) {
"&$cGlobalName"
}
}
private val declarationMapper = builderResult.declarationMapper
private val kotlinFile = object : KotlinFile(
context.configuration.pkgName,
namesToBeDeclared = builderResult.stubs.computeNamesToBeDeclared(context.configuration.pkgName)
) {
override val mappingBridgeGenerator: MappingBridgeGenerator
get() = this@StubIrBridgeBuilder.mappingBridgeGenerator
}
private val simpleBridgeGenerator: SimpleBridgeGenerator =
SimpleBridgeGeneratorImpl(
context.platform,
context.configuration.pkgName,
context.jvmFileClassName,
context.libraryForCStubs,
topLevelNativeScope = object : NativeScope {
override val mappingBridgeGenerator: MappingBridgeGenerator
get() = this@StubIrBridgeBuilder.mappingBridgeGenerator
},
topLevelKotlinScope = kotlinFile
)
private val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
private val excludedStubs = mutableSetOf<StubIrElement>()
private val bridgeGeneratingVisitor = object : StubIrVisitor<StubContainer?, Unit> {
override fun visitClass(element: ClassStub, data: StubContainer?) {
element.annotations.filterIsInstance<AnnotationStub.ObjC.ExternalClass>().firstOrNull()?.let {
val origin = element.origin
if (it.protocolGetter.isNotEmpty() && origin is StubOrigin.ObjCProtocol && !origin.isMeta) {
val protocol = (element.origin as StubOrigin.ObjCProtocol).protocol
// TODO: handle the case when protocol getter stub can't be compiled.
generateProtocolGetter(it.protocolGetter, protocol)
}
}
element.children.forEach {
it.accept(this, element)
}
}
override fun visitTypealias(element: TypealiasStub, data: StubContainer?) {
}
override fun visitFunction(element: FunctionStub, data: StubContainer?) {
try {
when {
element.external -> tryProcessCCallAnnotation(element)
element.isOptionalObjCMethod() -> { }
element.origin is StubOrigin.Synthetic.EnumByValue -> { }
data != null && data.isInterface -> { }
else -> generateBridgeBody(element)
}
} catch (e: Throwable) {
context.log("Warning: cannot generate bridge for ${element.name}.")
excludedStubs += element
}
}
private fun tryProcessCCallAnnotation(function: FunctionStub) {
val origin = function.origin as? StubOrigin.Function
?: return
val cCallAnnotation = function.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: return
val wrapper = wrapperGenerator.generateCCalleeWrapper(origin.function, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(function, emptyList(), wrapper.lines)
}
override fun visitProperty(element: PropertyStub, data: StubContainer?) {
try {
when (val kind = element.kind) {
is PropertyStub.Kind.Constant -> {
}
is PropertyStub.Kind.Val -> {
visitPropertyAccessor(kind.getter, data)
}
is PropertyStub.Kind.Var -> {
visitPropertyAccessor(kind.getter, data)
visitPropertyAccessor(kind.setter, data)
}
}
} catch (e: Throwable) {
context.log("Warning: cannot generate bridge for ${element.name}.")
excludedStubs += element
}
}
override fun visitConstructor(constructorStub: ConstructorStub, data: StubContainer?) {
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: StubContainer?) {
when (propertyAccessor) {
is PropertyAccessor.Getter.SimpleGetter -> {
when (propertyAccessor) {
in builderResult.bridgeGenerationComponents.getterToBridgeInfo -> {
val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(propertyAccessor)
val typeInfo = extra.typeInfo
propertyAccessorBridgeBodies[propertyAccessor] = typeInfo.argFromBridged(simpleBridgeGenerator.kotlinToNative(
nativeBacked = propertyAccessor,
returnType = typeInfo.bridgedType,
kotlinValues = emptyList(),
independent = false
) {
typeInfo.cToBridged(expr = extra.cGlobalName)
}, kotlinFile, nativeBacked = propertyAccessor)
}
in builderResult.bridgeGenerationComponents.arrayGetterInfo -> {
val extra = builderResult.bridgeGenerationComponents.arrayGetterInfo.getValue(propertyAccessor)
val typeInfo = extra.typeInfo
val getAddressExpression = getGlobalAddressExpression(extra.cGlobalName, propertyAccessor)
propertyAccessorBridgeBodies[propertyAccessor] = typeInfo.argFromBridged(getAddressExpression, kotlinFile, nativeBacked = propertyAccessor) + "!!"
}
}
}
is PropertyAccessor.Getter.ReadBits -> {
val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(propertyAccessor)
val rawType = extra.typeInfo.bridgedType
val readBits = "readBits(this.rawPtr, ${propertyAccessor.offset}, ${propertyAccessor.size}, ${propertyAccessor.signed}).${rawType.convertor!!}()"
val getExpr = extra.typeInfo.argFromBridged(readBits, kotlinFile, object : NativeBacked {})
propertyAccessorBridgeBodies[propertyAccessor] = getExpr
}
is PropertyAccessor.Setter.SimpleSetter -> when (propertyAccessor) {
in builderResult.bridgeGenerationComponents.setterToBridgeInfo -> {
val extra = builderResult.bridgeGenerationComponents.setterToBridgeInfo.getValue(propertyAccessor)
val typeInfo = extra.typeInfo
val bridgedValue = BridgeTypedKotlinValue(typeInfo.bridgedType, typeInfo.argToBridged("value"))
val setter = simpleBridgeGenerator.kotlinToNative(
nativeBacked = propertyAccessor,
returnType = BridgedType.VOID,
kotlinValues = listOf(bridgedValue),
independent = false
) { nativeValues ->
out("${extra.cGlobalName} = ${typeInfo.cFromBridged(
nativeValues.single(),
scope,
nativeBacked = propertyAccessor
)};")
""
}
propertyAccessorBridgeBodies[propertyAccessor] = setter
}
}
is PropertyAccessor.Setter.WriteBits -> {
val extra = builderResult.bridgeGenerationComponents.setterToBridgeInfo.getValue(propertyAccessor)
val rawValue = extra.typeInfo.argToBridged("value")
propertyAccessorBridgeBodies[propertyAccessor] = "writeBits(this.rawPtr, ${propertyAccessor.offset}, ${propertyAccessor.size}, $rawValue.toLong())"
}
is PropertyAccessor.Getter.InterpretPointed -> {
val getAddressExpression = getGlobalAddressExpression(propertyAccessor.cGlobalName, propertyAccessor)
propertyAccessorBridgeBodies[propertyAccessor] = getAddressExpression
}
is PropertyAccessor.Getter.ExternalGetter -> {
if (propertyAccessor in builderResult.wrapperGenerationComponents.getterToWrapperInfo) {
val extra = builderResult.wrapperGenerationComponents.getterToWrapperInfo.getValue(propertyAccessor)
val cCallAnnotation = propertyAccessor.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: error("external getter for ${extra.global.name} wasn't marked with @CCall")
val wrapper = if (extra.passViaPointer) {
wrapperGenerator.generateCGlobalByPointerGetter(extra.global, cCallAnnotation.symbolName)
} else {
wrapperGenerator.generateCGlobalGetter(extra.global, cCallAnnotation.symbolName)
}
simpleBridgeGenerator.insertNativeBridge(propertyAccessor, emptyList(), wrapper.lines)
}
}
is PropertyAccessor.Setter.ExternalSetter -> {
if (propertyAccessor in builderResult.wrapperGenerationComponents.setterToWrapperInfo) {
val extra = builderResult.wrapperGenerationComponents.setterToWrapperInfo.getValue(propertyAccessor)
val cCallAnnotation = propertyAccessor.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: error("external setter for ${extra.global.name} wasn't marked with @CCall")
val wrapper = wrapperGenerator.generateCGlobalSetter(extra.global, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(propertyAccessor, emptyList(), wrapper.lines)
}
}
}
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: StubContainer?) {
simpleStubContainer.classes.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.functions.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.properties.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.typealiases.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.simpleContainers.forEach {
it.accept(this, simpleStubContainer)
}
}
}
private fun isCValuesRef(type: StubType): Boolean =
(type as? ClassifierStubType)?.let { it.classifier == KotlinTypes.cValuesRef }
?: false
private fun generateBridgeBody(function: FunctionStub) {
assert(context.platform == KotlinPlatform.JVM) { "Function ${function.name} was not marked as external." }
assert(function.origin is StubOrigin.Function) { "Can't create bridge for ${function.name}" }
val origin = function.origin as StubOrigin.Function
val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile)
val bridgeArguments = mutableListOf<TypedKotlinValue>()
var isVararg = false
function.parameters.forEachIndexed { index, parameter ->
isVararg = isVararg or parameter.isVararg
val parameterName = parameter.name.asSimpleName()
val bridgeArgument = when {
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.cstr?.getPointer(memScope)"
}
parameter in builderResult.bridgeGenerationComponents.wCStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.wcstr?.getPointer(memScope)"
}
isCValuesRef(parameter.type) -> {
bodyGenerator.pushMemScoped()
bodyGenerator.getNativePointer(parameterName)
}
else -> {
parameterName
}
}
bridgeArguments += TypedKotlinValue(origin.function.parameters[index].type, bridgeArgument)
}
// TODO: Improve assertion message.
assert(!isVararg || context.platform != KotlinPlatform.NATIVE) {
"Function ${function.name} was processed incorrectly."
}
val result = mappingBridgeGenerator.kotlinToNative(
bodyGenerator,
function,
origin.function.returnType,
bridgeArguments,
independent = false
) { nativeValues ->
"${origin.function.name}(${nativeValues.joinToString()})"
}
bodyGenerator.returnResult(result)
functionBridgeBodies[function] = bodyGenerator.build()
}
private fun generateProtocolGetter(protocolGetterName: String, protocol: ObjCProtocol) {
val builder = NativeCodeBuilder(simpleBridgeGenerator.topLevelNativeScope)
val nativeBacked = object : NativeBacked {}
with(builder) {
out("Protocol* $protocolGetterName() {")
out(" return @protocol(${protocol.name});")
out("}")
}
simpleBridgeGenerator.insertNativeBridge(nativeBacked, emptyList(), builder.lines)
}
fun build(): BridgeBuilderResult {
bridgeGeneratingVisitor.visitSimpleStubContainer(builderResult.stubs, null)
return BridgeBuilderResult(
kotlinFile,
simpleBridgeGenerator.prepare(),
propertyAccessorBridgeBodies.toMap(),
functionBridgeBodies.toMap(),
excludedStubs.toSet()
)
}
}
@@ -0,0 +1,407 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* Components that are not passed via StubIr but required for bridge generation.
*/
class BridgeGenerationInfo(val cGlobalName: String, val typeInfo: TypeInfo)
/**
* Additional components that are required to generate bridges.
* TODO: Metadata-based interop should not depend on these components.
*/
interface BridgeGenerationComponents {
val setterToBridgeInfo: Map<PropertyAccessor.Setter, BridgeGenerationInfo>
val getterToBridgeInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo>
val arrayGetterInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo>
val enumToTypeMirror: Map<ClassStub.Enum, TypeMirror>
val wCStringParameters: Set<FunctionParameterStub>
val cStringParameters: Set<FunctionParameterStub>
}
class BridgeGenerationComponentsBuilder {
val getterToBridgeInfo = mutableMapOf<PropertyAccessor.Getter, BridgeGenerationInfo>()
val setterToBridgeInfo = mutableMapOf<PropertyAccessor.Setter, BridgeGenerationInfo>()
val arrayGetterBridgeInfo = mutableMapOf<PropertyAccessor.Getter, BridgeGenerationInfo>()
val enumToTypeMirror = mutableMapOf<ClassStub.Enum, TypeMirror>()
val wCStringParameters = mutableSetOf<FunctionParameterStub>()
val cStringParameters = mutableSetOf<FunctionParameterStub>()
fun build(): BridgeGenerationComponents = object : BridgeGenerationComponents {
override val getterToBridgeInfo =
this@BridgeGenerationComponentsBuilder.getterToBridgeInfo.toMap()
override val setterToBridgeInfo =
this@BridgeGenerationComponentsBuilder.setterToBridgeInfo.toMap()
override val enumToTypeMirror =
this@BridgeGenerationComponentsBuilder.enumToTypeMirror.toMap()
override val wCStringParameters: Set<FunctionParameterStub> =
this@BridgeGenerationComponentsBuilder.wCStringParameters.toSet()
override val cStringParameters: Set<FunctionParameterStub> =
this@BridgeGenerationComponentsBuilder.cStringParameters.toSet()
override val arrayGetterInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo> =
this@BridgeGenerationComponentsBuilder.arrayGetterBridgeInfo.toMap()
}
}
/**
* Components that are not passed via StubIr but required for generation of wrappers.
*/
class WrapperGenerationInfo(val global: GlobalDecl, val passViaPointer: Boolean = false)
interface WrapperGenerationComponents {
val getterToWrapperInfo: Map<PropertyAccessor.Getter.ExternalGetter, WrapperGenerationInfo>
val setterToWrapperInfo: Map<PropertyAccessor.Setter.ExternalSetter, WrapperGenerationInfo>
}
class WrapperGenerationComponentsBuilder {
val getterToWrapperInfo = mutableMapOf<PropertyAccessor.Getter.ExternalGetter, WrapperGenerationInfo>()
val setterToWrapperInfo = mutableMapOf<PropertyAccessor.Setter.ExternalSetter, WrapperGenerationInfo>()
fun build(): WrapperGenerationComponents = object : WrapperGenerationComponents {
override val getterToWrapperInfo = this@WrapperGenerationComponentsBuilder.getterToWrapperInfo.toMap()
override val setterToWrapperInfo = this@WrapperGenerationComponentsBuilder.setterToWrapperInfo.toMap()
}
}
/**
* Common part of all [StubIrBuilder] implementations.
*/
interface StubsBuildingContext {
val configuration: InteropConfiguration
fun mirror(type: Type): TypeMirror
val declarationMapper: DeclarationMapper
fun generateNextUniqueId(prefix: String): String
val generatedObjCCategoriesMembers: MutableMap<ObjCClass, GeneratedObjCCategoriesMembers>
val platform: KotlinPlatform
/**
* In some cases StubIr should be different for metadata and sourcecode modes.
* For example, it is impossible to represent call to superclass constructor in
* metadata directly and arguments should be passed via annotations instead.
*/
val generationMode: GenerationMode
fun isStrictEnum(enumDef: EnumDef): Boolean
val macroConstantsByName: Map<String, MacroDef>
fun tryCreateIntegralStub(type: Type, value: Long): IntegralConstantStub?
fun tryCreateDoubleStub(type: Type, value: Double): DoubleConstantStub?
val bridgeComponentsBuilder: BridgeGenerationComponentsBuilder
val wrapperComponentsBuilder: WrapperGenerationComponentsBuilder
fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean = false): Classifier
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
fun isOverloading(func: FunctionDecl): Boolean
}
/**
*
*/
internal interface StubElementBuilder {
val context: StubsBuildingContext
fun build(): List<StubIrElement>
}
class StubsBuildingContextImpl(
private val stubIrContext: StubIrContext
) : StubsBuildingContext {
override val configuration: InteropConfiguration = stubIrContext.configuration
override val platform: KotlinPlatform = stubIrContext.platform
override val generationMode: GenerationMode = stubIrContext.generationMode
val imports: Imports = stubIrContext.imports
private val nativeIndex: NativeIndex = stubIrContext.nativeIndex
private var theCounter = 0
private val uniqFunctions = mutableSetOf<String>()
override fun isOverloading(func: FunctionDecl) = !uniqFunctions.add(func.name) // TODO: params & return type.
override fun generateNextUniqueId(prefix: String) =
prefix + pkgName.replace('.', '_') + theCounter++
override fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type)
/**
* Indicates whether this enum should be represented as Kotlin enum.
*/
override fun isStrictEnum(enumDef: EnumDef): Boolean = with(enumDef) {
if (this.isAnonymous) {
return false
}
val name = this.kotlinName
if (name in configuration.strictEnums) {
return true
}
if (name in configuration.nonStrictEnums) {
return false
}
// Let the simple heuristic decide:
return !this.constants.any { it.isExplicitlyDefined }
}
override val generatedObjCCategoriesMembers = mutableMapOf<ObjCClass, GeneratedObjCCategoriesMembers>()
override val declarationMapper = object : DeclarationMapper {
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val baseName = structDecl.kotlinName
val pkg = when (platform) {
KotlinPlatform.JVM -> pkgName
KotlinPlatform.NATIVE -> if (structDecl.def == null) {
cnamesStructsPackageName // to be imported as forward declaration.
} else {
getPackageFor(structDecl)
}
}
return Classifier.topLevel(pkg, baseName)
}
override fun isMappedToStrict(enumDef: EnumDef): Boolean = isStrictEnum(enumDef)
override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName
override fun getPackageFor(declaration: TypeDeclaration): String {
return imports.getPackage(declaration.location) ?: pkgName
}
override val useUnsignedTypes: Boolean
get() = when (platform) {
KotlinPlatform.JVM -> false
KotlinPlatform.NATIVE -> true
}
}
override val macroConstantsByName: Map<String, MacroDef> =
(nativeIndex.macroConstants + nativeIndex.wrappedMacros).associateBy { it.name }
/**
* The name to be used for this enum in Kotlin
*/
val EnumDef.kotlinName: String
get() = if (spelling.startsWith("enum ")) {
spelling.substringAfter(' ')
} else {
assert (!isAnonymous)
spelling
}
private val pkgName: String
get() = configuration.pkgName
/**
* The name to be used for this struct in Kotlin
*/
val StructDecl.kotlinName: String
get() = stubIrContext.getKotlinName(this)
override fun tryCreateIntegralStub(type: Type, value: Long): IntegralConstantStub? {
val integerType = when (val unwrappedType = type.unwrapTypedefs()) {
is IntegerType -> unwrappedType
CharType -> IntegerType(1, true, "char")
else -> return null
}
val size = integerType.size
if (size != 1 && size != 2 && size != 4 && size != 8) return null
return IntegralConstantStub(value, size, declarationMapper.isMappedToSigned(integerType))
}
override fun tryCreateDoubleStub(type: Type, value: Double): DoubleConstantStub? {
val unwrappedType = type.unwrapTypedefs() as? FloatingType ?: return null
val size = unwrappedType.size
if (size != 4 && size != 8) return null
return DoubleConstantStub(value, size)
}
override val bridgeComponentsBuilder = BridgeGenerationComponentsBuilder()
override val wrapperComponentsBuilder = WrapperGenerationComponentsBuilder()
override fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean): Classifier {
return declarationMapper.getKotlinClassFor(objCClassOrProtocol, isMeta)
}
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val classifier = declarationMapper.getKotlinClassForPointed(structDecl)
return classifier
}
}
data class StubIrBuilderResult(
val stubs: SimpleStubContainer,
val declarationMapper: DeclarationMapper,
val bridgeGenerationComponents: BridgeGenerationComponents,
val wrapperGenerationComponents: WrapperGenerationComponents
)
/**
* Produces [StubIrBuilderResult] for given [KotlinPlatform] using [InteropConfiguration].
*/
class StubIrBuilder(private val context: StubIrContext) {
private val configuration = context.configuration
private val nativeIndex: NativeIndex = context.nativeIndex
private val classes = mutableListOf<ClassStub>()
private val functions = mutableListOf<FunctionStub>()
private val globals = mutableListOf<PropertyStub>()
private val typealiases = mutableListOf<TypealiasStub>()
private val containers = mutableListOf<SimpleStubContainer>()
private fun addStubs(stubs: List<StubIrElement>) = stubs.forEach(this::addStub)
private fun addStub(stub: StubIrElement) {
when(stub) {
is ClassStub -> classes += stub
is FunctionStub -> functions += stub
is PropertyStub -> globals += stub
is TypealiasStub -> typealiases += stub
is SimpleStubContainer -> containers += stub
else -> error("Unexpected stub: $stub")
}
}
private val excludedFunctions: Set<String>
get() = configuration.excludedFunctions
private val excludedMacros: Set<String>
get() = configuration.excludedMacros
private val buildingContext = StubsBuildingContextImpl(context)
fun build(): StubIrBuilderResult {
nativeIndex.objCProtocols.filter { !it.isForwardDeclaration }.forEach { generateStubsForObjCProtocol(it) }
nativeIndex.objCClasses.filter { !it.isForwardDeclaration && !it.isNSStringSubclass()} .forEach { generateStubsForObjCClass(it) }
nativeIndex.objCCategories.filter { !it.clazz.isNSStringSubclass() }.forEach { generateStubsForObjCCategory(it) }
nativeIndex.structs.forEach { generateStubsForStruct(it) }
nativeIndex.enums.forEach { generateStubsForEnum(it) }
nativeIndex.functions.filter { it.name !in excludedFunctions }.forEach { generateStubsForFunction(it) }
nativeIndex.typedefs.forEach { generateStubsForTypedef(it) }
nativeIndex.globals.filter { it.name !in excludedFunctions }.forEach { generateStubsForGlobal(it) }
nativeIndex.macroConstants.filter { it.name !in excludedMacros }.forEach { generateStubsForMacroConstant(it) }
nativeIndex.wrappedMacros.filter { it.name !in excludedMacros }.forEach { generateStubsForWrappedMacro(it) }
val meta = StubContainerMeta()
val stubs = SimpleStubContainer(
meta,
classes.toList(),
functions.toList(),
globals.toList(),
typealiases.toList(),
containers.toList()
)
return StubIrBuilderResult(
stubs,
buildingContext.declarationMapper,
buildingContext.bridgeComponentsBuilder.build(),
buildingContext.wrapperComponentsBuilder.build()
)
}
private fun generateStubsForWrappedMacro(macro: WrappedMacroDef) {
try {
generateStubsForGlobal(GlobalDecl(macro.name, macro.type, isConst = true))
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for macro ${macro.name}")
}
}
private fun generateStubsForMacroConstant(constant: ConstantDef) {
try {
addStubs(MacroConstantStubBuilder(buildingContext, constant).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for constant ${constant.name}")
}
}
private fun generateStubsForEnum(enumDef: EnumDef) {
try {
addStubs(EnumStubBuilder(buildingContext, enumDef).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate definition for enum ${enumDef.spelling}")
}
}
private fun generateStubsForFunction(func: FunctionDecl) {
try {
addStubs(FunctionStubBuilder(buildingContext, func, skipOverloads = true).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for function ${func.name}")
}
}
private fun generateStubsForStruct(decl: StructDecl) {
try {
addStubs(StructStubBuilder(buildingContext, decl).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate definition for struct ${decl.spelling}")
}
}
private fun generateStubsForTypedef(typedefDef: TypedefDef) {
try {
addStubs(TypedefStubBuilder(buildingContext, typedefDef).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate typedef ${typedefDef.name}")
}
}
private fun generateStubsForGlobal(global: GlobalDecl) {
try {
addStubs(GlobalStubBuilder(buildingContext, global).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for global ${global.name}")
}
}
private fun generateStubsForObjCProtocol(objCProtocol: ObjCProtocol) {
addStubs(ObjCProtocolStubBuilder(buildingContext, objCProtocol).build())
}
private fun generateStubsForObjCClass(objCClass: ObjCClass) {
addStubs(ObjCClassStubBuilder(buildingContext, objCClass).build())
}
private fun generateStubsForObjCCategory(objCCategory: ObjCCategory) {
addStubs(ObjCCategoryStubBuilder(buildingContext, objCCategory).build())
}
}
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
import java.io.File
import java.util.*
class StubIrContext(
val log: (String) -> Unit,
val configuration: InteropConfiguration,
val nativeIndex: NativeIndex,
val imports: Imports,
val platform: KotlinPlatform,
val generationMode: GenerationMode,
val libName: String
) {
val libraryForCStubs = configuration.library.copy(
includes = mutableListOf<String>().apply {
add("stdint.h")
add("string.h")
if (platform == KotlinPlatform.JVM) {
add("jni.h")
}
addAll(configuration.library.includes)
},
compilerArgs = configuration.library.compilerArgs,
additionalPreambleLines = configuration.library.additionalPreambleLines +
when (configuration.library.language) {
Language.C -> emptyList()
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
}
).precompileHeaders()
// TODO: Used only for JVM.
val jvmFileClassName = if (configuration.pkgName.isEmpty()) {
libName
} else {
configuration.pkgName.substringAfterLast('.')
}
val validPackageName = configuration.pkgName.split(".").joinToString(".") {
if (it.matches(VALID_PACKAGE_NAME_REGEX)) it else "`$it`"
}
private val anonymousStructKotlinNames = mutableMapOf<StructDecl, String>()
private val forbiddenStructNames = run {
val typedefNames = nativeIndex.typedefs.map { it.name }
typedefNames.toSet()
}
/**
* The name to be used for this struct in Kotlin
*/
fun getKotlinName(decl: StructDecl): String {
val spelling = decl.spelling
if (decl.isAnonymous) {
val names = anonymousStructKotlinNames
return names.getOrPut(decl) {
"anonymousStruct${names.size + 1}"
}
}
val strippedCName = if (spelling.startsWith("struct ") || spelling.startsWith("union ")) {
spelling.substringAfter(' ')
} else {
spelling
}
// TODO: don't mangle struct names because it wouldn't work if the struct
// is imported into another interop library.
return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct")
}
fun addManifestProperties(properties: Properties) {
val exportForwardDeclarations = configuration.exportForwardDeclarations.toMutableList()
nativeIndex.structs
.filter { it.def == null }
.mapTo(exportForwardDeclarations) {
"$cnamesStructsPackageName.${getKotlinName(it)}"
}
properties["exportForwardDeclarations"] = exportForwardDeclarations.joinToString(" ")
// TODO: consider exporting Objective-C class and protocol forward refs.
}
companion object {
private val VALID_PACKAGE_NAME_REGEX = "[a-zA-Z0-9_.]+".toRegex()
}
}
class StubIrDriver(
private val context: StubIrContext,
private val options: DriverOptions
) {
data class DriverOptions(
val entryPoint: String?,
val moduleName: String,
val outCFile: File,
val outKtFileCreator: () -> File
)
sealed class Result {
object SourceCode : Result()
class Metadata(val metadata: KlibModuleMetadata): Result()
}
fun run(): Result {
val (entryPoint, moduleName, outCFile, outKtFile) = options
val builderResult = StubIrBuilder(context).build()
val bridgeBuilderResult = StubIrBridgeBuilder(context, builderResult).build()
outCFile.bufferedWriter().use {
emitCFile(context, it, entryPoint, bridgeBuilderResult.nativeBridges)
}
return when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
emitSourceCode(outKtFile(), builderResult, bridgeBuilderResult)
}
GenerationMode.METADATA -> emitMetadata(builderResult, moduleName, bridgeBuilderResult)
}
}
private fun emitSourceCode(
outKtFile: File, builderResult: StubIrBuilderResult, bridgeBuilderResult: BridgeBuilderResult
): Result.SourceCode {
outKtFile.bufferedWriter().use { ktFile ->
StubIrTextEmitter(context, builderResult, bridgeBuilderResult).emit(ktFile)
}
return Result.SourceCode
}
private fun emitMetadata(
builderResult: StubIrBuilderResult, moduleName: String, bridgeBuilderResult: BridgeBuilderResult
) = Result.Metadata(StubIrMetadataEmitter(context, builderResult, moduleName, bridgeBuilderResult).emit())
private fun emitCFile(context: StubIrContext, cFile: Appendable, entryPoint: String?, nativeBridges: NativeBridges) {
val out = { it: String -> cFile.appendLine(it) }
context.libraryForCStubs.preambleLines.forEach {
out(it)
}
out("")
out("// NOTE THIS FILE IS AUTO-GENERATED")
out("")
nativeBridges.nativeLines.forEach { out(it) }
if (entryPoint != null) {
out("extern int Konan_main(int argc, char** argv);")
out("")
out("__attribute__((__used__))")
out("int $entryPoint(int argc, char** argv) {")
out(" return Konan_main(argc, argv);")
out("}")
}
}
}
@@ -0,0 +1,727 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
internal class MacroConstantStubBuilder(
override val context: StubsBuildingContext,
private val constant: ConstantDef
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val kotlinName = constant.name
val origin = StubOrigin.Constant(constant)
val declaration = when (constant) {
is IntegerConstantDef -> {
val literal = context.tryCreateIntegralStub(constant.type, constant.value) ?: return emptyList()
val kotlinType = context.mirror(constant.type).argType.toStubIrType()
when (context.platform) {
KotlinPlatform.NATIVE -> PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Constant(literal), origin = origin)
// No reason to make it const val with backing field on Kotlin/JVM yet:
KotlinPlatform.JVM -> {
val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal)
PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Val(getter), origin = origin)
}
}
}
is FloatingConstantDef -> {
val literal = context.tryCreateDoubleStub(constant.type, constant.value) ?: return emptyList()
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyStub.Kind.Val(PropertyAccessor.Getter.SimpleGetter(constant = literal))
}
GenerationMode.METADATA -> {
PropertyStub.Kind.Constant(literal)
}
}
val kotlinType = context.mirror(constant.type).argType.toStubIrType()
PropertyStub(kotlinName, kotlinType, kind, origin = origin)
}
is StringConstantDef -> {
val literal = StringConstantStub(constant.value)
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyStub.Kind.Val(PropertyAccessor.Getter.SimpleGetter(constant = literal))
}
GenerationMode.METADATA -> {
PropertyStub.Kind.Constant(literal)
}
}
PropertyStub(kotlinName, KotlinTypes.string.toStubIrType(), kind, origin = origin)
}
else -> return emptyList()
}
return listOf(declaration)
}
}
internal class StructStubBuilder(
override val context: StubsBuildingContext,
private val decl: StructDecl
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val platform = context.platform
val def = decl.def ?: return generateForwardStruct(decl)
val structAnnotation: AnnotationStub? = if (platform == KotlinPlatform.JVM) {
if (def.kind == StructDef.Kind.STRUCT && def.fieldsHaveDefaultAlignment()) {
AnnotationStub.CNaturalStruct(def.members)
} else {
null
}
} else {
tryRenderStructOrUnion(def)?.let {
AnnotationStub.CStruct(it)
}
}
val classifier = context.getKotlinClassForPointed(decl)
val fields: List<PropertyStub?> = def.fields.map { field ->
try {
assert(field.name.isNotEmpty())
assert(field.offset % 8 == 0L)
val offset = field.offset / 8
val fieldRefType = context.mirror(field.type)
val unwrappedFieldType = field.type.unwrapTypedefs()
val origin = StubOrigin.StructMember(field)
val fieldName = mangleSimple(field.name)
if (unwrappedFieldType is ArrayType) {
val type = (fieldRefType as TypeMirror.ByValue).valueType
val annotations = if (platform == KotlinPlatform.JVM) {
val length = getArrayLength(unwrappedFieldType)
// TODO: @CLength should probably be used on types instead of properties.
listOf(AnnotationStub.CLength(length))
} else {
emptyList()
}
val getter = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> PropertyAccessor.Getter.ArrayMemberAt(offset)
GenerationMode.METADATA -> PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.ArrayMemberAt(offset)))
}
val kind = PropertyStub.Kind.Val(getter)
// TODO: Should receiver be added?
PropertyStub(fieldName, type.toStubIrType(), kind, annotations = annotations, origin = origin)
} else {
val pointedType = fieldRefType.pointedType.toStubIrType()
val pointedTypeArgument = TypeArgumentStub(pointedType)
if (fieldRefType is TypeMirror.ByValue) {
val getter: PropertyAccessor.Getter
val setter: PropertyAccessor.Setter
when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
getter = PropertyAccessor.Getter.MemberAt(offset, typeArguments = listOf(pointedTypeArgument), hasValueAccessor = true)
setter = PropertyAccessor.Setter.MemberAt(offset, typeArguments = listOf(pointedTypeArgument))
}
GenerationMode.METADATA -> {
getter = PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.MemberAt(offset)))
setter = PropertyAccessor.Setter.ExternalSetter(listOf(AnnotationStub.CStruct.MemberAt(offset)))
}
}
val kind = PropertyStub.Kind.Var(getter, setter)
PropertyStub(fieldName, fieldRefType.argType.toStubIrType(), kind, origin = origin)
} else {
val accessor = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> PropertyAccessor.Getter.MemberAt(offset, hasValueAccessor = false)
GenerationMode.METADATA -> PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.MemberAt(offset)))
}
val kind = PropertyStub.Kind.Val(accessor)
PropertyStub(fieldName, pointedType, kind, origin = origin)
}
}
} catch (e: Throwable) {
null
}
}
val bitFields: List<PropertyStub> = def.bitFields.map { field ->
val typeMirror = context.mirror(field.type)
val typeInfo = typeMirror.info
val kotlinType = typeMirror.argType
val signed = field.type.isIntegerTypeSigned()
val fieldName = mangleSimple(field.name)
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
val readBits = PropertyAccessor.Getter.ReadBits(field.offset, field.size, signed)
val writeBits = PropertyAccessor.Setter.WriteBits(field.offset, field.size)
context.bridgeComponentsBuilder.getterToBridgeInfo[readBits] = BridgeGenerationInfo("", typeInfo)
context.bridgeComponentsBuilder.setterToBridgeInfo[writeBits] = BridgeGenerationInfo("", typeInfo)
PropertyStub.Kind.Var(readBits, writeBits)
}
GenerationMode.METADATA -> {
val readBits = PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.BitField(field.offset, field.size)))
val writeBits = PropertyAccessor.Setter.ExternalSetter(listOf(AnnotationStub.CStruct.BitField(field.offset, field.size)))
PropertyStub.Kind.Var(readBits, writeBits)
}
}
PropertyStub(fieldName, kotlinType.toStubIrType(), kind, origin = StubOrigin.StructMember(field))
}
val superClass = context.platform.getRuntimeType("CStructVar")
require(superClass is ClassifierStubType)
val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr"))
val origin = StubOrigin.Struct(decl)
val primaryConstructor = ConstructorStub(
parameters = listOf(rawPtrConstructorParam),
isPrimary = true,
annotations = emptyList(),
origin = origin
)
val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam)))
val companionSuper = superClass.nested("Type")
val typeSize = listOf(IntegralConstantStub(def.size, 4, true), IntegralConstantStub(def.align.toLong(), 4, true))
val companionSuperInit = SuperClassInit(companionSuper, typeSize)
val companionClassifier = classifier.nested("Companion")
val annotation = AnnotationStub.CStruct.VarType(def.size, def.align).takeIf {
context.generationMode == GenerationMode.METADATA
}
val companion = ClassStub.Companion(
companionClassifier,
superClassInit = companionSuperInit,
annotations = listOfNotNull(annotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion)
)
return listOf(ClassStub.Simple(
classifier,
origin = origin,
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
constructors = listOf(primaryConstructor),
methods = emptyList(),
modality = ClassStubModality.NONE,
annotations = listOfNotNull(structAnnotation),
superClassInit = superClassInit,
companion = companion
))
}
private fun getArrayLength(type: ArrayType): Long {
val unwrappedElementType = type.elemType.unwrapTypedefs()
val elementLength = if (unwrappedElementType is ArrayType) {
getArrayLength(unwrappedElementType)
} else {
1L
}
val elementCount = when (type) {
is ConstArrayType -> type.length
is IncompleteArrayType -> 0L
else -> TODO(type.toString())
}
return elementLength * elementCount
}
private tailrec fun Type.isIntegerTypeSigned(): Boolean = when (this) {
is IntegerType -> this.isSigned
is BoolType -> false
is EnumType -> this.def.baseType.isIntegerTypeSigned()
is Typedef -> this.def.aliased.isIntegerTypeSigned()
else -> error(this)
}
/**
* Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct.
*/
private fun generateForwardStruct(s: StructDecl): List<StubIrElement> = when (context.platform) {
KotlinPlatform.JVM -> {
val classifier = context.getKotlinClassForPointed(s)
val superClass = context.platform.getRuntimeType("COpaque")
val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr"))
val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam)))
val origin = StubOrigin.Struct(s)
val primaryConstructor = ConstructorStub(listOf(rawPtrConstructorParam), emptyList(), isPrimary = true, origin = origin)
listOf(ClassStub.Simple(
classifier,
ClassStubModality.NONE,
constructors = listOf(primaryConstructor),
superClassInit = superClassInit,
origin = origin))
}
KotlinPlatform.NATIVE -> emptyList()
}
}
internal class EnumStubBuilder(
override val context: StubsBuildingContext,
private val enumDef: EnumDef
) : StubElementBuilder {
private val classifier = (context.mirror(EnumType(enumDef)) as TypeMirror.ByValue).valueType.classifier
private val baseTypeMirror = context.mirror(enumDef.baseType)
private val baseType = baseTypeMirror.argType.toStubIrType()
override fun build(): List<StubIrElement> {
if (!context.isStrictEnum(enumDef)) {
return generateEnumAsConstants(enumDef)
}
val constructorParameter = FunctionParameterStub("value", baseType)
val valueProperty = PropertyStub(
name = "value",
type = baseType,
kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.GetConstructorParameter(constructorParameter)),
modality = MemberStubModality.OPEN,
origin = StubOrigin.Synthetic.EnumValueField(enumDef),
isOverride = true)
val canonicalsByValue = enumDef.constants
.groupingBy { it.value }
.reduce { _, accumulator, element ->
if (element.isMoreCanonicalThan(accumulator)) {
element
} else {
accumulator
}
}
val (canonicalConstants, aliasConstants) = enumDef.constants.partition { canonicalsByValue[it.value] == it }
val canonicalEntriesWithAliases = canonicalConstants
.sortedBy { it.value } // TODO: Is it stable enough?
.mapIndexed { index, constant ->
val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value)
?: error("Cannot create enum value ${constant.value} of type ${enumDef.baseType}")
val entry = EnumEntryStub(mangleSimple(constant.name), literal, StubOrigin.EnumEntry(constant), index)
val aliases = aliasConstants
.filter { it.value == constant.value }
.map { constructAliasProperty(it, entry) }
entry to aliases
}
val origin = StubOrigin.Enum(enumDef)
val primaryConstructor = ConstructorStub(
parameters = listOf(constructorParameter),
annotations = emptyList(),
isPrimary = true,
origin = origin,
visibility = VisibilityModifier.PRIVATE
)
val byValueFunction = FunctionStub(
name = "byValue",
returnType = ClassifierStubType(classifier),
parameters = listOf(FunctionParameterStub("value", baseType)),
origin = StubOrigin.Synthetic.EnumByValue(enumDef),
receiver = null,
modality = MemberStubModality.FINAL,
annotations = listOf(AnnotationStub.Deprecated.deprecatedCEnumByValue)
)
val companion = ClassStub.Companion(
classifier = classifier.nested("Companion"),
properties = canonicalEntriesWithAliases.flatMap { it.second },
methods = listOf(byValueFunction)
)
val enumVarClass = constructEnumVarClass().takeIf { context.generationMode == GenerationMode.METADATA }
val kotlinEnumType = ClassifierStubType(Classifier.topLevel("kotlin", "Enum"),
listOf(TypeArgumentStub(ClassifierStubType(classifier))))
val enum = ClassStub.Enum(
classifier = classifier,
superClassInit = SuperClassInit(kotlinEnumType),
entries = canonicalEntriesWithAliases.map { it.first },
companion = companion,
constructors = listOf(primaryConstructor),
properties = listOf(valueProperty),
origin = origin,
interfaces = listOf(context.platform.getRuntimeType("CEnum")),
childrenClasses = listOfNotNull(enumVarClass)
)
context.bridgeComponentsBuilder.enumToTypeMirror[enum] = baseTypeMirror
return listOf(enum)
}
private fun constructAliasProperty(enumConstant: EnumConstant, entry: EnumEntryStub): PropertyStub {
val aliasAnnotation = AnnotationStub.CEnumEntryAlias(entry.name)
.takeIf { context.generationMode == GenerationMode.METADATA }
return PropertyStub(
enumConstant.name,
ClassifierStubType(classifier),
kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.GetEnumEntry(entry)),
origin = StubOrigin.EnumEntry(enumConstant),
annotations = listOfNotNull(aliasAnnotation)
)
}
private fun constructEnumVarClass(): ClassStub.Simple {
val enumVarClassifier = classifier.nested("Var")
val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr"))
val superClass = context.platform.getRuntimeType("CEnumVar")
require(superClass is ClassifierStubType)
val primaryConstructor = ConstructorStub(
parameters = listOf(rawPtrConstructorParam),
isPrimary = true,
annotations = emptyList(),
origin = StubOrigin.Synthetic.DefaultConstructor
)
val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam)))
val baseIntegerTypeSize = when (val unwrappedType = enumDef.baseType.unwrapTypedefs()) {
is IntegerType -> unwrappedType.size.toLong()
CharType -> 1L
else -> error("Incorrect base type for enum ${classifier.fqName}")
}
val typeSize = IntegralConstantStub(baseIntegerTypeSize, 4, true)
val companionSuper = (context.platform.getRuntimeType("CPrimitiveVar") as ClassifierStubType).nested("Type")
val varSizeAnnotation = AnnotationStub.CEnumVarTypeSize(baseIntegerTypeSize.toInt())
.takeIf { context.generationMode == GenerationMode.METADATA }
val companion = ClassStub.Companion(
classifier = enumVarClassifier.nested("Companion"),
superClassInit = SuperClassInit(companionSuper, listOf(typeSize)),
annotations = listOfNotNull(varSizeAnnotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion)
)
val valueProperty = PropertyStub(
name = "value",
type = ClassifierStubType(classifier),
kind = PropertyStub.Kind.Var(
PropertyAccessor.Getter.ExternalGetter(),
PropertyAccessor.Setter.ExternalSetter()
),
origin = StubOrigin.Synthetic.EnumVarValueField(enumDef)
)
return ClassStub.Simple(
classifier = enumVarClassifier,
constructors = listOf(primaryConstructor),
superClassInit = superClassInit,
companion = companion,
modality = ClassStubModality.NONE,
origin = StubOrigin.VarOf(StubOrigin.Enum(enumDef)),
properties = listOf(valueProperty)
)
}
private fun EnumConstant.isMoreCanonicalThan(other: EnumConstant): Boolean = with(other.name.toLowerCase()) {
contains("min") || contains("max") ||
contains("first") || contains("last") ||
contains("begin") || contains("end")
}
/**
* Produces to [out] the Kotlin definitions for given enum which shouldn't be represented as Kotlin enum.
*/
private fun generateEnumAsConstants(enumDef: EnumDef): List<StubIrElement> {
// TODO: if this enum defines e.g. a type of struct field, then it should be generated inside the struct class
// to prevent name clashing
val entries = mutableListOf<PropertyStub>()
val typealiases = mutableListOf<TypealiasStub>()
val constants = enumDef.constants.filter {
// Macro "overrides" the original enum constant.
it.name !in context.macroConstantsByName
}
val kotlinType: KotlinType
val baseKotlinType = context.mirror(enumDef.baseType).argType
val meta = if (enumDef.isAnonymous) {
kotlinType = baseKotlinType
StubContainerMeta(textAtStart = if (constants.isNotEmpty()) "// ${enumDef.spelling}:" else "")
} else {
val typeMirror = context.mirror(EnumType(enumDef))
if (typeMirror !is TypeMirror.ByValue) {
error("unexpected enum type mirror: $typeMirror")
}
val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueType)
val varTypeClassifier = typeMirror.pointedType.classifier
val valueTypeClassifier = typeMirror.valueType.classifier
val origin = StubOrigin.Enum(enumDef)
typealiases += TypealiasStub(varTypeClassifier, varTypeName.toStubIrType(), StubOrigin.VarOf(origin))
typealiases += TypealiasStub(valueTypeClassifier, baseKotlinType.toStubIrType(), origin)
kotlinType = typeMirror.valueType
StubContainerMeta()
}
for (constant in constants) {
val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value) ?: continue
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal)
PropertyStub.Kind.Val(getter)
}
GenerationMode.METADATA -> {
PropertyStub.Kind.Constant(literal)
}
}
entries += PropertyStub(
constant.name,
kotlinType.toStubIrType(),
kind,
MemberStubModality.FINAL,
null,
origin = StubOrigin.EnumEntry(constant)
)
}
val container = SimpleStubContainer(
meta,
properties = entries.toList(),
typealiases = typealiases.toList()
)
return listOf(container)
}
}
internal class FunctionStubBuilder(
override val context: StubsBuildingContext,
private val func: FunctionDecl,
private val skipOverloads: Boolean = false
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val platform = context.platform
val parameters = mutableListOf<FunctionParameterStub>()
var hasStableParameterNames = true
func.parameters.forEachIndexed { index, parameter ->
val parameterName = parameter.name.let {
if (it == null || it.isEmpty()) {
hasStableParameterNames = false
"arg$index"
} else {
it
}
}
val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type)
parameters += when {
representCFunctionParameterAsString(func, parameter.type) -> {
val annotations = when (platform) {
KotlinPlatform.JVM -> emptyList()
KotlinPlatform.NATIVE -> listOf(AnnotationStub.CCall.CString)
}
val type = KotlinTypes.string.makeNullable().toStubIrType()
val functionParameterStub = FunctionParameterStub(parameterName, type, annotations)
context.bridgeComponentsBuilder.cStringParameters += functionParameterStub
functionParameterStub
}
representCFunctionParameterAsWString(func, parameter.type) -> {
val annotations = when (platform) {
KotlinPlatform.JVM -> emptyList()
KotlinPlatform.NATIVE -> listOf(AnnotationStub.CCall.WCString)
}
val type = KotlinTypes.string.makeNullable().toStubIrType()
val functionParameterStub = FunctionParameterStub(parameterName, type, annotations)
context.bridgeComponentsBuilder.wCStringParameters += functionParameterStub
functionParameterStub
}
representAsValuesRef != null -> {
FunctionParameterStub(parameterName, representAsValuesRef.toStubIrType())
}
else -> {
val mirror = context.mirror(parameter.type)
val type = mirror.argType.toStubIrType()
FunctionParameterStub(parameterName, type)
}
}
}
val returnType = if (func.returnsVoid()) {
KotlinTypes.unit
} else {
context.mirror(func.returnType).argType
}.toStubIrType()
if (skipOverloads && context.isOverloading(func))
return emptyList()
val annotations: List<AnnotationStub>
val mustBeExternal: Boolean
if (platform == KotlinPlatform.JVM) {
annotations = emptyList()
mustBeExternal = false
} else {
if (func.isVararg) {
val type = KotlinTypes.any.makeNullable().toStubIrType()
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
annotations = listOf(AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${func.name}"))
mustBeExternal = true
}
val functionStub = FunctionStub(
func.name,
returnType,
parameters.toList(),
StubOrigin.Function(func),
annotations,
mustBeExternal,
null,
MemberStubModality.FINAL,
hasStableParameterNames = hasStableParameterNames
)
return listOf(functionStub)
}
private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType
private fun representCFunctionParameterAsValuesRef(type: Type): KotlinType? {
val pointeeType = when (type) {
is PointerType -> type.pointeeType
is ArrayType -> type.elemType
else -> return null
}
val unwrappedPointeeType = pointeeType.unwrapTypedefs()
if (unwrappedPointeeType is VoidType) {
// Represent `void*` as `CValuesRef<*>?`:
return KotlinTypes.cValuesRef.typeWith(StarProjection).makeNullable()
}
if (unwrappedPointeeType is FunctionType) {
// Don't represent function pointer as `CValuesRef<T>?` currently:
return null
}
if (unwrappedPointeeType is ArrayType) {
return representCFunctionParameterAsValuesRef(pointeeType)
}
return KotlinTypes.cValuesRef.typeWith(context.mirror(pointeeType).pointedType).makeNullable()
}
private val platformWStringTypes = setOf("LPCWSTR")
private val noStringConversion: Set<String>
get() = context.configuration.noStringConversion
private fun Type.isAliasOf(names: Set<String>): Boolean {
var type = this
while (type is Typedef) {
if (names.contains(type.def.name)) return true
type = type.def.aliased
}
return false
}
private fun representCFunctionParameterAsString(function: FunctionDecl, type: Type): Boolean {
val unwrappedType = type.unwrapTypedefs()
return unwrappedType is PointerType && unwrappedType.pointeeIsConst &&
unwrappedType.pointeeType.unwrapTypedefs() == CharType &&
!noStringConversion.contains(function.name)
}
// We take this approach as generic 'const short*' shall not be used as String.
private fun representCFunctionParameterAsWString(function: FunctionDecl, type: Type) = type.isAliasOf(platformWStringTypes)
&& !noStringConversion.contains(function.name)
}
internal class GlobalStubBuilder(
override val context: StubsBuildingContext,
private val global: GlobalDecl
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val mirror = context.mirror(global.type)
val unwrappedType = global.type.unwrapTypedefs()
val origin = StubOrigin.Global(global)
val kotlinType: KotlinType
val kind: PropertyStub.Kind
if (unwrappedType is ArrayType) {
kotlinType = (mirror as TypeMirror.ByValue).valueType
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val extra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[it] = extra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
kind = PropertyStub.Kind.Val(getter)
} else {
when (mirror) {
is TypeMirror.ByValue -> {
kotlinType = mirror.argType
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val getterExtra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
kind = if (global.isConst) {
PropertyStub.Kind.Val(getter)
} else {
val setter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Setter.SimpleSetter().also {
val setterExtra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_setter")
PropertyAccessor.Setter.ExternalSetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
PropertyStub.Kind.Var(getter, setter)
}
}
is TypeMirror.ByRef -> {
kotlinType = mirror.pointedType
val getter = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyAccessor.Getter.InterpretPointed(global.name, kotlinType.toStubIrType())
}
GenerationMode.METADATA -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global, passViaPointer = true)
}
}
}
kind = PropertyStub.Kind.Val(getter)
}
}
}
return listOf(PropertyStub(global.name, kotlinType.toStubIrType(), kind, origin = origin))
}
}
internal class TypedefStubBuilder(
override val context: StubsBuildingContext,
private val typedefDef: TypedefDef
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val mirror = context.mirror(Typedef(typedefDef))
val baseMirror = context.mirror(typedefDef.aliased)
val varType = mirror.pointedType.classifier
val origin = StubOrigin.TypeDef(typedefDef)
return when (baseMirror) {
is TypeMirror.ByValue -> {
val valueType = (mirror as TypeMirror.ByValue).valueType
val varTypeAliasee = mirror.info.constructPointedType(valueType)
val valueTypeAliasee = baseMirror.valueType
listOf(
TypealiasStub(varType, varTypeAliasee.toStubIrType(), StubOrigin.VarOf(origin)),
TypealiasStub(valueType.classifier, valueTypeAliasee.toStubIrType(), origin)
)
}
is TypeMirror.ByRef -> {
val varTypeAliasee = baseMirror.pointedType
listOf(TypealiasStub(varType, varTypeAliasee.toStubIrType(), origin))
}
}
}
}
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol
private val StubOrigin.ObjCMethod.isOptional: Boolean
get() = container is ObjCProtocol && method.isOptional
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
this.origin.isOptional
val StubContainer.isInterface: Boolean
get() = if (this is ClassStub.Simple) {
modality == ClassStubModality.INTERFACE
} else {
false
}
/**
* Compute which names will be declared by [StubContainer] in the given [pkgName]
*/
fun StubContainer.computeNamesToBeDeclared(pkgName: String): List<String> {
fun checkPackageCorrectness(classifier: Classifier) {
assert(classifier.pkg == pkgName) {
"""Wrong classifier package.
|Expected: $pkgName
|Got: ${classifier.pkg}
|""".trimMargin()
}
}
val classNames = classes.mapNotNull {
when (it) {
is ClassStub.Simple -> it.classifier
is ClassStub.Companion -> null
is ClassStub.Enum -> it.classifier
}
}.onEach { checkPackageCorrectness(it) }.map { it.topLevelName }
val typealiasNames = typealiases
.onEach { checkPackageCorrectness(it.alias) }
.map { it.alias.topLevelName }
val namesFromNestedContainers = simpleContainers
.flatMap { it.computeNamesToBeDeclared(pkgName) }
return classNames + typealiasNames + namesFromNestedContainers
}
val StubContainer.defaultMemberModality: MemberStubModality
get() = when (this) {
is SimpleStubContainer -> MemberStubModality.FINAL
is ClassStub.Simple -> if (this.modality == ClassStubModality.INTERFACE) {
MemberStubModality.ABSTRACT
} else {
MemberStubModality.FINAL
}
is ClassStub.Companion -> MemberStubModality.FINAL
is ClassStub.Enum -> MemberStubModality.FINAL
}
/**
* Returns constructor that should be rendered in class header.
*/
val ClassStub.explicitPrimaryConstructor: ConstructorStub?
get() = functions.filterIsInstance<ConstructorStub>().firstOrNull(ConstructorStub::isPrimary)
fun ClassStub.nestedName(): String =
classifier.getRelativeFqName().substringAfterLast('.')
fun ConstantStub.determineConstantAnnotationClassifier(): Classifier = when (this) {
is StringConstantStub -> "String"
is IntegralConstantStub -> when (size) {
1 -> if (isSigned) "Byte" else "UByte"
2 -> if (isSigned) "Short" else "UShort"
4 -> if (isSigned) "Int" else "UInt"
8 -> if (isSigned) "Long" else "ULong"
else -> error("Integral constant with unexpected size of $size.")
}
is DoubleConstantStub -> when (size) {
4 -> "Float"
8 -> "Double"
else -> error("Real constant with unexpected size of $size.")
}
}.let { Classifier.topLevel(cinteropInternalPackage, "ConstantValue").nested(it) }
/**
* Returns the original name of the given type.
*/
val StubType.underlyingTypeFqName: String
get() = when (this) {
is ClassifierStubType -> classifier.fqName
is AbbreviatedType -> underlyingType.underlyingTypeFqName
is FunctionalType -> classifier.fqName
is TypeParameterType -> name
}
@@ -0,0 +1,600 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import kotlinx.metadata.*
import kotlinx.metadata.klib.*
import org.jetbrains.kotlin.metadata.serialization.Interner
import org.jetbrains.kotlin.utils.addIfNotNull
class StubIrMetadataEmitter(
private val context: StubIrContext,
private val builderResult: StubIrBuilderResult,
private val moduleName: String,
private val bridgeBuilderResult: BridgeBuilderResult
) {
fun emit(): KlibModuleMetadata {
val annotations = emptyList<KmAnnotation>()
val fragments = emitModuleFragments()
return KlibModuleMetadata(moduleName, fragments, annotations)
}
private fun emitModuleFragments(): List<KmModuleFragment> =
ModuleMetadataEmitter(
context.configuration.pkgName,
builderResult.stubs,
bridgeBuilderResult
).emit().let { kmModuleFragment ->
// We need to create module fragment for each part of package name.
val pkgName = context.configuration.pkgName
val fakePackages = pkgName.mapIndexedNotNull { idx, char ->
if (char == '.') idx else null
}.map { dotPosition ->
KmModuleFragment().also {
it.fqName = pkgName.substring(0, dotPosition)
}
}
fakePackages + kmModuleFragment
}
}
/**
* Translates single [StubContainer] to [KmModuleFragment].
*/
internal class ModuleMetadataEmitter(
private val packageFqName: String,
private val module: SimpleStubContainer,
private val bridgeBuilderResult: BridgeBuilderResult
) {
fun emit(): KmModuleFragment {
val context = VisitingContext(bridgeBuilderResult = bridgeBuilderResult)
val elements = KmElements(visitor.visitSimpleStubContainer(module, context))
return writeModule(elements)
}
private fun writeModule(elements: KmElements) = KmModuleFragment().also { km ->
km.fqName = packageFqName
km.classes += elements.classes.toList()
km.className += elements.classes.map(KmClass::name)
km.pkg = writePackage(elements)
}
private fun writePackage(elements: KmElements) = KmPackage().also { km ->
km.fqName = packageFqName
km.typeAliases += elements.typeAliases.toList()
km.properties += elements.properties.toList()
km.functions += elements.functions.toList()
}
/**
* StubIr translation result. Since Km* classes don't have common hierarchy we need
* to use list of Any.
*/
private class KmElements(result: List<Any>) {
val classes: List<KmClass> = result.filterIsInstance<List<KmClass>>().flatten()
val properties: List<KmProperty> = result.filterIsInstance<KmProperty>()
val typeAliases: List<KmTypeAlias> = result.filterIsInstance<KmTypeAlias>()
val functions: List<KmFunction> = result.filterIsInstance<KmFunction>()
val constructors: List<KmConstructor> = result.filterIsInstance<KmConstructor>()
}
/**
* Used to pass data between parents and children when visiting StubIr elements.
*/
private data class VisitingContext(
val container: StubContainer? = null,
val typeParametersInterner: Interner<TypeParameterStub> = Interner(),
val bridgeBuilderResult: BridgeBuilderResult
) {
inline fun <R> withMappingExtensions(block: MappingExtensions.() -> R) =
with (MappingExtensions(typeParametersInterner, bridgeBuilderResult), block)
}
private fun isTopLevelContainer(container: StubContainer?): Boolean =
container == null
private fun getPropertyNameInScope(property: PropertyStub, container: StubContainer?): String =
if (isTopLevelContainer(container)) {
getTopLevelPropertyDeclarationName(bridgeBuilderResult.kotlinFile, property)
} else {
property.name
}
private val visitor = object : StubIrVisitor<VisitingContext, Any?> {
override fun visitClass(element: ClassStub, data: VisitingContext): List<KmClass> {
val classVisitingContext = VisitingContext(
container = element,
typeParametersInterner = Interner(data.typeParametersInterner),
bridgeBuilderResult = data.bridgeBuilderResult
)
val children = element.children + if (element is ClassStub.Companion) {
listOf(ConstructorStub(isPrimary = true, visibility = VisibilityModifier.PRIVATE, origin = StubOrigin.Synthetic.DefaultConstructor))
} else emptyList()
val elements = KmElements(children.mapNotNull { it.accept(this, classVisitingContext) })
val kmClass = data.withMappingExtensions {
KmClass().also { km ->
element.annotations.mapTo(km.annotations) { it.map() }
km.flags = element.flags
km.name = element.classifier.fqNameSerialized
element.superClassInit?.let { km.supertypes += it.type.map() }
element.interfaces.mapTo(km.supertypes) { it.map() }
element.classes.mapTo(km.nestedClasses) { it.nestedName() }
km.typeAliases += elements.typeAliases.toList()
km.properties += elements.properties.toList()
km.functions += elements.functions.toList()
km.constructors += elements.constructors.toList()
km.companionObject = element.companion?.nestedName()
if (element is ClassStub.Enum) {
element.entries.mapTo(km.klibEnumEntries) { mapEnumEntry(it, classVisitingContext) }
}
}
}
// Metadata stores classes as flat list.
return listOf(kmClass) + elements.classes
}
override fun visitTypealias(element: TypealiasStub, data: VisitingContext): KmTypeAlias =
data.withMappingExtensions {
KmTypeAlias(element.flags, element.alias.topLevelName).also { km ->
km.underlyingType = element.aliasee.map(shouldExpandTypeAliases = false)
km.expandedType = element.aliasee.map()
}
}
override fun visitFunction(element: FunctionStub, data: VisitingContext) =
data.withMappingExtensions {
val function = if (bridgeBuilderResult.nativeBridges.isSupported(element)) {
element
} else {
element.copy(
external = false,
annotations = listOf(AnnotationStub.Deprecated.unableToImport)
)
}
KmFunction(function.flags, function.name).also { km ->
km.receiverParameterType = function.receiver?.type?.map()
function.typeParameters.mapTo(km.typeParameters) { it.map() }
function.parameters.mapTo(km.valueParameters) { it.map() }
function.annotations.mapTo(km.annotations) { it.map() }
km.returnType = function.returnType.map()
}
}
override fun visitProperty(element: PropertyStub, data: VisitingContext) =
data.withMappingExtensions {
val property = when (val bridgeSupportedKind = element.bridgeSupportedKind) {
null -> element.copy(
kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.SimpleGetter()),
annotations = listOf(AnnotationStub.Deprecated.unableToImport)
)
element.kind -> element
else -> element.copy(kind = bridgeSupportedKind)
}
val name = getPropertyNameInScope(property, data.container)
KmProperty(property.flags, name, property.getterFlags, property.setterFlags).also { km ->
property.annotations.mapTo(km.annotations) { it.map() }
km.receiverParameterType = property.receiverType?.map()
km.returnType = property.type.map()
val kind = property.kind
if (kind is PropertyStub.Kind.Var) {
kind.setter.annotations.mapTo(km.setterAnnotations) { it.map() }
// TODO: Maybe it's better to explicitly add setter parameter in stub.
km.setterParameter = FunctionParameterStub("value", property.type).map()
}
km.getterAnnotations += when (kind) {
is PropertyStub.Kind.Val -> kind.getter.annotations.map { it.map() }
is PropertyStub.Kind.Var -> kind.getter.annotations.map { it.map() }
is PropertyStub.Kind.Constant -> emptyList()
}
if (kind is PropertyStub.Kind.Constant) {
km.compileTimeValue = kind.constant.mapToAnnotationArgument()
}
}
}
override fun visitConstructor(constructorStub: ConstructorStub, data: VisitingContext) =
data.withMappingExtensions {
KmConstructor(constructorStub.flags).apply {
constructorStub.parameters.mapTo(valueParameters, { it.map() })
constructorStub.annotations.mapTo(annotations, { it.map() })
}
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: VisitingContext) {
// TODO("not implemented")
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: VisitingContext): List<Any> =
simpleStubContainer.children.mapNotNull { it.accept(this, data) } +
simpleStubContainer.simpleContainers.flatMap { visitSimpleStubContainer(it, data) }
private fun mapEnumEntry(enumEntry: EnumEntryStub, data: VisitingContext): KlibEnumEntry =
data.withMappingExtensions {
KlibEnumEntry(
name = enumEntry.name,
ordinal = enumEntry.ordinal,
annotations = mutableListOf(enumEntry.constant.mapToConstantAnnotation())
)
}
}
}
/**
* Collection of extension functions that simplify translation of
* StubIr elements to Kotlin Metadata.
*/
private class MappingExtensions(
private val typeParametersInterner: Interner<TypeParameterStub>,
private val bridgeBuilderResult: BridgeBuilderResult
) {
private fun flagsOfNotNull(vararg flags: Flag?): Flags =
flagsOf(*listOfNotNull(*flags).toTypedArray())
private fun <K, V> mapOfNotNull(vararg entries: Pair<K, V>?): Map<K, V> =
listOfNotNull(*entries).toMap()
private val VisibilityModifier.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC.takeIf { this == VisibilityModifier.PUBLIC },
Flag.IS_PROTECTED.takeIf { this == VisibilityModifier.PROTECTED },
Flag.IS_INTERNAL.takeIf { this == VisibilityModifier.INTERNAL },
Flag.IS_PRIVATE.takeIf { this == VisibilityModifier.PRIVATE }
)
private val MemberStubModality.flags: Flags
get() = flagsOfNotNull(
Flag.IS_FINAL.takeIf { this == MemberStubModality.FINAL },
Flag.IS_OPEN.takeIf { this == MemberStubModality.OPEN },
Flag.IS_ABSTRACT.takeIf { this == MemberStubModality.ABSTRACT }
)
val FunctionStub.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC,
Flag.Function.IS_EXTERNAL.takeIf { this.external },
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.Function.HAS_NON_STABLE_PARAMETER_NAMES.takeIf { !this.hasStableParameterNames }
) or modality.flags
val Classifier.fqNameSerialized: String
get() = buildString {
if (pkg.isNotEmpty()) {
append(pkg.replace('.', '/'))
append('/')
}
// Nested classes should dot-separated.
append(getRelativeFqName(asSimpleName = false))
}
val PropertyStub.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC,
Flag.Property.IS_DECLARATION,
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.Property.HAS_CONSTANT.takeIf { kind is PropertyStub.Kind.Constant },
Flag.Property.HAS_GETTER,
Flag.Property.HAS_SETTER.takeIf { kind is PropertyStub.Kind.Var },
when (kind) {
is PropertyStub.Kind.Val -> null
is PropertyStub.Kind.Var -> Flag.Property.IS_VAR
is PropertyStub.Kind.Constant -> Flag.Property.IS_CONST
}
) or modality.flags
val PropertyStub.getterFlags: Flags
get() = when (val kind = kind) {
is PropertyStub.Kind.Val -> kind.getter.flags(modality)
is PropertyStub.Kind.Var -> kind.getter.flags(modality)
is PropertyStub.Kind.Constant -> kind.flags
}
val PropertyStub.Kind.Constant.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC,
Flag.IS_FINAL
)
private fun PropertyAccessor.Getter.flags(propertyModality: MemberStubModality): Flags =
flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.IS_PUBLIC,
Flag.PropertyAccessor.IS_NOT_DEFAULT,
Flag.PropertyAccessor.IS_EXTERNAL.takeIf { this is PropertyAccessor.Getter.ExternalGetter }
) or propertyModality.flags
val PropertyStub.setterFlags: Flags
get() = when (val kind = kind) {
is PropertyStub.Kind.Var -> kind.setter.flags(modality)
else -> flagsOf()
}
private fun PropertyAccessor.Setter.flags(propertyModality: MemberStubModality): Flags =
flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.IS_PUBLIC,
Flag.PropertyAccessor.IS_NOT_DEFAULT,
Flag.PropertyAccessor.IS_EXTERNAL.takeIf { this is PropertyAccessor.Setter.ExternalSetter }
) or propertyModality.flags
val StubType.flags: Flags
get() = flagsOfNotNull(
Flag.Type.IS_NULLABLE.takeIf { nullable }
)
val TypealiasStub.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC
)
val FunctionParameterStub.flags: Flags
get() = flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() }
)
val ClassStub.flags: Flags
get() = flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.IS_PUBLIC,
Flag.IS_OPEN.takeIf { this is ClassStub.Simple && modality == ClassStubModality.OPEN },
Flag.IS_FINAL.takeIf { this is ClassStub.Simple && modality == ClassStubModality.NONE },
Flag.IS_ABSTRACT.takeIf { this is ClassStub.Simple
&& (modality == ClassStubModality.ABSTRACT || modality == ClassStubModality.INTERFACE) },
Flag.Class.IS_INTERFACE.takeIf { this is ClassStub.Simple && modality == ClassStubModality.INTERFACE },
Flag.Class.IS_COMPANION_OBJECT.takeIf { this is ClassStub.Companion },
Flag.Class.IS_CLASS.takeIf { this is ClassStub.Simple && modality != ClassStubModality.INTERFACE },
Flag.Class.IS_ENUM_CLASS.takeIf { this is ClassStub.Enum }
)
val ConstructorStub.flags: Flags
get() = flagsOfNotNull(
Flag.Constructor.IS_SECONDARY.takeIf { !isPrimary },
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() }
) or visibility.flags
fun AnnotationStub.map(): KmAnnotation {
fun Pair<String, String>.asAnnotationArgument() =
(first to KmAnnotationArgument.StringValue(second)).takeIf { second.isNotEmpty() }
fun replaceWith(replaceWith: String) = KmAnnotationArgument.AnnotationValue(KmAnnotation(
Classifier.topLevel("kotlin", "ReplaceWith").fqNameSerialized,
mapOfNotNull(
"imports" to KmAnnotationArgument.ArrayValue(emptyList()),
("expression" to replaceWith).asAnnotationArgument()
)
))
fun deprecationLevel(level: DeprecationLevel) = KmAnnotationArgument.EnumValue(
Classifier.topLevel("kotlin", "DeprecationLevel").fqNameSerialized,
level.name
)
val args = when (this) {
AnnotationStub.ObjC.ConsumesReceiver -> emptyMap()
AnnotationStub.ObjC.ReturnsRetained -> emptyMap()
is AnnotationStub.ObjC.Method -> mapOfNotNull(
("selector" to selector).asAnnotationArgument(),
("encoding" to encoding).asAnnotationArgument(),
("isStret" to KmAnnotationArgument.BooleanValue(isStret))
)
is AnnotationStub.ObjC.Factory -> mapOfNotNull(
("selector" to selector).asAnnotationArgument(),
("encoding" to encoding).asAnnotationArgument(),
("isStret" to KmAnnotationArgument.BooleanValue(isStret))
)
AnnotationStub.ObjC.Consumed -> emptyMap()
is AnnotationStub.ObjC.Constructor -> mapOfNotNull(
("designated" to KmAnnotationArgument.BooleanValue(designated)),
("initSelector" to selector).asAnnotationArgument()
)
is AnnotationStub.ObjC.ExternalClass -> mapOfNotNull(
("protocolGetter" to protocolGetter).asAnnotationArgument(),
("binaryName" to binaryName).asAnnotationArgument()
)
AnnotationStub.CCall.CString -> emptyMap()
AnnotationStub.CCall.WCString -> emptyMap()
is AnnotationStub.CCall.Symbol -> mapOfNotNull(
("id" to symbolName).asAnnotationArgument()
)
is AnnotationStub.CStruct -> mapOfNotNull(
("spelling" to struct).asAnnotationArgument()
)
is AnnotationStub.CNaturalStruct ->
error("@CNaturalStruct should not be used for Kotlin/Native interop")
is AnnotationStub.CLength -> mapOfNotNull(
"value" to KmAnnotationArgument.LongValue(length)
)
is AnnotationStub.Deprecated -> mapOfNotNull(
("message" to message).asAnnotationArgument(),
("replaceWith" to replaceWith(replaceWith)),
("level" to deprecationLevel(level))
)
is AnnotationStub.CEnumEntryAlias -> mapOfNotNull(
("entryName" to entryName).asAnnotationArgument()
)
is AnnotationStub.CEnumVarTypeSize -> mapOfNotNull(
("size" to KmAnnotationArgument.IntValue(size))
)
is AnnotationStub.CStruct.MemberAt -> mapOfNotNull(
("offset" to KmAnnotationArgument.LongValue(offset))
)
is AnnotationStub.CStruct.ArrayMemberAt -> mapOfNotNull(
("offset" to KmAnnotationArgument.LongValue(offset))
)
is AnnotationStub.CStruct.BitField -> mapOfNotNull(
("offset" to KmAnnotationArgument.LongValue(offset)),
("size" to KmAnnotationArgument.IntValue(size))
)
is AnnotationStub.CStruct.VarType -> mapOfNotNull(
("size" to KmAnnotationArgument.LongValue(size)),
("align" to KmAnnotationArgument.IntValue(align))
)
}
return KmAnnotation(classifier.fqNameSerialized, args)
}
/**
* @param shouldExpandTypeAliases describes how should we write type aliases.
* If [shouldExpandTypeAliases] is true then type alias-based types are written as
* ```
* Type {
* abbreviatedType = AbbreviatedType.abbreviatedClassifier
* classifier = AbbreviatedType.underlyingType
* arguments = AbbreviatedType.underlyingType.typeArguments
* }
* ```
* So we basically replacing type alias with underlying class.
* Otherwise:
* ```
* Type {
* classifier = AbbreviatedType.abbreviatedClassifier
* }
* ```
* As of 25 Nov 2019, the latter form is used only for KmTypeAlias.underlyingType.
*/
// TODO: Add caching if needed.
fun StubType.map(shouldExpandTypeAliases: Boolean = true): KmType = when (this) {
is AbbreviatedType -> {
val typeAliasClassifier = KmClassifier.TypeAlias(abbreviatedClassifier.fqNameSerialized)
val typeArguments = typeArguments.map { it.map(shouldExpandTypeAliases) }
val abbreviatedType = KmType(flags).also { km ->
km.classifier = typeAliasClassifier
km.arguments += typeArguments
}
if (shouldExpandTypeAliases) {
// Abbreviated and expanded types have the same nullability.
KmType(flags).also { km ->
km.abbreviatedType = abbreviatedType
val kmUnderlyingType = underlyingType.map(true)
km.arguments += kmUnderlyingType.arguments
km.classifier = kmUnderlyingType.classifier
}
} else {
abbreviatedType
}
}
is ClassifierStubType -> KmType(flags).also { km ->
typeArguments.mapTo(km.arguments) { it.map(shouldExpandTypeAliases) }
km.classifier = KmClassifier.Class(classifier.fqNameSerialized)
}
is FunctionalType -> KmType(flags).also { km ->
typeArguments.mapTo(km.arguments) { it.map(shouldExpandTypeAliases) }
km.classifier = KmClassifier.Class(classifier.fqNameSerialized)
}
is TypeParameterType -> KmType(flags).also { km ->
km.classifier = KmClassifier.TypeParameter(id)
}
}
fun FunctionParameterStub.map(): KmValueParameter =
KmValueParameter(flags, name).also { km ->
val kmType = type.map()
if (isVararg) {
km.varargElementType = kmType
km.type = ClassifierStubType(
Classifier.topLevel("kotlin", "Array"),
listOf(TypeArgumentStub(type))
).map()
} else {
km.type = kmType
}
annotations.mapTo(km.annotations, { it.map() })
}
fun TypeParameterStub.map(): KmTypeParameter =
KmTypeParameter(flagsOf(), name, id, KmVariance.INVARIANT).also { km ->
km.upperBounds.addIfNotNull(upperBound?.map())
}
private fun TypeArgument.map(expanded: Boolean = true): KmTypeProjection = when (this) {
TypeArgument.StarProjection -> KmTypeProjection.STAR
is TypeArgumentStub -> KmTypeProjection(variance.map(), type.map(expanded))
else -> error("Unexpected TypeArgument: $this")
}
private fun TypeArgument.Variance.map(): KmVariance = when (this) {
TypeArgument.Variance.INVARIANT -> KmVariance.INVARIANT
TypeArgument.Variance.IN -> KmVariance.IN
TypeArgument.Variance.OUT -> KmVariance.OUT
}
fun ConstantStub.mapToAnnotationArgument(): KmAnnotationArgument<*> = when (this) {
is StringConstantStub -> KmAnnotationArgument.StringValue(value)
is IntegralConstantStub -> when (size) {
1 -> if (isSigned) {
KmAnnotationArgument.ByteValue(value.toByte())
} else {
KmAnnotationArgument.UByteValue(value.toByte())
}
2 -> if (isSigned) {
KmAnnotationArgument.ShortValue(value.toShort())
} else {
KmAnnotationArgument.UShortValue(value.toShort())
}
4 -> if (isSigned) {
KmAnnotationArgument.IntValue(value.toInt())
} else {
KmAnnotationArgument.UIntValue(value.toInt())
}
8 -> if (isSigned) {
KmAnnotationArgument.LongValue(value)
} else {
KmAnnotationArgument.ULongValue(value)
}
else -> error("Integral constant of value $value with unexpected size of $size.")
}
is DoubleConstantStub -> when (size) {
4 -> KmAnnotationArgument.FloatValue(value.toFloat())
8 -> KmAnnotationArgument.DoubleValue(value)
else -> error("Floating-point constant of value $value with unexpected size of $size.")
}
}
fun ConstantStub.mapToConstantAnnotation(): KmAnnotation =
KmAnnotation(
determineConstantAnnotationClassifier().fqNameSerialized,
mapOf("value" to mapToAnnotationArgument())
)
private val TypeParameterType.id: Int
get() = typeParameterDeclaration.id
private val TypeParameterStub.id: Int
get() = typeParametersInterner.intern(this)
/**
* Sometimes we can't generate bridge for getter or setter.
* For example, it may happen due to bug in libclang which may
* erroneously skip `const` qualifier of global variable.
*
* In this case we should change effective property's kind to either `val`
* or even omit the declaration at all.
*/
val PropertyStub.bridgeSupportedKind: PropertyStub.Kind?
get() = when (kind) {
is PropertyStub.Kind.Var -> {
val isGetterSupported = bridgeBuilderResult.nativeBridges.isSupported(kind.getter)
val isSetterSupported = bridgeBuilderResult.nativeBridges.isSupported(kind.setter)
when {
isGetterSupported && isSetterSupported -> kind
!isGetterSupported -> null
else -> PropertyStub.Kind.Val(kind.getter)
}
}
is PropertyStub.Kind.Val -> {
val isGetterSupported = bridgeBuilderResult.nativeBridges.isSupported(kind.getter)
if (isGetterSupported) {
kind
} else {
null
}
}
is PropertyStub.Kind.Constant -> kind
}
}
@@ -0,0 +1,663 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.utils.addIfNotNull
import java.lang.IllegalStateException
/**
* Emits stubs and bridge functions as *.kt and *.c files.
* Many unintuitive printings are made for compatability with previous version of stub generator.
*
* [omitEmptyLines] is useful for testing output (e.g. diff calculating).
*/
class StubIrTextEmitter(
private val context: StubIrContext,
private val builderResult: StubIrBuilderResult,
private val bridgeBuilderResult: BridgeBuilderResult,
private val omitEmptyLines: Boolean = false
) {
private val kotlinFile = bridgeBuilderResult.kotlinFile
private val nativeBridges = bridgeBuilderResult.nativeBridges
private val propertyAccessorBridgeBodies = bridgeBuilderResult.propertyAccessorBridgeBodies
private val functionBridgeBodies = bridgeBuilderResult.functionBridgeBodies
private val pkgName: String
get() = context.configuration.pkgName
private val StubContainer.isTopLevelContainer: Boolean
get() = this == builderResult.stubs || this in builderResult.stubs.simpleContainers
/**
* The output currently used by the generator.
* Should append line separator after any usage.
*/
private var out: (String) -> Unit = {
throw IllegalStateException()
}
private fun emitEmptyLine() {
if (!omitEmptyLines) {
out("")
}
}
private fun <R> withOutput(output: (String) -> Unit, action: () -> R): R {
val oldOut = out
out = output
try {
return action()
} finally {
out = oldOut
}
}
private fun <R> withOutput(appendable: Appendable, action: () -> R): R {
return withOutput({ appendable.appendLine(it) }, action)
}
private fun generateLinesBy(action: () -> Unit): List<String> {
val result = mutableListOf<String>()
withOutput({ result.add(it) }, action)
return result
}
private fun generateKotlinFragmentBy(block: () -> Unit): Sequence<String> {
val lines = generateLinesBy(block)
return lines.asSequence()
}
private fun <R> indent(action: () -> R): R {
val oldOut = out
return withOutput({ oldOut(" $it") }, action)
}
private fun <R> block(header: String, body: () -> R): R {
out("$header {")
val res = indent {
body()
}
out("}")
return res
}
private fun emitKotlinFileHeader() {
if (context.platform == KotlinPlatform.JVM) {
out("@file:JvmName(${context.jvmFileClassName.quoteAsKotlinLiteral()})")
}
if (context.platform == KotlinPlatform.NATIVE) {
out("@file:kotlinx.cinterop.InteropStubs")
}
val suppress = mutableListOf("UNUSED_VARIABLE", "UNUSED_EXPRESSION").apply {
add("DEPRECATION") // CVariable.Type and CEnum companion deprecations.
if (context.configuration.library.language == Language.OBJECTIVE_C) {
add("CONFLICTING_OVERLOADS")
add("RETURN_TYPE_MISMATCH_ON_INHERITANCE")
add("PROPERTY_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting property with conflicting types
add("VAR_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting mutable property with conflicting types
add("RETURN_TYPE_MISMATCH_ON_OVERRIDE")
add("WRONG_MODIFIER_CONTAINING_DECLARATION") // For `final val` in interface.
add("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
add("UNUSED_PARAMETER") // For constructors.
add("MANY_IMPL_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions.
add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support.
add("DEPRECATION") // For uncheckedCast.
add("DEPRECATION_ERROR") // For initializers.
}
}
out("@file:Suppress(${suppress.joinToString { it.quoteAsKotlinLiteral() }})")
if (pkgName != "") {
out("package ${context.validPackageName}")
out("")
}
if (context.platform == KotlinPlatform.NATIVE) {
out("import kotlin.native.SymbolName")
out("import kotlinx.cinterop.internal.*")
}
out("import kotlinx.cinterop.*")
kotlinFile.buildImports().forEach {
out(it)
}
out("")
out("// NOTE THIS FILE IS AUTO-GENERATED")
}
fun emit(ktFile: Appendable) {
// Stubs generation may affect imports list so do it before header generation.
val stubLines = generateKotlinFragmentBy {
printer.visitSimpleStubContainer(builderResult.stubs, null)
}
withOutput(ktFile) {
emitKotlinFileHeader()
stubLines.forEach(out)
nativeBridges.kotlinLines.forEach(out)
if (context.platform == KotlinPlatform.JVM)
out("private val loadLibrary = loadKonanLibrary(\"${context.libName}\")")
}
}
private val printer = object : StubIrVisitor<StubContainer?, Unit> {
override fun visitClass(element: ClassStub, data: StubContainer?) {
element.annotations.forEach {
out(renderAnnotation(it))
}
val header = renderClassHeader(element)
when {
element.children.isEmpty() -> out(header)
else -> block(header) {
if (element is ClassStub.Enum) {
emitEnumEntries(element)
}
element.children
// We render a primary constructor as part of a header.
.filterNot { it is ConstructorStub && it.isPrimary }
.forEach {
emitEmptyLine()
it.accept(this, element)
}
if (element is ClassStub.Enum) {
emitEnumVarClass(element)
}
}
}
}
override fun visitTypealias(element: TypealiasStub, data: StubContainer?) {
val alias = renderClassifierDeclaration(element.alias)
val aliasee = renderStubType(element.aliasee)
out("typealias $alias = $aliasee")
}
override fun visitFunction(element: FunctionStub, data: StubContainer?) {
if (element in bridgeBuilderResult.excludedStubs) return
val header = run {
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val override = if (element.isOverride) "override " else ""
val modality = renderMemberModality(element.modality, data)
"$override${modality}fun$typeParameters $receiver${element.name.asSimpleName()}$parameters: ${renderStubType(element.returnType)}"
}
if (!nativeBridges.isSupported(element)) {
sequenceOf(
annotationForUnableToImport,
"$header = throw UnsupportedOperationException()"
).forEach(out)
return
}
element.annotations.forEach {
out(renderAnnotation(it))
}
when {
element.external -> out("external $header")
element.isOptionalObjCMethod() -> out("$header = optional()")
element.origin is StubOrigin.Synthetic.EnumByValue ->
out("$header = values().find { it.value == value }!!")
data != null && data.isInterface -> out(header)
else -> block(header) {
functionBridgeBodies.getValue(element).forEach(out)
}
}
}
override fun visitProperty(element: PropertyStub, data: StubContainer?) =
emitProperty(element, data)
override fun visitConstructor(constructorStub: ConstructorStub, data: StubContainer?) {
constructorStub.annotations.forEach {
out(renderAnnotation(it))
}
val visibility = renderVisibilityModifier(constructorStub.visibility)
out("${visibility}constructor(${constructorStub.parameters.joinToString { renderFunctionParameter(it) }}) {}")
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: StubContainer?) {
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: StubContainer?) {
if (simpleStubContainer.meta.textAtStart.isNotEmpty()) {
out(simpleStubContainer.meta.textAtStart)
}
simpleStubContainer.classes.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.functions.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.properties.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.typealiases.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.simpleContainers.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
if (simpleStubContainer.meta.textAtEnd.isNotEmpty()) {
out(simpleStubContainer.meta.textAtEnd)
}
}
}
// About method naming convention:
// - "emit" prefix means that method will call `out` by itself.
// - "render" prefix means that method returns string that should be emitted by caller.
private fun emitEnumEntries(enum: ClassStub.Enum) {
enum.entries.forEach {
out(renderEnumEntry(it) + ",")
}
out(";")
}
private fun emitEnumVarClass(enum: ClassStub.Enum) {
val simpleKotlinName = enum.classifier.topLevelName.asSimpleName()
val typeMirror = builderResult.bridgeGenerationComponents.enumToTypeMirror.getValue(enum)
val basePointedTypeName = typeMirror.pointedType.render(kotlinFile)
block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") {
out("@Deprecated(\"Use sizeOf<T>() or alignOf<T>() instead.\")")
out("companion object : Type(sizeOf<$basePointedTypeName>().toInt())")
out("var value: $simpleKotlinName")
out(" get() = byValue(this.reinterpret<$basePointedTypeName>().value)")
out(" set(value) { this.reinterpret<$basePointedTypeName>().value = value.value }")
}
}
private fun emitProperty(element: PropertyStub, owner: StubContainer?) {
if (element in bridgeBuilderResult.excludedStubs) return
val override = if (element.isOverride) "override " else ""
val modality = "$override${renderMemberModality(element.modality, owner)}"
val receiver = if (element.receiverType != null) "${renderStubType(element.receiverType)}." else ""
val name = if (owner?.isTopLevelContainer == true) {
getTopLevelPropertyDeclarationName(kotlinFile, element).asSimpleName()
} else {
element.name.asSimpleName()
}
val header = "$receiver$name: ${renderStubType(element.type)}"
if (element.kind is PropertyStub.Kind.Val && !nativeBridges.isSupported(element.kind.getter)
|| element.kind is PropertyStub.Kind.Var && !nativeBridges.isSupported(element.kind.getter)) {
out(annotationForUnableToImport)
out("val $header")
out(" get() = TODO()")
} else {
element.annotations.forEach {
out(renderAnnotation(it))
}
when (val kind = element.kind) {
is PropertyStub.Kind.Constant -> {
out("${modality}const val $header = ${renderValueUsage(kind.constant)}")
}
is PropertyStub.Kind.Val -> {
val shouldWriteInline = kind.getter.let {
(it is PropertyAccessor.Getter.SimpleGetter && it.constant != null)
// We should render access to constructor parameter inline.
// Otherwise, it may be access to the property itself. (val f: Any get() = f)
|| it is PropertyAccessor.Getter.GetConstructorParameter
}
if (shouldWriteInline) {
out("${modality}val $header ${renderGetter(kind.getter)}")
} else {
out("${modality}val $header")
indent {
out(renderGetter(kind.getter))
}
}
}
is PropertyStub.Kind.Var -> {
val isSupported = nativeBridges.isSupported(kind.setter)
val variableKind = if (isSupported) "var" else "val"
out("$modality$variableKind $header")
indent {
out(renderGetter(kind.getter))
if (isSupported) {
out(renderSetter(kind.setter))
}
}
}
}
}
}
private fun renderFunctionReceiver(receiver: ReceiverParameterStub): String {
return renderStubType(receiver.type)
}
private fun renderFunctionParameter(parameter: FunctionParameterStub): String {
val annotations = if (parameter.annotations.isEmpty())
""
else
parameter.annotations.joinToString(separator = " ") { renderAnnotation(it) } + " "
val vararg = if (parameter.isVararg) "vararg " else ""
return "$annotations$vararg${parameter.name.asSimpleName()}: ${renderStubType(parameter.type)}"
}
private fun renderMemberModality(modality: MemberStubModality, container: StubContainer?): String =
if (container?.defaultMemberModality == modality) {
""
} else
when (modality) {
MemberStubModality.OPEN -> "open "
MemberStubModality.FINAL -> "final "
MemberStubModality.ABSTRACT -> "abstract "
}
private fun renderVisibilityModifier(visibilityModifier: VisibilityModifier) = when (visibilityModifier) {
VisibilityModifier.PRIVATE -> "private "
VisibilityModifier.PROTECTED -> "protected "
VisibilityModifier.INTERNAL -> "internal "
VisibilityModifier.PUBLIC -> ""
}
private fun renderClassHeader(classStub: ClassStub): String {
val modality = when (classStub) {
is ClassStub.Simple -> renderClassStubModality(classStub.modality)
is ClassStub.Companion -> ""
is ClassStub.Enum -> "enum class "
}
val className = when (classStub) {
is ClassStub.Simple -> renderClassifierDeclaration(classStub.classifier)
is ClassStub.Companion -> "companion object"
is ClassStub.Enum -> renderClassifierDeclaration(classStub.classifier)
}
val constructorParams = classStub.explicitPrimaryConstructor?.parameters?.let(this::renderConstructorParams) ?: ""
val inheritance = mutableListOf<String>().apply {
// Enum inheritance is implicit.
if (classStub !is ClassStub.Enum) {
addIfNotNull(classStub.superClassInit?.let { renderSuperInit(it) })
}
addAll(classStub.interfaces.map { renderStubType(it) })
}.let { if (it.isNotEmpty()) " : ${it.joinToString()}" else "" }
return "$modality$className$constructorParams$inheritance"
}
private fun renderClassifierDeclaration(classifier: Classifier): String =
kotlinFile.declare(classifier).asSimpleName()
private fun renderClassStubModality(classStubModality: ClassStubModality): String = when (classStubModality) {
ClassStubModality.INTERFACE -> "interface "
ClassStubModality.OPEN -> "open class "
ClassStubModality.ABSTRACT -> "abstract class "
ClassStubModality.NONE -> "class "
}
private fun renderConstructorParams(parameters: List<FunctionParameterStub>): String =
if (parameters.isEmpty()) {
""
} else {
parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
}
private fun renderSuperInit(superClassInit: SuperClassInit): String {
val parameters = superClassInit.arguments.joinToString(prefix = "(", postfix = ")") { renderValueUsage(it) }
return "${renderStubType(superClassInit.type)}$parameters"
}
private fun renderStubType(stubType: StubType): String {
val nullable = if (stubType.nullable) "?" else ""
return when (stubType) {
is ClassifierStubType -> {
val classifier = kotlinFile.reference(stubType.classifier)
val typeArguments = renderTypeArguments(stubType.typeArguments)
"$classifier$typeArguments$nullable"
}
is FunctionalType -> buildString {
if (stubType.nullable) append("(")
append('(')
stubType.parameterTypes.joinTo(this) { renderStubType(it) }
append(") -> ")
append(renderStubType(stubType.returnType))
if (stubType.nullable) append(")?")
}
is TypeParameterType -> "${stubType.name}$nullable"
is AbbreviatedType -> {
val classifier = kotlinFile.reference(stubType.abbreviatedClassifier)
val typeArguments = renderTypeArguments(stubType.typeArguments)
"$classifier$typeArguments$nullable"
}
}
}
private fun renderValueUsage(value: ValueStub): String = when (value) {
is StringConstantStub -> value.value.quoteAsKotlinLiteral()
is IntegralConstantStub -> renderIntegralConstant(value)!!
is DoubleConstantStub -> renderDoubleConstant(value)!!
is GetConstructorParameter -> value.constructorParameterStub.name
}
private fun renderAnnotation(annotationStub: AnnotationStub): String = when (annotationStub) {
AnnotationStub.ObjC.ConsumesReceiver -> "@CCall.ConsumesReceiver"
AnnotationStub.ObjC.ReturnsRetained -> "@CCall.ReturnsRetained"
is AnnotationStub.ObjC.Method -> {
val stret = if (annotationStub.isStret) ", true" else ""
val selector = annotationStub.selector.quoteAsKotlinLiteral()
val encoding = annotationStub.encoding.quoteAsKotlinLiteral()
"@ObjCMethod($selector, $encoding$stret)"
}
is AnnotationStub.ObjC.Factory -> {
val stret = if (annotationStub.isStret) ", true" else ""
val selector = annotationStub.selector.quoteAsKotlinLiteral()
val encoding = annotationStub.encoding.quoteAsKotlinLiteral()
"@ObjCFactory($selector, $encoding$stret)"
}
AnnotationStub.ObjC.Consumed ->
"@CCall.Consumed"
is AnnotationStub.ObjC.Constructor ->
"@ObjCConstructor(${annotationStub.selector.quoteAsKotlinLiteral()}, ${annotationStub.designated})"
is AnnotationStub.ObjC.ExternalClass -> {
val protocolGetter = annotationStub.protocolGetter.quoteAsKotlinLiteral()
val binaryName = annotationStub.binaryName.quoteAsKotlinLiteral()
"@ExternalObjCClass" + when {
annotationStub.protocolGetter.isEmpty() && annotationStub.binaryName.isEmpty() -> ""
annotationStub.protocolGetter.isEmpty() -> "(\"\", $binaryName)"
annotationStub.binaryName.isEmpty() -> "($protocolGetter)"
else -> "($protocolGetter, $binaryName)"
}
}
AnnotationStub.CCall.CString ->
"@CCall.CString"
AnnotationStub.CCall.WCString ->
"@CCall.WCString"
is AnnotationStub.CCall.Symbol ->
"@CCall(${annotationStub.symbolName.quoteAsKotlinLiteral()})"
is AnnotationStub.CStruct ->
"@CStruct(${annotationStub.struct.quoteAsKotlinLiteral()})"
is AnnotationStub.CNaturalStruct ->
"@CNaturalStruct(${annotationStub.members.joinToString { it.name.quoteAsKotlinLiteral() }})"
is AnnotationStub.CLength ->
"@CLength(${annotationStub.length})"
is AnnotationStub.Deprecated ->
"@Deprecated(${annotationStub.message.quoteAsKotlinLiteral()}, " +
"ReplaceWith(${annotationStub.replaceWith.quoteAsKotlinLiteral()}), " +
"DeprecationLevel.${annotationStub.level.name})"
is AnnotationStub.CEnumEntryAlias,
is AnnotationStub.CEnumVarTypeSize,
is AnnotationStub.CStruct.MemberAt,
is AnnotationStub.CStruct.ArrayMemberAt,
is AnnotationStub.CStruct.BitField,
is AnnotationStub.CStruct.VarType ->
error("${annotationStub.classifier.fqName} annotation is unsupported in textual mode")
}
private fun renderEnumEntry(enumEntryStub: EnumEntryStub): String =
"${enumEntryStub.name.asSimpleName()}(${renderValueUsage(enumEntryStub.constant)})"
private fun renderGetter(accessor: PropertyAccessor.Getter): String {
val annotations = accessor.annotations.joinToString(separator = "") { renderAnnotation(it) + " " }
return annotations + when (accessor) {
is PropertyAccessor.Getter.ExternalGetter -> {
"external get"
}
is PropertyAccessor.Getter.GetConstructorParameter -> "= ${renderPropertyAccessorBody(accessor)}"
else -> {
"get() = ${renderPropertyAccessorBody(accessor)}"
}
}
}
private fun renderSetter(accessor: PropertyAccessor.Setter): String {
val annotations = accessor.annotations.joinToString(separator = "") { renderAnnotation(it) + " " }
return annotations + if (accessor is PropertyAccessor.Setter.ExternalSetter) {
"external set"
} else {
"set(value) { ${renderPropertyAccessorBody(accessor)} }"
}
}
private fun renderPropertyAccessorBody(accessor: PropertyAccessor): String = when (accessor) {
is PropertyAccessor.Getter.SimpleGetter -> {
when {
accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor)
accessor.constant != null -> renderValueUsage(accessor.constant)
else -> error("Bridge body for getter was not generated")
}
}
is PropertyAccessor.Getter.GetConstructorParameter -> accessor.constructorParameter.name
is PropertyAccessor.Getter.ArrayMemberAt -> "arrayMemberAt(${accessor.offset})"
is PropertyAccessor.Getter.MemberAt -> {
val typeArguments = renderTypeArguments(accessor.typeArguments)
val valueAccess = if (accessor.hasValueAccessor) ".value" else ""
"memberAt$typeArguments(${accessor.offset})$valueAccess"
}
is PropertyAccessor.Getter.ReadBits -> {
propertyAccessorBridgeBodies.getValue(accessor)
}
is PropertyAccessor.Getter.GetEnumEntry -> accessor.enumEntryStub.name
is PropertyAccessor.Setter.SimpleSetter -> when {
accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor)
else -> error("Bridge body for setter was not generated")
}
is PropertyAccessor.Setter.MemberAt -> {
if (accessor.typeArguments.isEmpty()) {
error("Unexpected memberAt setter without type parameters!")
} else {
val typeArguments = renderTypeArguments(accessor.typeArguments)
"memberAt$typeArguments(${accessor.offset}).value = value"
}
}
is PropertyAccessor.Setter.WriteBits -> {
propertyAccessorBridgeBodies.getValue(accessor)
}
is PropertyAccessor.Getter.InterpretPointed -> {
val typeParameters = accessor.typeParameters.joinToString(prefix = "<", postfix = ">") { renderStubType(it) }
val getAddressExpression = propertyAccessorBridgeBodies.getValue(accessor)
"interpretPointed$typeParameters($getAddressExpression)"
}
is PropertyAccessor.Getter.ExternalGetter,
is PropertyAccessor.Setter.ExternalSetter -> error("External property accessor shouldn't have a body!")
}
private fun renderIntegralConstant(integralValue: IntegralConstantStub): String? {
val (value, size, isSigned) = integralValue
return if (isSigned) {
if (value == Long.MIN_VALUE) {
return "${value + 1} - 1" // Workaround for "The value is out of range" compile error.
}
val narrowedValue: Number = when (size) {
1 -> value.toByte()
2 -> value.toShort()
4 -> value.toInt()
8 -> value
else -> return null
}
narrowedValue.toString()
} else {
// Note: stub generator is built and run with different ABI versions,
// so Kotlin unsigned types can't be used here currently.
val narrowedValue: String = when (size) {
1 -> (value and 0xFF).toString()
2 -> (value and 0xFFFF).toString()
4 -> (value and 0xFFFFFFFF).toString()
8 -> java.lang.Long.toUnsignedString(value)
else -> return null
}
"${narrowedValue}u"
}
}
private fun renderDoubleConstant(doubleValue: DoubleConstantStub): String? {
val (value, size) = doubleValue
return when (size) {
4 -> {
val floatValue = value.toFloat()
val bits = java.lang.Float.floatToRawIntBits(floatValue)
"bitsToFloat($bits) /* == $floatValue */"
}
8 -> {
val bits = java.lang.Double.doubleToRawLongBits(value)
"bitsToDouble($bits) /* == $value */"
}
else -> null
}
}
private fun renderTypeArguments(typeArguments: List<TypeArgument>) = if (typeArguments.isNotEmpty()) {
typeArguments.joinToString(", ", "<", ">") { renderTypeArgument(it) }
} else {
""
}
private fun renderTypeArgument(typeArgument: TypeArgument) = when (typeArgument) {
is TypeArgumentStub -> {
val variance = when (typeArgument.variance) {
TypeArgument.Variance.INVARIANT -> ""
TypeArgument.Variance.IN -> "in "
TypeArgument.Variance.OUT -> "out "
}
"$variance${renderStubType(typeArgument.type)}"
}
TypeArgument.StarProjection -> "*"
else -> error("Unexpected type argument: $typeArgument")
}
private fun renderTypeParameters(typeParameters: List<TypeParameterStub>) = if (typeParameters.isNotEmpty()) {
typeParameters.joinToString(", ", " <", ">") { renderTypeParameter(it) }
} else {
""
}
private fun renderTypeParameter(typeParameterStub: TypeParameterStub): String {
val name = typeParameterStub.name
return typeParameterStub.upperBound?.let {
"$name : ${renderStubType(it)}"
} ?: name
}
}
@@ -0,0 +1,223 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty
sealed class StubType {
abstract val nullable: Boolean
abstract val typeArguments: List<TypeArgument>
}
/**
* Wrapper over [Classifier].
*/
class ClassifierStubType(
val classifier: Classifier,
override val typeArguments: List<TypeArgument> = emptyList(),
override val nullable: Boolean = false
) : StubType() {
fun nested(name: String): ClassifierStubType =
ClassifierStubType(classifier.nested(name))
override fun toString(): String =
"${classifier.topLevelName}${typeArguments.ifNotEmpty { joinToString(prefix = "<", postfix = ">") } ?: ""}"
}
class AbbreviatedType(
val underlyingType: StubType,
val abbreviatedClassifier: Classifier,
override val typeArguments: List<TypeArgument>,
override val nullable: Boolean = false
) : StubType() {
override fun toString(): String =
"${abbreviatedClassifier.topLevelName}${typeArguments.ifNotEmpty { joinToString(prefix = "<", postfix = ">") } ?: ""}"
}
/**
* @return type from kotlinx.cinterop package
*/
fun KotlinPlatform.getRuntimeType(name: String, nullable: Boolean = false): StubType {
val classifier = Classifier.topLevel(cinteropPackage, name)
PredefinedTypesHandler.tryExpandPlatformDependentTypealias(classifier, this, nullable)?.let { return it }
return ClassifierStubType(classifier, nullable = nullable)
}
/**
* Functional type from kotlin package: ([parameterTypes]) -> [returnType]
*/
class FunctionalType(
val parameterTypes: List<StubType>, // TODO: Use TypeArguments.
val returnType: StubType,
override val nullable: Boolean = false
) : StubType() {
val classifier: Classifier =
Classifier.topLevel("kotlin", "Function${parameterTypes.size}")
override val typeArguments: List<TypeArgument> by lazy {
listOf(*parameterTypes.toTypedArray(), returnType).map { TypeArgumentStub(it) }
}
}
class TypeParameterType(
val name: String,
override val nullable: Boolean,
val typeParameterDeclaration: TypeParameterStub
) : StubType() {
override val typeArguments: List<TypeArgument> = emptyList()
}
fun KotlinType.toStubIrType(): StubType = when (this) {
is KotlinFunctionType -> this.toStubIrType()
is KotlinClassifierType -> this.toStubIrType()
else -> error("Unexpected KotlinType: $this")
}
private fun KotlinFunctionType.toStubIrType(): StubType =
FunctionalType(parameterTypes.map(KotlinType::toStubIrType), returnType.toStubIrType(), nullable)
private fun KotlinClassifierType.toStubIrType(): StubType {
val typeArguments = arguments.map(KotlinTypeArgument::toStubIrType)
PredefinedTypesHandler.tryExpandPredefinedTypealias(classifier, nullable, typeArguments)?.let { return it }
return if (underlyingType == null) {
ClassifierStubType(classifier, typeArguments, nullable)
} else {
AbbreviatedType(underlyingType.toStubIrType(), classifier, typeArguments, nullable)
}
}
private fun KotlinTypeArgument.toStubIrType(): TypeArgument = when (this) {
is KotlinType -> TypeArgumentStub(this.toStubIrType())
StarProjection -> TypeArgument.StarProjection
else -> error("Unexpected KotlinTypeArgument: $this")
}
/**
* Types that come from kotlinx.cinterop require special handling because we
* don't have explicit information about their structure.
* For example, to be able to produce metadata-based interop library we need to know
* that ByteVar is a typealias to ByteVarOf<Byte>.
*/
private object PredefinedTypesHandler {
private const val cInteropPackage = "kotlinx.cinterop"
private val nativePtrClassifier = Classifier.topLevel(cInteropPackage, "NativePtr")
private val primitives = setOf(
KotlinTypes.boolean,
KotlinTypes.byte, KotlinTypes.short, KotlinTypes.int, KotlinTypes.long,
KotlinTypes.uByte, KotlinTypes.uShort, KotlinTypes.uInt, KotlinTypes.uLong,
KotlinTypes.float, KotlinTypes.double,
KotlinTypes.vector128
)
/**
* kotlinx.cinterop.{primitive}Var -> kotlin.{primitive}
*/
private val primitiveVarClassifierToPrimitiveType: Map<Classifier, KotlinClassifierType> =
primitives.associateBy {
val typeVar = "${it.classifier.topLevelName}Var"
Classifier.topLevel(cInteropPackage, typeVar)
}
/**
* @param primitiveType primitive type from kotlin package.
* @return kotlinx.cinterop.[primitiveType]VarOf<[primitiveType]>
*/
private fun getVarOfTypeFor(primitiveType: KotlinClassifierType, nullable: Boolean): ClassifierStubType {
val typeVarOf = "${primitiveType.classifier.topLevelName}VarOf"
val classifier = Classifier.topLevel(cInteropPackage, typeVarOf)
return ClassifierStubType(classifier, listOf(TypeArgumentStub(primitiveType.toStubIrType())), nullable = nullable)
}
private fun expandCOpaquePointerVar(nullable: Boolean): AbbreviatedType {
val typeArgument = TypeArgumentStub(expandCOpaquePointer(nullable=false))
val underlyingType = ClassifierStubType(
KotlinTypes.cPointerVarOf, listOf(typeArgument), nullable = nullable
)
return AbbreviatedType(underlyingType, KotlinTypes.cOpaquePointerVar.classifier, emptyList(), nullable)
}
private fun expandCOpaquePointer(nullable: Boolean): AbbreviatedType {
val typeArgument = TypeArgumentStub(ClassifierStubType(KotlinTypes.cPointed), TypeArgument.Variance.OUT)
val underlyingType = ClassifierStubType(
KotlinTypes.cPointer, listOf(typeArgument), nullable = nullable
)
return AbbreviatedType(underlyingType, KotlinTypes.cOpaquePointer.classifier, emptyList(), nullable)
}
private fun expandCPointerVar(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
require(typeArguments.size == 1) { "CPointerVar has only one type argument." }
val cPointer = ClassifierStubType(KotlinTypes.cPointer, typeArguments)
val cPointerVarOfTypeArgument = TypeArgumentStub(cPointer)
val underlyingType = ClassifierStubType(
KotlinTypes.cPointerVarOf, listOf(cPointerVarOfTypeArgument), nullable = nullable
)
return AbbreviatedType(underlyingType, KotlinTypes.cPointerVar, typeArguments, nullable)
}
/**
* @param primitiveVarType one of kotlinx.cinterop.{primitive}Var types.
* @return typealias in terms of StubIR types.
*/
private fun expandPrimitiveVarType(primitiveVarClassifier: Classifier, nullable: Boolean): AbbreviatedType {
val primitiveType = primitiveVarClassifierToPrimitiveType.getValue(primitiveVarClassifier)
val underlyingType = getVarOfTypeFor(primitiveType, nullable)
return AbbreviatedType(underlyingType, primitiveVarClassifier, listOf(), nullable)
}
private fun expandNativePtr(platform: KotlinPlatform, nullable: Boolean): StubType {
val underlyingTypeClassifier = when (platform) {
KotlinPlatform.JVM -> KotlinTypes.long.classifier
KotlinPlatform.NATIVE -> Classifier.topLevel("kotlin.native.internal", "NativePtr")
}
val underlyingType = ClassifierStubType(underlyingTypeClassifier, nullable = nullable)
return AbbreviatedType(underlyingType, nativePtrClassifier, listOf(), nullable)
}
private fun expandObjCObjectMeta(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
require(typeArguments.isEmpty())
val objCClass = ClassifierStubType(KotlinTypes.objCClass, emptyList(), nullable)
return AbbreviatedType(objCClass, KotlinTypes.objCObjectMeta, emptyList(), nullable)
}
private fun expandCArrayPointer(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
val cPointer = ClassifierStubType(KotlinTypes.cPointer, typeArguments)
return AbbreviatedType(cPointer, KotlinTypes.cArrayPointer, typeArguments, nullable)
}
private fun expandObjCBlockVar(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
val underlyingType = ClassifierStubType(KotlinTypes.objCNotImplementedVar, typeArguments, nullable)
return AbbreviatedType(underlyingType, KotlinTypes.objCBlockVar, typeArguments, nullable)
}
/**
* @return [ClassifierStubType] if [classifier] is a typealias from [kotlinx.cinterop] package.
*/
fun tryExpandPredefinedTypealias(classifier: Classifier, nullable: Boolean, typeArguments: List<TypeArgument>): AbbreviatedType? =
when (classifier) {
in primitiveVarClassifierToPrimitiveType.keys -> expandPrimitiveVarType(classifier, nullable)
KotlinTypes.cOpaquePointer.classifier -> expandCOpaquePointer(nullable)
KotlinTypes.cOpaquePointerVar.classifier -> expandCOpaquePointerVar(nullable)
KotlinTypes.cPointerVar -> expandCPointerVar(typeArguments, nullable)
KotlinTypes.objCObjectMeta -> expandObjCObjectMeta(typeArguments, nullable)
KotlinTypes.cArrayPointer -> expandCArrayPointer(typeArguments, nullable)
KotlinTypes.objCBlockVar -> expandObjCBlockVar(typeArguments, nullable)
else -> null
}
/**
* Variant of [tryExpandPredefinedTypealias] with [platform]-dependent result.
*/
fun tryExpandPlatformDependentTypealias(
classifier: Classifier, platform: KotlinPlatform, nullable: Boolean
): StubType? =
when (classifier) {
nativePtrClassifier -> expandNativePtr(platform, nullable)
else -> null
}
}
@@ -0,0 +1,22 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
interface StubIrVisitor<T, R> {
fun visitClass(element: ClassStub, data: T): R
fun visitTypealias(element: TypealiasStub, data: T): R
fun visitFunction(element: FunctionStub, data: T): R
fun visitProperty(element: PropertyStub, data: T): R
fun visitConstructor(constructorStub: ConstructorStub, data: T): R
fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: T): R
fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: T): R
}
@@ -0,0 +1,100 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
val EnumDef.isAnonymous: Boolean
get() = spelling.contains("(anonymous ") // TODO: it is a hack
val StructDecl.isAnonymous: Boolean
get() = spelling.contains("(anonymous ") // TODO: it is a hack
/**
* Returns the expression which could be used for this type in C code.
* Note: the resulting string doesn't exactly represent this type, but it is enough for current purposes.
*
* TODO: use libclang to implement?
*/
fun Type.getStringRepresentation(): String = when (this) {
VoidType -> "void"
CharType -> "char"
CBoolType -> "_Bool"
ObjCBoolType -> "BOOL"
is IntegerType -> this.spelling
is FloatingType -> this.spelling
is VectorType -> this.spelling
is PointerType -> getPointerTypeStringRepresentation(this.pointeeType)
is ArrayType -> getPointerTypeStringRepresentation(this.elemType)
is RecordType -> this.decl.spelling
is EnumType -> if (this.def.isAnonymous) {
this.def.baseType.getStringRepresentation()
} else {
this.def.spelling
}
is Typedef -> this.def.aliased.getStringRepresentation()
is ObjCPointer -> when (this) {
is ObjCIdType -> "id$protocolQualifier"
is ObjCClassPointer -> "Class$protocolQualifier"
is ObjCObjectPointer -> "${def.name}$protocolQualifier*"
is ObjCInstanceType -> TODO(this.toString()) // Must have already been handled.
is ObjCBlockPointer -> "id"
}
else -> throw NotImplementedError()
}
fun getPointerTypeStringRepresentation(pointee: Type): String =
(getStringRepresentationOfPointee(pointee) ?: "void") + "*"
private fun getStringRepresentationOfPointee(type: Type): String? {
val unwrapped = type.unwrapTypedefs()
return when (unwrapped) {
is PrimitiveType -> unwrapped.getStringRepresentation()
is PointerType -> getStringRepresentationOfPointee(unwrapped.pointeeType)?.plus("*")
is RecordType -> if (unwrapped.decl.isAnonymous || unwrapped.decl.spelling == "struct __va_list_tag") {
null
} else {
unwrapped.decl.spelling
}
else -> null
}
}
private val ObjCQualifiedPointer.protocolQualifier: String
get() = if (this.protocols.isEmpty()) "" else " <${protocols.joinToString { it.name }}>"
fun blockTypeStringRepresentation(type: ObjCBlockPointer): String {
return buildString {
append(type.returnType.getStringRepresentation())
append("(^)")
append("(")
val blockParameters = if (type.parameterTypes.isEmpty()) {
"void"
} else {
type.parameterTypes.joinToString { it.getStringRepresentation() }
}
append(blockParameters)
append(")")
}
}
@@ -0,0 +1,164 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.util.DefFile
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.gen.jvm.buildNativeLibrary
import org.jetbrains.kotlin.native.interop.gen.jvm.prepareTool
import org.jetbrains.kotlin.native.interop.indexer.NativeLibraryHeaders
import org.jetbrains.kotlin.native.interop.indexer.getHeaderPaths
import org.jetbrains.kotlin.native.interop.tool.CInteropArguments
import java.io.File
fun defFileDependencies(args: Array<String>) {
val defFiles = mutableListOf<File>()
val targets = mutableListOf<String>()
var index = 0
while (index < args.size) {
val arg = args[index]
++index
when (arg) {
"-target" -> {
targets += args[index]
++index
}
else -> {
defFiles.add(File(arg))
}
}
}
defFileDependencies(makeDependencyAssigner(targets, defFiles))
}
private fun makeDependencyAssigner(targets: List<String>, defFiles: List<File>) =
CompositeDependencyAssigner(targets.map { makeDependencyAssignerForTarget(it, defFiles) })
private fun makeDependencyAssignerForTarget(target: String, defFiles: List<File>): SingleTargetDependencyAssigner {
val tool = prepareTool(target, KotlinPlatform.NATIVE)
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(arrayOf())
val libraries = defFiles.associateWith {
buildNativeLibrary(
tool,
DefFile(it, tool.substitutions),
cinteropArguments,
ImportsImpl(emptyMap())
).getHeaderPaths()
}
return SingleTargetDependencyAssigner(libraries)
}
private fun defFileDependencies(dependencyAssigner: DependencyAssigner) {
while (!dependencyAssigner.isDone()) {
val (file, depends) = dependencyAssigner.getReady().entries.sortedBy { it.key }.first()
dependencyAssigner.markDone(file)
patchDepends(file, depends.sorted())
println("${file.name} done.")
}
}
private fun patchDepends(file: File, newDepends: List<String>) {
val defFileLines = file.readLines()
val dependsLine = buildString {
append("depends =")
newDepends.forEach {
append(" ")
append(it)
}
}
val newDefFileLines = listOf(dependsLine) + defFileLines.filter { !it.startsWith("depends =") }
file.bufferedWriter().use { writer ->
newDefFileLines.forEach { writer.appendLine(it) }
}
}
private interface DependencyAssigner {
fun isDone(): Boolean
fun getReady(): Map<File, Set<String>>
fun markDone(file: File)
}
private class CompositeDependencyAssigner(val dependencyAssigners: List<DependencyAssigner>) : DependencyAssigner {
override fun isDone(): Boolean = dependencyAssigners.all { it.isDone() }
override fun getReady(): Map<File, Set<String>> {
return dependencyAssigners.map { it.getReady() }.reduce { left, right ->
(left.keys intersect right.keys)
.associateWith { left.getValue(it) union right.getValue(it) }
}.also {
require(it.isNotEmpty()) { "incompatible dependencies" } // TODO: add more info.
}
}
override fun markDone(file: File) {
dependencyAssigners.forEach { it.markDone(file) }
}
}
private class SingleTargetDependencyAssigner(
defFilesToHeaders: Map<File, NativeLibraryHeaders<String>>
) : DependencyAssigner {
private val pendingDefFilesToHeaders = defFilesToHeaders.toMutableMap()
private val processedHeadersToDefFiles = mutableMapOf<String, File>()
init {
val ownedHeaders = pendingDefFilesToHeaders.values.flatMap { it.ownHeaders }
val unownedHeadersToDefFiles = mutableMapOf<String, File>()
pendingDefFilesToHeaders.forEach { (def, lib) ->
(lib.importedHeaders - ownedHeaders).forEach {
unownedHeadersToDefFiles.putIfAbsent(it, def)
}
}
if (unownedHeadersToDefFiles.isNotEmpty()) {
error("Unowned headers:\n" +
unownedHeadersToDefFiles.entries.joinToString("\n") { "${it.key}\n imported by: ${it.value.name}" })
}
}
override fun isDone(): Boolean = pendingDefFilesToHeaders.isEmpty()
override fun getReady(): Map<File, Set<String>> {
val result = mutableMapOf<File, Set<String>>()
defFiles@for ((defFile, headers) in pendingDefFilesToHeaders) {
val depends = mutableSetOf<String>()
headers@for (header in (headers.ownHeaders + headers.importedHeaders)) {
val dependency = processedHeadersToDefFiles[header]
?: if (header in headers.ownHeaders) continue@headers else continue@defFiles
depends.add(dependency.nameWithoutExtension)
}
result[defFile] = depends
}
if (result.isEmpty()) {
pendingDefFilesToHeaders.entries.forEach { (def, headers) ->
println(def.name)
println("Own headers:")
headers.ownHeaders.forEach { println(it) }
println("Unowned imported headers:")
headers.importedHeaders.forEach { if (it !in processedHeadersToDefFiles) println(it) }
println()
}
error("Cyclic dependency? Remaining libs:\n" + pendingDefFilesToHeaders.keys.joinToString("\n") { it.name })
}
return result
}
override fun markDone(file: File) {
val headers = pendingDefFilesToHeaders.remove(file)!!
headers.ownHeaders.forEach {
processedHeadersToDefFiles.putIfAbsent(it, file)
}
}
}
@@ -0,0 +1,136 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.tool
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.*
const val HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX = "headerFilterAdditionalSearchPrefix"
const val NODEFAULTLIBS_DEPRECATED = "nodefaultlibs"
const val NODEFAULTLIBS = "no-default-libs"
const val NOENDORSEDLIBS = "no-endorsed-libs"
const val PURGE_USER_LIBS = "Xpurge-user-libs"
const val TEMP_DIR = "Xtemporary-files-dir"
const val NOPACK = "nopack"
const val COMPILE_SOURCES = "Xcompile-source"
const val SHORT_MODULE_NAME = "Xshort-module-name"
const val FOREIGN_EXCEPTION_MODE = "Xforeign-exception-mode"
// TODO: unify camel and snake cases.
// Possible solution is to accept both cases
open class CommonInteropArguments(val argParser: ArgParser) {
val verbose by argParser.option(ArgType.Boolean, description = "Enable verbose logging output").default(false)
val pkg by argParser.option(ArgType.String, description = "place generated bindings to the package")
val output by argParser.option(ArgType.String, shortName = "o", description = "specifies the resulting library file")
.default("nativelib")
val libraryPath by argParser.option(ArgType.String, description = "add a library search path")
.multiple().delimiter(",")
val staticLibrary by argParser.option(ArgType.String, description = "embed static library to the result")
.multiple().delimiter(",")
val library by argParser.option(ArgType.String, shortName = "l", description = "library to use for building")
.multiple()
val repo by argParser.option(ArgType.String, shortName = "r", description = "repository to resolve dependencies")
.multiple()
val mode by argParser.option(ArgType.Choice(listOf(MODE_METADATA, MODE_SOURCECODE)), description = "the way interop library is generated")
.default(DEFAULT_MODE)
val nodefaultlibs by argParser.option(ArgType.Boolean, NODEFAULTLIBS,
description = "don't link the libraries from dist/klib automatically").default(false)
val nodefaultlibsDeprecated by argParser.option(ArgType.Boolean, NODEFAULTLIBS_DEPRECATED,
description = "don't link the libraries from dist/klib automatically",
deprecatedWarning = "Old form of flag. Please, use $NODEFAULTLIBS.").default(false)
val noendorsedlibs by argParser.option(ArgType.Boolean, NOENDORSEDLIBS,
description = "don't link the endorsed libraries from dist automatically").default(false)
val purgeUserLibs by argParser.option(ArgType.Boolean, PURGE_USER_LIBS,
description = "don't link unused libraries even explicitly specified").default(false)
val nopack by argParser.option(ArgType.Boolean, fullName = NOPACK,
description = "Don't pack the produced library into a klib file").default(false)
val tempDir by argParser.option(ArgType.String, TEMP_DIR,
description = "save temporary files to the given directory")
val kotlincOption by argParser.option(ArgType.String, "Xkotlinc-option",
description = "additional kotlinc compiler option").multiple()
companion object {
const val MODE_SOURCECODE = "sourcecode"
const val MODE_METADATA = "metadata"
const val DEFAULT_MODE = MODE_METADATA
}
}
open class CInteropArguments(argParser: ArgParser =
ArgParser("cinterop",
prefixStyle = ArgParser.OptionPrefixStyle.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.String, description = "native target to compile to").default("host")
val def by argParser.option(ArgType.String, description = "the library definition file")
val header by argParser.option(ArgType.String, description = "header file to produce kotlin bindings for")
.multiple().delimiter(",")
val headerFilterPrefix by argParser.option(ArgType.String, HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, "hfasp",
"header file to produce kotlin bindings for").multiple().delimiter(",")
val compilerOpts by argParser.option(ArgType.String,
description = "additional compiler options (allows to add several options separated by spaces)",
deprecatedWarning = "-compilerOpts is deprecated. Please use -compiler-options.")
.multiple().delimiter(" ")
val compilerOptions by argParser.option(ArgType.String, "compiler-options",
description = "additional compiler options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val linkerOpts = argParser.option(ArgType.String, "linkerOpts",
description = "additional linker options (allows to add several options separated by spaces)",
deprecatedWarning = "-linkerOpts is deprecated. Please use -linker-options.")
.multiple().delimiter(" ")
val linkerOptions = argParser.option(ArgType.String, "linker-options",
description = "additional linker options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val compilerOption by argParser.option(ArgType.String, "compiler-option",
description = "additional compiler option").multiple()
val linkerOption = argParser.option(ArgType.String, "linker-option",
description = "additional linker option").multiple()
val linker by argParser.option(ArgType.String, description = "use specified linker")
val compileSource by argParser.option(ArgType.String,
fullName = COMPILE_SOURCES,
description = "additional C/C++ sources to be compiled into resulting library"
).multiple()
val sourceCompileOptions by argParser.option(ArgType.String,
fullName = "Xsource-compiler-option",
description = "compiler options for sources provided via -$COMPILE_SOURCES"
).multiple()
val shortModuleName by argParser.option(ArgType.String,
fullName = SHORT_MODULE_NAME,
description = "A short name used to denote this library in the IDE"
)
val moduleName by argParser.option(ArgType.String,
fullName = "Xmodule-name",
description = "A full name of the library used for dependency resolution"
)
val foreignExceptionMode by argParser.option(ArgType.String, FOREIGN_EXCEPTION_MODE,
description = "Handle native exception in Kotlin: <terminate|objc-wrap>")
}
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop",
prefixStyle = ArgParser.OptionPrefixStyle.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.Choice(listOf("wasm32")),
description = "wasm target to compile to").default("wasm32")
}
internal fun warn(msg: String) {
println("warning: $msg")
}
@@ -0,0 +1,13 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.tool.CommonInteropArguments
enum class GenerationMode {
SOURCE_CODE, METADATA
}
fun parseGenerationMode(mode: String): GenerationMode? = when(mode) {
CommonInteropArguments.MODE_METADATA -> GenerationMode.METADATA
CommonInteropArguments.MODE_SOURCECODE -> GenerationMode.SOURCE_CODE
else -> null
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
/**
* Describes the native library and the options for adjusting the Kotlin API to be generated for this library.
*/
class InteropConfiguration(
val library: CompilationWithPCH,
val pkgName: String,
val excludedFunctions: Set<String>,
val excludedMacros: Set<String>,
val strictEnums: Set<String>,
val nonStrictEnums: Set<String>,
val noStringConversion: Set<String>,
val exportForwardDeclarations: List<String>,
val disableDesignatedInitializerChecks: Boolean,
val target: KonanTarget
)
enum class KotlinPlatform {
JVM,
NATIVE
}
@@ -0,0 +1,108 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen.jvm
import kotlinx.metadata.*
import kotlinx.metadata.klib.KlibModuleFragmentWriteStrategy
import kotlinx.metadata.klib.KlibModuleMetadata
import kotlinx.metadata.klib.className
import kotlinx.metadata.klib.fqName
import org.jetbrains.kotlin.backend.common.serialization.KlibIrVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.konan.CompilerVersion
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.impl.KonanLibraryWriterImpl
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibraryVersioning
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.util.removeSuffixIfPresent
import java.util.*
fun createInteropLibrary(
metadata: KlibModuleMetadata,
outputPath: String,
moduleName: String,
nativeBitcodeFiles: List<String>,
target: KonanTarget,
manifest: Properties,
dependencies: List<KotlinLibrary>,
nopack: Boolean,
shortName: String?,
staticLibraries: List<String>
) {
val version = KotlinLibraryVersioning(
libraryVersion = null,
abiVersion = KotlinAbiVersion.CURRENT,
compilerVersion = CompilerVersion.CURRENT.toString(),
metadataVersion = KlibMetadataVersion.INSTANCE.toString(),
irVersion = KlibIrVersion.INSTANCE.toString()
)
val outputPathWithoutExtension = outputPath.removeSuffixIfPresent(".klib")
KonanLibraryWriterImpl(
File(outputPathWithoutExtension),
moduleName,
version,
target,
BuiltInsPlatform.NATIVE,
nopack = nopack,
shortName = shortName
).apply {
val serializedMetadata = metadata.write(ChunkingWriteStrategy())
addMetadata(SerializedMetadata(serializedMetadata.header, serializedMetadata.fragments, serializedMetadata.fragmentNames))
nativeBitcodeFiles.forEach(this::addNativeBitcode)
addManifestAddend(manifest)
addLinkDependencies(dependencies)
staticLibraries.forEach(this::addIncludedBinary)
commit()
}
}
// TODO: Consider adding it to kotlinx-metadata-klib.
class ChunkingWriteStrategy(
private val classesChunkSize: Int = 128,
private val packagesChunkSize: Int = 128
) : KlibModuleFragmentWriteStrategy {
override fun processPackageParts(parts: List<KmModuleFragment>): List<KmModuleFragment> {
if (parts.isEmpty()) return emptyList()
val fqName = parts.first().fqName
?: error("KmModuleFragment should have a not-null fqName!")
val classFragments = parts.flatMap(KmModuleFragment::classes)
.chunked(classesChunkSize) { chunk ->
KmModuleFragment().also { fragment ->
fragment.fqName = fqName
fragment.classes += chunk
chunk.mapTo(fragment.className, KmClass::name)
}
}
val packageFragments = parts.mapNotNull(KmModuleFragment::pkg)
.flatMap { it.functions + it.typeAliases + it.properties }
.chunked(packagesChunkSize) { chunk ->
KmModuleFragment().also { fragment ->
fragment.fqName = fqName
fragment.pkg = KmPackage().also { pkg ->
pkg.fqName = fqName
pkg.properties += chunk.filterIsInstance<KmProperty>()
pkg.functions += chunk.filterIsInstance<KmFunction>()
pkg.typeAliases += chunk.filterIsInstance<KmTypeAlias>()
}
}
}
val result = classFragments + packageFragments
return if (result.isEmpty()) {
// We still need to emit empty packages because they may
// represent parts of package declaration (e.g. platform.[]).
// Tooling (e.g. `klib contents`) expects this kind of behavior.
parts
} else {
result
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.tool
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.target.customerDistribution
import org.jetbrains.kotlin.konan.util.KonanHomeProvider
import org.jetbrains.kotlin.konan.util.defaultTargetSubstitutions
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
class ToolConfig(userProvidedTargetName: String?, flavor: KotlinPlatform) {
private val konanHome = KonanHomeProvider.determineKonanHome()
private val distribution = customerDistribution(konanHome)
private val platformManager = PlatformManager(distribution)
private val targetManager = platformManager.targetManager(userProvidedTargetName)
private val host = HostManager.host
val target = targetManager.target
private val platform = platformManager.platform(target)
val clang = platform.clang
val substitutions = defaultTargetSubstitutions(target)
fun downloadDependencies() = platform.downloadDependencies()
val defaultCompilerOpts =
platform.clang.targetLibclangArgs.toList()
val platformCompilerOpts = if (flavor == KotlinPlatform.JVM)
platform.clang.hostCompilerArgsForJni.toList() else emptyList()
val llvmHome = platform.absoluteLlvmHome
val sysRoot = platform.absoluteTargetSysRoot
val libclang = when (host) {
KonanTarget.MINGW_X64 -> "$llvmHome/bin/libclang.dll"
else -> "$llvmHome/lib/${System.mapLibraryName("clang")}"
}
}
@@ -0,0 +1,527 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.konan.TempFiles
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.util.DefFile
import org.jetbrains.kotlin.native.interop.gen.*
import org.jetbrains.kotlin.native.interop.gen.wasm.processIdlLib
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.native.interop.tool.*
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.required
import org.jetbrains.kotlin.konan.ForeignExceptionMode
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.util.KonanHomeProvider
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.packageFqName
import org.jetbrains.kotlin.library.resolver.impl.KotlinLibraryResolverImpl
import org.jetbrains.kotlin.library.resolver.impl.libraryResolver
import org.jetbrains.kotlin.library.toUnresolvedLibraries
import org.jetbrains.kotlin.util.removeSuffixIfPresent
import java.io.File
import java.lang.IllegalArgumentException
import java.nio.file.*
import java.util.*
data class InternalInteropOptions(val generated: String, val natives: String, val manifest: String? = null,
val cstubsName: String? = null)
fun main(args: Array<String>) {
// Adding flavor option for interop plugin.
class FullCInteropArguments: CInteropArguments() {
val flavor by argParser.option(ArgType.Choice(listOf("jvm", "native", "wasm")), description = "Interop target")
.default("jvm")
val generated by argParser.option(ArgType.String, description = "place generated bindings to the directory")
.required()
val natives by argParser.option(ArgType.String, description = "where to put the built native files")
.required()
}
val arguments = FullCInteropArguments()
arguments.argParser.parse(args)
val flavorName = arguments.flavor
processCLib(flavorName, arguments, InternalInteropOptions(arguments.generated, arguments.natives))
}
fun interop(
flavor: String, args: Array<String>,
additionalArgs: InternalInteropOptions
): Array<String>? = when(flavor) {
"jvm", "native" -> {
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(args)
processCLib(flavor, cinteropArguments, additionalArgs)
}
"wasm" -> processIdlLib(args, additionalArgs)
else -> error("Unexpected flavor")
}
// Options, whose values are space-separated and can be escaped.
val escapedOptions = setOf("-compilerOpts", "-linkerOpts", "-compiler-options", "-linker-options")
private fun String.asArgList(key: String) =
if (escapedOptions.contains(key))
this.split(Regex("(?<!\\\\)\\Q \\E")).filter { it.isNotEmpty() }.map { it.replace("\\ ", " ") }
else
listOf(this)
private fun <T> Collection<T>.atMostOne(): T? {
return when (this.size) {
0 -> null
1 -> this.iterator().next()
else -> throw IllegalArgumentException("Collection has more than one element.")
}
}
private fun List<String>?.isTrue(): Boolean {
// The rightmost wins, null != "true".
return this?.last() == "true"
}
private fun runCmd(command: Array<String>, verbose: Boolean = false) {
Command(*command).getOutputLines(true).let { lines ->
if (verbose) lines.forEach(::println)
}
}
private fun Properties.storeProperties(file: File) {
file.outputStream().use {
this.store(it, null)
}
}
private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace: (Any, Any, Any) -> Unit) {
val oldValue = this[key]
if (oldValue != null && oldValue != newValue) {
beforeReplace(key, oldValue, newValue)
}
this[key] = newValue
}
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
val languages = mapOf(
"C" to Language.C,
"Objective-C" to Language.OBJECTIVE_C
)
val language = config.language ?: return Language.C
return languages[language] ?:
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
}
private fun parseImports(dependencies: List<KotlinLibrary>): ImportsImpl =
dependencies.filterIsInstance<KonanLibrary>().mapNotNull { library ->
// TODO: handle missing properties?
library.packageFqName?.let { packageFqName ->
val headerIds = library.includedHeaders
headerIds.map { HeaderId(it) to PackageInfo(packageFqName, library) }
}
}.reversed().flatten().toMap().let(::ImportsImpl)
fun getCompilerFlagsForVfsOverlay(headerFilterPrefix: Array<String>, def: DefFile): List<String> {
val relativeToRoot = mutableMapOf<Path, Path>() // TODO: handle clashes
val filteredIncludeDirs = headerFilterPrefix .map { Paths.get(it) }
if (filteredIncludeDirs.isNotEmpty()) {
val headerFilterGlobs = def.config.headerFilter
if (headerFilterGlobs.isEmpty()) {
error("'$HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX' option requires " +
"'headerFilter' to be specified in .def file")
}
relativeToRoot += findFilesByGlobs(roots = filteredIncludeDirs, globs = headerFilterGlobs)
}
if (relativeToRoot.isEmpty()) {
return emptyList()
}
val virtualRoot = Paths.get(System.getProperty("java.io.tmpdir")).resolve("konanSystemInclude")
val virtualPathToReal = relativeToRoot.map { (relativePath, realRoot) ->
virtualRoot.resolve(relativePath) to realRoot.resolve(relativePath)
}.toMap()
val vfsOverlayFile = createVfsOverlayFile(virtualPathToReal)
return listOf("-I${virtualRoot.toAbsolutePath()}", "-ivfsoverlay", vfsOverlayFile.toAbsolutePath().toString())
}
private fun findFilesByGlobs(roots: List<Path>, globs: List<String>): Map<Path, Path> {
val relativeToRoot = mutableMapOf<Path, Path>()
val pathMatchers = globs.map { FileSystems.getDefault().getPathMatcher("glob:$it") }
roots.reversed()
.filter { path ->
return@filter when {
path.toFile().exists() -> true
else -> { warn("$path doesn't exist"); false }
}
}
.forEach { root ->
// TODO: don't scan the entire tree, skip subdirectories according to globs.
Files.walk(root, FileVisitOption.FOLLOW_LINKS).forEach { path ->
val relativePath = root.relativize(path)
if (!Files.isDirectory(path) && pathMatchers.any { it.matches(relativePath) }) {
relativeToRoot[relativePath] = root
}
}
}
return relativeToRoot
}
private fun processCLib(flavorName: String, cinteropArguments: CInteropArguments,
additionalArgs: InternalInteropOptions): Array<String>? {
val ktGenRoot = additionalArgs.generated
val nativeLibsDir = additionalArgs.natives
val flavor = KotlinPlatform.values().single { it.name.equals(flavorName, ignoreCase = true) }
val defFile = cinteropArguments.def?.let { File(it) }
val manifestAddend = additionalArgs.manifest?.let { File(it) }
if (defFile == null && cinteropArguments.pkg == null) {
cinteropArguments.argParser.printError("-def or -pkg should be provided!")
}
val tool = prepareTool(cinteropArguments.target, flavor)
val def = DefFile(defFile, tool.substitutions)
val isLinkerOptsSetByUser = (cinteropArguments.linkerOpts.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.linkerOptions.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.linkerOption.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER)
if (flavorName == "native" && isLinkerOptsSetByUser) {
warn("-linker-option(s)/-linkerOpts option is not supported by cinterop. Please add linker options to .def file or binary compilation instead.")
}
val additionalLinkerOpts = cinteropArguments.linkerOpts.value.toTypedArray() + cinteropArguments.linkerOption.value.toTypedArray() +
cinteropArguments.linkerOptions.value.toTypedArray()
val verbose = cinteropArguments.verbose
val language = selectNativeLanguage(def.config)
val entryPoint = def.config.entryPoints.atMostOne()
val linkerOpts =
def.config.linkerOpts.toTypedArray() +
tool.defaultCompilerOpts +
additionalLinkerOpts
val linkerName = cinteropArguments.linker ?: def.config.linker
val linker = "${tool.llvmHome}/bin/$linkerName"
val compiler = "${tool.llvmHome}/bin/clang"
val excludedFunctions = def.config.excludedFunctions.toSet()
val excludedMacros = def.config.excludedMacros.toSet()
val staticLibraries = def.config.staticLibraries + cinteropArguments.staticLibrary.toTypedArray()
val libraryPaths = def.config.libraryPaths + cinteropArguments.libraryPath.toTypedArray()
val fqParts = (cinteropArguments.pkg ?: def.config.packageName)?.split('.')
?: defFile!!.name.split('.').reversed().drop(1)
val outKtPkg = fqParts.joinToString(".")
val mode = run {
val providedMode = parseGenerationMode(cinteropArguments.mode)
?: error ("Unexpected interop generation mode: ${cinteropArguments.mode}")
if (providedMode == GenerationMode.METADATA && flavor == KotlinPlatform.JVM) {
warn("Metadata mode isn't supported for Kotlin/JVM! Falling back to sourcecode.")
GenerationMode.SOURCE_CODE
} else {
providedMode
}
}
val resolver = getLibraryResolver(cinteropArguments, tool.target)
val allLibraryDependencies = when (flavor) {
KotlinPlatform.NATIVE -> resolveDependencies(resolver, cinteropArguments)
else -> listOf()
}
val libName = additionalArgs.cstubsName ?: fqParts.joinToString("") + "stubs"
val tempFiles = TempFiles(libName, cinteropArguments.tempDir)
val imports = parseImports(allLibraryDependencies)
val library = buildNativeLibrary(tool, def, cinteropArguments, imports)
val (nativeIndex, compilation) = buildNativeIndex(library, verbose)
// Our current approach to arm64_32 support is to compile armv7k version of bitcode
// for arm64_32. That's the reason for this substitution.
// TODO: Add proper support with the next LLVM update.
val target = when (tool.target) {
KonanTarget.WATCHOS_ARM64 -> KonanTarget.WATCHOS_ARM32
else -> tool.target
}
val klibSuffix = CompilerOutputKind.LIBRARY.suffix(target)
val moduleName = cinteropArguments.moduleName
?: File(cinteropArguments.output).name.removeSuffixIfPresent(klibSuffix)
val configuration = InteropConfiguration(
library = compilation,
pkgName = outKtPkg,
excludedFunctions = excludedFunctions,
excludedMacros = excludedMacros,
strictEnums = def.config.strictEnums.toSet(),
nonStrictEnums = def.config.nonStrictEnums.toSet(),
noStringConversion = def.config.noStringConversion.toSet(),
exportForwardDeclarations = def.config.exportForwardDeclarations,
disableDesignatedInitializerChecks = def.config.disableDesignatedInitializerChecks,
target = target
)
File(nativeLibsDir).mkdirs()
val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}")
val logger = if (verbose) {
{ message: String -> println(message) }
} else {
{}
}
val stubIrContext = StubIrContext(logger, configuration, nativeIndex, imports, flavor, mode, libName)
val stubIrOutput = run {
val outKtFileCreator = {
val outKtFileName = fqParts.last() + ".kt"
val outKtFileRelative = (fqParts + outKtFileName).joinToString("/")
val file = File(ktGenRoot, outKtFileRelative)
file.parentFile.mkdirs()
file
}
val driverOptions = StubIrDriver.DriverOptions(entryPoint, moduleName, File(outCFile.absolutePath), outKtFileCreator)
val stubIrDriver = StubIrDriver(stubIrContext, driverOptions)
stubIrDriver.run()
}
// TODO: if a library has partially included headers, then it shouldn't be used as a dependency.
def.manifestAddendProperties["includedHeaders"] = nativeIndex.includedHeaders.joinToString(" ") { it.value }
def.manifestAddendProperties.putAndRunOnReplace("package", outKtPkg) {
_, oldValue, newValue ->
warn("The package value `$oldValue` specified in .def file is overridden with explicit $newValue")
}
def.manifestAddendProperties["interop"] = "true"
if (stubIrOutput is StubIrDriver.Result.Metadata) {
def.manifestAddendProperties["ir_provider"] = KLIB_INTEROP_IR_PROVIDER_IDENTIFIER
}
stubIrContext.addManifestProperties(def.manifestAddendProperties)
// cinterop command line option overrides def file property
val foreignExceptionMode = cinteropArguments.foreignExceptionMode?: def.config.foreignExceptionMode
foreignExceptionMode?.let {
def.manifestAddendProperties[ForeignExceptionMode.manifestKey] =
ForeignExceptionMode.byValue(it).value // may throw IllegalArgumentException
}
manifestAddend?.parentFile?.mkdirs()
manifestAddend?.let { def.manifestAddendProperties.storeProperties(it) }
val compilerArgs = stubIrContext.libraryForCStubs.compilerArgs.toTypedArray()
val nativeOutputPath: String = when (flavor) {
KotlinPlatform.JVM -> {
val outOFile = tempFiles.create(libName,".o")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-c", outCFile.absolutePath, "-o", outOFile.absolutePath)
runCmd(compilerCmd, verbose)
val outLib = File(nativeLibsDir, System.mapLibraryName(libName))
val linkerCmd = arrayOf(linker,
outOFile.absolutePath, "-shared", "-o", outLib.absolutePath,
*linkerOpts)
runCmd(linkerCmd, verbose)
outOFile.absolutePath
}
KotlinPlatform.NATIVE -> {
val outLib = File(nativeLibsDir, "$libName.bc")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-emit-llvm", "-c", outCFile.absolutePath, "-o", outLib.absolutePath)
runCmd(compilerCmd, verbose)
outLib.absolutePath
}
}
val compiledFiles = compileSources(nativeLibsDir, tool, cinteropArguments)
return when (stubIrOutput) {
is StubIrDriver.Result.SourceCode -> {
val bitcodePaths = compiledFiles.map { listOf("-native-library", it) }.flatten()
argsToCompiler(staticLibraries, libraryPaths) + bitcodePaths
}
is StubIrDriver.Result.Metadata -> {
val stdlibDependency = resolver.resolveWithDependencies(
emptyList(),
noDefaultLibs = true,
noEndorsedLibs = true
).getFullList()
createInteropLibrary(
metadata = stubIrOutput.metadata,
nativeBitcodeFiles = compiledFiles + nativeOutputPath,
target = tool.target,
moduleName = moduleName,
outputPath = cinteropArguments.output,
manifest = def.manifestAddendProperties,
dependencies = stdlibDependency + imports.requiredLibraries.toList(),
nopack = cinteropArguments.nopack,
shortName = cinteropArguments.shortModuleName,
staticLibraries = resolveLibraries(staticLibraries, libraryPaths)
)
return null
}
}
}
private fun compileSources(
nativeLibsDir: String,
toolConfig: ToolConfig,
cinteropArguments: CInteropArguments
): List<String> = cinteropArguments.compileSource.mapIndexed { index, source ->
// Mangle file name to avoid collisions.
val mangledFileName = "${index}_${File(source).nameWithoutExtension}"
val outputFileName = "$nativeLibsDir/${mangledFileName}.bc"
val compilerArgs = cinteropArguments.sourceCompileOptions.toTypedArray()
val compilerCmd = toolConfig.clang.clangCXX(*compilerArgs, source, "-emit-llvm", "-c", "-o", outputFileName)
runCmd(compilerCmd.toTypedArray(), verbose = cinteropArguments.verbose)
outputFileName
}
private fun getLibraryResolver(
cinteropArguments: CInteropArguments, target: KonanTarget
): KotlinLibraryResolverImpl<KonanLibrary> {
val libraries = cinteropArguments.library
val repos = cinteropArguments.repo
return defaultResolver(
repos,
libraries.filter { it.contains(org.jetbrains.kotlin.konan.file.File.separator) },
target,
Distribution(KonanHomeProvider.determineKonanHome())
).libraryResolver()
}
private fun resolveDependencies(
resolver: KotlinLibraryResolverImpl<KonanLibrary>, cinteropArguments: CInteropArguments
): List<KotlinLibrary> {
val libraries = cinteropArguments.library
val noDefaultLibs = cinteropArguments.nodefaultlibs || cinteropArguments.nodefaultlibsDeprecated
val noEndorsedLibs = cinteropArguments.noendorsedlibs
return resolver.resolveWithDependencies(
libraries.toUnresolvedLibraries,
noStdLib = false,
noDefaultLibs = noDefaultLibs,
noEndorsedLibs = noEndorsedLibs
).getFullList()
}
internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
val tool = ToolConfig(target, flavor)
tool.downloadDependencies()
System.load(tool.libclang)
return tool
}
internal fun buildNativeLibrary(
tool: ToolConfig,
def: DefFile,
arguments: CInteropArguments,
imports: ImportsImpl
): NativeLibrary {
val additionalHeaders = (arguments.header).toTypedArray()
val additionalCompilerOpts = (arguments.compilerOpts +
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val language = selectNativeLanguage(def.config)
val compilerOpts: List<String> = mutableListOf<String>().apply {
addAll(def.config.compilerOpts)
addAll(tool.defaultCompilerOpts)
// We compile with -O2 because Clang may insert inline asm in bitcode at -O0.
// It is undesirable in case of watchos_arm64 since we target armv7k
// for this target instead of arm64_32 because it is not supported in LLVM 8.
//
// Note that PCH and the *.c file should be compiled with the same optimization level.
add("-O2")
addAll(additionalCompilerOpts)
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
addAll(when (language) {
Language.C -> emptyList()
Language.OBJECTIVE_C -> {
// "Objective-C" within interop means "Objective-C with ARC":
listOf("-fobjc-arc")
// Using this flag here has two effects:
// 1. The headers are parsed with ARC enabled, thus the API is visible correctly.
// 2. The generated Objective-C stubs are compiled with ARC enabled, so reference counting
// calls are inserted automatically.
}
})
}
val compilation = CompilationImpl(
includes = headerFiles,
additionalPreambleLines = def.defHeaderLines,
compilerArgs = compilerOpts + tool.platformCompilerOpts,
language = language
)
val headerFilter: NativeLibraryHeaderFilter
val includes: List<String>
val modules = def.config.modules
if (modules.isEmpty()) {
val excludeDependentModules = def.config.excludeDependentModules
val headerFilterGlobs = def.config.headerFilter
val headerInclusionPolicy = HeaderInclusionPolicyImpl(headerFilterGlobs)
headerFilter = NativeLibraryHeaderFilter.NameBased(headerInclusionPolicy, excludeDependentModules)
includes = headerFiles
} else {
require(language == Language.OBJECTIVE_C) { "cinterop supports 'modules' only when 'language = Objective-C'" }
require(headerFiles.isEmpty()) { "cinterop doesn't support having headers and modules specified at the same time" }
require(def.config.headerFilter.isEmpty()) { "cinterop doesn't support 'headerFilter' with 'modules'" }
val modulesInfo = getModulesInfo(compilation, modules)
headerFilter = NativeLibraryHeaderFilter.Predefined(modulesInfo.ownHeaders)
includes = modulesInfo.topLevelHeaders
}
val excludeSystemLibs = def.config.excludeSystemLibs
val headerExclusionPolicy = HeaderExclusionPolicyImpl(imports)
return NativeLibrary(
includes = includes,
additionalPreambleLines = compilation.additionalPreambleLines,
compilerArgs = compilation.compilerArgs,
headerToIdMapper = HeaderToIdMapper(sysRoot = tool.sysRoot),
language = compilation.language,
excludeSystemLibs = excludeSystemLibs,
headerExclusionPolicy = headerExclusionPolicy,
headerFilter = headerFilter
)
}
@@ -0,0 +1,410 @@
package org.jetbrains.kotlin.native.interop.gen.wasm
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.native.interop.gen.argsToCompiler
import org.jetbrains.kotlin.native.interop.gen.wasm.idl.*
import org.jetbrains.kotlin.native.interop.gen.jvm.InternalInteropOptions
import org.jetbrains.kotlin.native.interop.tool.JSInteropArguments
fun kotlinHeader(packageName: String): String {
return "package $packageName\n" +
"import kotlinx.wasm.jsinterop.*\n"
}
fun Type.toKotlinType(argName: String? = null): String = when (this) {
is idlVoid -> "Unit"
is idlInt -> "Int"
is idlFloat -> "Float"
is idlDouble -> "Double"
is idlString -> "String"
is idlObject -> "JsValue"
is idlFunction -> "KtFunction<R${argName!!}>"
is idlInterfaceRef -> name
else -> error("Unexpected type")
}
fun Arg.wasmMapping(): String = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> name
is idlFloat -> name
is idlDouble -> "doubleUpper($name), doubleLower($name)"
is idlString -> "stringPointer($name), stringLengthBytes($name)"
is idlObject -> TODO("implement me")
is idlFunction -> "wrapFunction<R$name>($name), ArenaManager.currentArena"
is idlInterfaceRef -> TODO("Implement me")
else -> error("Unexpected type")
}
fun Type.wasmReturnArg(): String =
when (this) {
is idlVoid -> "ArenaManager.currentArena" // TODO: optimize.
is idlInt -> "ArenaManager.currentArena"
is idlFloat -> "ArenaManager.currentArena"
is idlDouble -> "ArenaManager.currentArena"
is idlString -> "ArenaManager.currentArena"
is idlObject -> "ArenaManager.currentArena"
is idlFunction -> "ArenaManager.currentArena"
is idlInterfaceRef -> "ArenaManager.currentArena"
else -> error("Unexpected type")
}
val Operation.wasmReturnArg: String get() = returnType.wasmReturnArg()
val Attribute.wasmReturnArg: String get() = type.wasmReturnArg()
fun Arg.wasmArgNames(): List<String> = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> listOf(name)
is idlFloat -> listOf(name)
is idlDouble -> listOf("${name}Upper", "${name}Lower")
is idlString -> listOf("${name}Ptr", "${name}Len")
is idlObject -> TODO("implement me (idlObject)")
is idlFunction -> listOf("${name}Index", "${name}ResultArena")
is idlInterfaceRef -> TODO("Implement me (idlInterfaceRef)")
else -> error("Unexpected type")
}
fun Type.wasmReturnMapping(value: String): String = when (this) {
is idlVoid -> ""
is idlInt -> value
is idlFloat -> value
is idlDouble -> value
is idlString -> "TODO(\"Implement me\")"
is idlObject -> "JsValue(ArenaManager.currentArena, $value)"
is idlFunction -> "TODO(\"Implement me\")"
is idlInterfaceRef -> "$name(ArenaManager.currentArena, $value)"
else -> error("Unexpected type")
}
fun wasmFunctionName(functionName: String, interfaceName: String)
= "knjs__${interfaceName}_$functionName"
fun wasmSetterName(propertyName: String, interfaceName: String)
= "knjs_set__${interfaceName}_$propertyName"
fun wasmGetterName(propertyName: String, interfaceName: String)
= "knjs_get__${interfaceName}_$propertyName"
val Operation.kotlinTypeParameters: String get() {
val lambdaRetTypes = args.filter { it.type is idlFunction }
.map { "R${it.name}" }. joinToString(", ")
return if (lambdaRetTypes == "") "" else "<$lambdaRetTypes>"
}
val Interface.wasmReceiverArgs get() =
if (isGlobal) emptyList()
else listOf("this.arena", "this.index")
fun Member.wasmReceiverArgs(parent: Interface) =
if (isStatic) emptyList()
else parent.wasmReceiverArgs
fun Type.generateKotlinCall(name: String, wasmArgList: String) =
"$name($wasmArgList)"
fun Type.generateKotlinCallWithReturn(name: String, wasmArgList: String) =
when(this) {
is idlVoid -> " ${generateKotlinCall(name, wasmArgList)}\n"
is idlDouble -> " ${generateKotlinCall(name, wasmArgList)}\n" +
" val wasmRetVal = ReturnSlot_getDouble()\n"
else -> " val wasmRetVal = ${generateKotlinCall(name, wasmArgList)}\n"
}
fun Operation.generateKotlinCallWithReturn(parent_name: String, wasmArgList: String) =
returnType.generateKotlinCallWithReturn(
wasmFunctionName(name, parent_name),
wasmArgList)
fun Attribute.generateKotlinGetterCallWithReturn(parent_name: String, wasmArgList: String) =
type.generateKotlinCallWithReturn(
wasmGetterName(name, parent_name),
wasmArgList)
fun Operation.generateKotlin(parent: Interface): String {
val argList = args.map {
"${it.name}: ${it.type.toKotlinType(it.name)}"
}.joinToString(", ")
val wasmArgList = (wasmReceiverArgs(parent) + args.map(Arg::wasmMapping) + wasmReturnArg).joinToString(", ")
// TODO: there can be multiple Rs.
return " fun $kotlinTypeParameters $name(" +
argList +
"): ${returnType.toKotlinType()} {\n" +
generateKotlinCallWithReturn(parent.name, wasmArgList) +
" return ${returnType.wasmReturnMapping("wasmRetVal")}\n"+
" }\n\n"
}
fun Attribute.generateKotlinSetter(parent: Interface): String {
val kotlinType = type.toKotlinType(name)
return " set(value: $kotlinType) {\n" +
" ${wasmSetterName(name, parent.name)}(" +
(wasmReceiverArgs(parent) + Arg("value", type).wasmMapping()).joinToString(", ") +
")\n" +
" }\n\n"
}
fun Attribute.generateKotlinGetter(parent: Interface): String {
val wasmArgList = (wasmReceiverArgs(parent) + wasmReturnArg).joinToString(", ")
return " get() {\n" +
generateKotlinGetterCallWithReturn(parent.name, wasmArgList) +
" return ${type.wasmReturnMapping("wasmRetVal")}\n"+
" }\n\n"
}
fun Attribute.generateKotlin(parent: Interface): String {
val kotlinType = type.toKotlinType(name)
val varOrVal = if (readOnly) "val" else "var"
return " $varOrVal $name: $kotlinType\n" +
generateKotlinGetter(parent) +
if (!readOnly) generateKotlinSetter(parent) else ""
}
val Interface.wasmTypedReceiverArgs get() =
if (isGlobal) emptyList()
else listOf("arena: Int", "index: Int")
fun Member.wasmTypedReceiverArgs(parent: Interface) =
if (isStatic) emptyList() else parent.wasmTypedReceiverArgs
fun Operation.generateWasmStub(parent: Interface): String {
val wasmName = wasmFunctionName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + args.toList().wasmTypedMapping() + wasmTypedReturnMapping).joinToString(", ")
return "@SymbolName(\"$wasmName\")\n" +
"external public fun $wasmName($allArgs): ${returnType.wasmReturnTypeMapping()}\n\n"
}
fun Attribute.generateWasmSetterStub(parent: Interface): String {
val wasmSetter = wasmSetterName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + Arg("value", this.type).wasmTypedMapping()).joinToString(", ")
return "@SymbolName(\"$wasmSetter\")\n" +
"external public fun $wasmSetter($allArgs): Unit\n\n"
}
fun Attribute.generateWasmGetterStub(parent: Interface): String {
val wasmGetter = wasmGetterName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + wasmTypedReturnMapping).joinToString(", ")
return "@SymbolName(\"$wasmGetter\")\n" +
"external public fun $wasmGetter($allArgs): Int\n\n"
}
fun Attribute.generateWasmStubs(parent: Interface) =
generateWasmGetterStub(parent) +
if (!readOnly) generateWasmSetterStub(parent) else ""
// TODO: consider using virtual methods
fun Member.generateKotlin(parent: Interface): String = when (this) {
is Operation -> this.generateKotlin(parent)
is Attribute -> this.generateKotlin(parent)
else -> error("Unexpected member")
}
// TODO: consider using virtual methods
fun Member.generateWasmStub(parent: Interface) =
when (this) {
is Operation -> this.generateWasmStub(parent)
is Attribute -> this.generateWasmStubs(parent)
else -> error("Unexpected member")
}
fun Arg.wasmTypedMapping()
= this.wasmArgNames().map { "$it: Int" } .joinToString(", ")
// TODO: Optimize for simple types.
fun Type.wasmTypedReturnMapping(): String = "resultArena: Int"
val Operation.wasmTypedReturnMapping get() = returnType.wasmTypedReturnMapping()
val Attribute.wasmTypedReturnMapping get() = type.wasmTypedReturnMapping()
fun List<Arg>.wasmTypedMapping():List<String>
= this.map(Arg::wasmTypedMapping)
// TODO: more complex return types, such as returning a pair of Ints
// will require a more complex approach.
fun Type.wasmReturnTypeMapping()
= if (this == idlVoid) "Unit" else "Int"
fun Interface.generateMemberWasmStubs() =
members.map {
it.generateWasmStub(this)
}.joinToString("")
fun Interface.generateKotlinMembers() =
members.filterNot { it.isStatic } .map {
it.generateKotlin(this)
}.joinToString("")
fun Interface.generateKotlinCompanion() =
" companion object {\n" +
members.filter { it.isStatic } .map {
it.generateKotlin(this)
}.joinToString("") +
" }\n"
fun Interface.generateKotlinClassHeader() =
"open class $name(arena: Int, index: Int): JsValue(arena, index) {\n" +
" constructor(jsValue: JsValue): this(jsValue.arena, jsValue.index)\n"
fun Interface.generateKotlinClassFooter() =
"}\n"
fun Interface.generateKotlinClassConverter() =
"val JsValue.as$name: $name\n" +
" get() {\n" +
" return $name(this.arena, this.index)\n"+
" }\n"
fun Interface.generateKotlin(): String {
fun unlessGlobal(value: () -> String): String {
return if (this.isGlobal) "" else value()
}
return generateMemberWasmStubs() +
unlessGlobal { generateKotlinClassHeader() } +
generateKotlinMembers() +
unlessGlobal {
generateKotlinCompanion() +
generateKotlinClassFooter() +
generateKotlinClassConverter()
}
}
fun generateKotlin(pkg: String, interfaces: List<Interface>) =
kotlinHeader(pkg) +
interfaces.map {
it.generateKotlin()
}.joinToString("\n") +
if (pkg == "kotlinx.interop.wasm.dom") // TODO: make it a general solution.
"fun <R> setInterval(interval: Int, lambda: KtFunction<R>) = setInterval(lambda, interval)\n"
else ""
/////////////////////////////////////////////////////////
fun Arg.composeWasmArgs(): String = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> ""
is idlFloat -> ""
is idlDouble ->
" var $name = twoIntsToDouble(${name}Upper, ${name}Lower);\n"
is idlString ->
" var $name = toUTF16String(${name}Ptr, ${name}Len);\n"
is idlObject -> TODO("implement me")
is idlFunction ->
" var $name = konan_dependencies.env.Konan_js_wrapLambda(lambdaResultArena, ${name}Index);\n"
is idlInterfaceRef -> TODO("Implement me")
else -> error("Unexpected type")
}
val Interface.receiver get() =
if (isGlobal) "" else "kotlinObject(arena, obj)."
fun Member.receiver(parent: Interface) =
if (isStatic) "${parent.name}." else parent.receiver
val Interface.wasmReceiverArgName get() =
if (isGlobal) emptyList() else listOf("arena", "obj")
fun Member.wasmReceiverArgName(parent: Interface) =
if (isStatic) emptyList() else parent.wasmReceiverArgName
val Operation.wasmReturnArgName get() =
returnType.wasmReturnArgName
val Attribute.wasmReturnArgName get() =
type.wasmReturnArgName
val Type.wasmReturnArgName get() =
when (this) {
is idlVoid -> emptyList()
is idlInt -> emptyList()
is idlFloat -> emptyList()
is idlDouble -> emptyList()
is idlString -> listOf("resultArena")
is idlObject -> listOf("resultArena")
is idlInterfaceRef -> listOf("resultArena")
else -> error("Unexpected type: $this")
}
val Type.wasmReturnExpression get() =
when(this) {
is idlVoid -> ""
is idlInt -> "result"
is idlFloat -> "result" // TODO: can we really pass floats as is?
is idlDouble -> "doubleToReturnSlot(result)"
is idlString -> "toArena(resultArena, result)"
is idlObject -> "toArena(resultArena, result)"
is idlInterfaceRef -> "toArena(resultArena, result)"
else -> error("Unexpected type: $this")
}
fun Operation.generateJs(parent: Interface): String {
val allArgs = wasmReceiverArgName(parent) + args.map { it.wasmArgNames() }.flatten() + wasmReturnArgName
val wasmMapping = allArgs.joinToString(", ")
val argList = args.map { it.name }. joinToString(", ")
val composedArgsList = args.map { it.composeWasmArgs() }. joinToString("")
return "\n ${wasmFunctionName(this.name, parent.name)}: function($wasmMapping) {\n" +
composedArgsList +
" var result = ${receiver(parent)}$name($argList);\n" +
" return ${returnType.wasmReturnExpression};\n" +
" }"
}
fun Attribute.generateJsSetter(parent: Interface): String {
val valueArg = Arg("value", type)
val allArgs = wasmReceiverArgName(parent) + valueArg.wasmArgNames()
val wasmMapping = allArgs.joinToString(", ")
return "\n ${wasmSetterName(name, parent.name)}: function($wasmMapping) {\n" +
valueArg.composeWasmArgs() +
" ${receiver(parent)}$name = value;\n" +
" }"
}
fun Attribute.generateJsGetter(parent: Interface): String {
val allArgs = wasmReceiverArgName(parent) + wasmReturnArgName
val wasmMapping = allArgs.joinToString(", ")
return "\n ${wasmGetterName(name, parent.name)}: function($wasmMapping) {\n" +
" var result = ${receiver(parent)}$name;\n" +
" return ${type.wasmReturnExpression};\n" +
" }"
}
fun Attribute.generateJs(parent: Interface) =
generateJsGetter(parent) +
if (!readOnly) ",\n${generateJsSetter(parent)}" else ""
fun Member.generateJs(parent: Interface): String = when (this) {
is Operation -> this.generateJs(parent)
is Attribute -> this.generateJs(parent)
else -> error("Unexpected member")
}
fun generateJs(interfaces: List<Interface>): String =
"konan.libraries.push ({\n" +
interfaces.map { interf ->
interf.members.map { member ->
member.generateJs(interf)
}
}.flatten() .joinToString(",\n") +
"\n})\n"
const val idlMathPackage = "kotlinx.interop.wasm.math"
const val idlDomPackage = "kotlinx.interop.wasm.dom"
fun processIdlLib(args: Array<String>, additionalArgs: InternalInteropOptions): Array<String> {
val jsInteropArguments = JSInteropArguments()
jsInteropArguments.argParser.parse(args)
// TODO: Refactor me.
val ktGenRoot = File(additionalArgs.generated).mkdirs()
val nativeLibsDir = File(additionalArgs.natives).mkdirs()
val idl = when (jsInteropArguments.pkg) {
idlMathPackage -> idlMath
idlDomPackage -> idlDom
else -> throw IllegalArgumentException("Please choose either $idlMathPackage or $idlDomPackage for -pkg argument")
}
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(jsInteropArguments.pkg!!, idl))
File(nativeLibsDir, "js_stubs.js").writeText(generateJs(idl))
File((additionalArgs.manifest)!!).writeText("") // The manifest is currently unused for wasm.
return argsToCompiler(jsInteropArguments.staticLibrary.toTypedArray(), jsInteropArguments.libraryPath.toTypedArray())
}
@@ -0,0 +1,50 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// This shall be an output of Web IDL parser.
val idlDom = listOf(
Interface("Context",
Attribute("lineWidth", idlInt),
Attribute("fillStyle", idlString),
Attribute("strokeStyle", idlString),
Operation("lineTo", idlVoid, Arg("x", idlInt), Arg("y", idlInt)),
Operation("moveTo", idlVoid, Arg("x", idlInt), Arg("y", idlInt)),
Operation("beginPath", idlVoid),
Operation("stroke", idlVoid),
Operation("fillRect", idlVoid, Arg("x", idlInt), Arg("y", idlInt), Arg("width", idlInt), Arg("height", idlInt)),
Operation("fillText", idlVoid, Arg("test", idlString), Arg("x", idlInt), Arg("y", idlInt), Arg("maxWidth", idlInt)),
Operation("fill", idlVoid),
Operation("closePath", idlVoid)
),
Interface("DOMRect",
Attribute("left", idlInt),
Attribute("right", idlInt),
Attribute("top", idlInt),
Attribute("bottom", idlInt)
),
Interface("Canvas",
Operation("getContext", idlInterfaceRef("Context"), Arg("context", idlString)),
Operation("getBoundingClientRect", idlInterfaceRef("DOMRect"))
),
Interface("Document",
Operation("getElementById", idlObject, Arg("id", idlString))
),
Interface("MouseEvent",
Attribute("clientX", idlInt, readOnly = true),
Attribute("clientY", idlInt, readOnly = true)
),
Interface("Response",
Operation("json", idlObject)
),
Interface("Promise",
Operation("then", idlInterfaceRef("Promise"), Arg("lambda", idlFunction))
),
Interface("__Global",
Attribute("document", idlInterfaceRef("Document"), readOnly = true),
Operation("fetch", idlInterfaceRef("Promise"), Arg("url", idlString)),
Operation("setInterval", idlVoid, Arg("lambda", idlFunction), Arg("interval", idlInt))
)
)
@@ -0,0 +1,37 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// This is (as of now) a poor man's IDL representation.
interface Type
interface Member {
val isStatic: Boolean get() = false
}
object idlVoid: Type
object idlInt: Type
object idlFloat: Type
object idlDouble: Type
object idlString: Type
object idlObject: Type
object idlFunction: Type
data class Attribute(val name: String, val type: Type,
val readOnly: Boolean = false,
override val isStatic: Boolean = false): Member
data class Arg(val name: String, val type: Type)
class Operation(val name: String, val returnType: Type,
override val isStatic: Boolean = false,
vararg val args: Arg): Member {
constructor(name: String, returnType: Type, vararg args: Arg) :
this(name, returnType, false, *args)
}
data class idlInterfaceRef(val name: String): Type
class Interface(val name: String, vararg val members: Member) {
val isGlobal = (name == "__Global")
}
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// There are no WebIDL descriptions of Math,
// so in any case this one will be a part of the project.
// Although, may be in the form of our own WebIDL source.
val idlMath = listOf(
Interface("Math",
Attribute("E", idlDouble, readOnly = true, isStatic = true),
Attribute("LN2", idlDouble, readOnly = true, isStatic = true),
Attribute("LN10", idlDouble, readOnly = true, isStatic = true),
Attribute("LOG2E", idlDouble, readOnly = true, isStatic = true),
Attribute("LOG10E", idlDouble, readOnly = true, isStatic = true),
Attribute("PI", idlDouble, readOnly = true, isStatic = true),
Attribute("SQRT1_2", idlDouble, readOnly = true, isStatic = true),
Attribute("SQRT2", idlDouble, readOnly = true, isStatic = true),
Operation("abs", idlDouble, true, Arg("x", idlDouble)),
Operation("acos", idlDouble, true, Arg("x", idlDouble)),
Operation("acosh", idlDouble, true, Arg("x", idlDouble)),
Operation("asin", idlDouble, true, Arg("x", idlDouble)),
Operation("asinh", idlDouble, true, Arg("x", idlDouble)),
Operation("atan", idlDouble, true, Arg("x", idlDouble)),
Operation("atanh", idlDouble, true, Arg("x", idlDouble)),
Operation("atan2", idlDouble, true, Arg("y", idlDouble), Arg("x", idlDouble)),
Operation("cbrt", idlDouble, true, Arg("x", idlDouble)),
Operation("ceil", idlDouble, true, Arg("x", idlDouble)),
Operation("clz32", idlDouble, true, Arg("x", idlDouble)),
Operation("cos", idlDouble, true, Arg("x", idlDouble)),
Operation("cosh", idlDouble, true, Arg("x", idlDouble)),
Operation("exp", idlDouble, true, Arg("x", idlDouble)),
Operation("expm1", idlDouble, true, Arg("x", idlDouble)),
Operation("floor", idlDouble, true, Arg("x", idlDouble)),
Operation("fround", idlDouble, true, Arg("x", idlDouble)),
//Operation("imul(x, y),
Operation("log", idlDouble, true, Arg("x", idlDouble)),
Operation("log1p", idlDouble, true, Arg("x", idlDouble)),
Operation("log10", idlDouble, true, Arg("x", idlDouble)),
Operation("log2", idlDouble, true, Arg("x", idlDouble)),
Operation("pow", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("random", idlDouble, true),
Operation("round", idlDouble, true, Arg("x", idlDouble)),
Operation("sign", idlDouble, true, Arg("x", idlDouble)),
Operation("sin", idlDouble, true, Arg("x", idlDouble)),
Operation("sinh", idlDouble, true, Arg("x", idlDouble)),
Operation("sqrt", idlDouble, true, Arg("x", idlDouble)),
Operation("tan", idlDouble, true, Arg("x", idlDouble)),
Operation("tanh", idlDouble, true, Arg("x", idlDouble)),
Operation("trunc", idlDouble, true, Arg("x", idlDouble)),
// Actually these functions have vararg parameter.
// But their kotlin analogs have only 2 parameters so we don't need to support varargs here.
Operation("hypot", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("max", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("min", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble))
)
)
@@ -0,0 +1,102 @@
package org.jetbrains.kotlin.native.interop.gen
import kotlinx.metadata.KmAnnotationArgument
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmModuleFragment
import kotlinx.metadata.klib.compileTimeValue
import kotlinx.metadata.klib.uniqId
import org.jetbrains.kotlin.native.interop.indexer.FunctionDecl
import org.jetbrains.kotlin.native.interop.indexer.IntegerConstantDef
import org.jetbrains.kotlin.native.interop.indexer.IntegerType
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class StubIrToMetadataTests {
companion object {
val intStubType = ClassifierStubType(Classifier.topLevel("kotlin", "Int"))
val intType = IntegerType(4, true, "int")
}
private fun createTrivialFunction(name: String): FunctionStub {
val cDeclaration = FunctionDecl(name, emptyList(), intType, "", false, false)
val origin = StubOrigin.Function(cDeclaration)
return FunctionStub(
name = cDeclaration.name,
returnType = intStubType,
parameters = listOf(),
origin = origin,
annotations = emptyList(),
external = true,
receiver = null,
modality = MemberStubModality.FINAL
)
}
private fun createTrivialIntegerConstantProperty(name: String, value: Long): PropertyStub {
val origin = StubOrigin.Constant(IntegerConstantDef(name, intType, value))
return PropertyStub(
name = name,
type = intStubType,
kind = PropertyStub.Kind.Constant(IntegralConstantStub(value, intType.size, true)),
origin = origin
)
}
private fun createMetadata(
fqName: String,
functions: List<FunctionStub> = emptyList(),
properties: List<PropertyStub> = emptyList()
) = SimpleStubContainer(functions = functions, properties = properties)
.let { ModuleMetadataEmitter(fqName, it).emit() }
.also(this::checkUniqIdPresence)
private fun checkUniqIdPresence(metadata: KmModuleFragment) {
metadata.classes.forEach { assertNotNull(it.uniqId) }
metadata.pkg?.let { pkg ->
pkg.functions.forEach { assertNotNull(it.uniqId) }
pkg.properties.forEach { assertNotNull(it.uniqId) }
pkg.typeAliases.forEach { assertNotNull(it.uniqId) }
}
}
@Test
fun `single simple function`() {
val packageName = "single_function"
val function = createTrivialFunction("hello")
val metadata = createMetadata(packageName, functions = listOf(function))
with (metadata) {
assertEquals(packageName, packageName)
assertTrue(classes.isEmpty())
assertNotNull(pkg)
assertTrue(pkg!!.functions.size == 1)
val kmFunction = pkg!!.functions[0]
assertEquals(kmFunction.name, function.name)
assertEquals(0, kmFunction.valueParameters.size)
val returnTypeClassifier = kmFunction.returnType.classifier
assertTrue(returnTypeClassifier is KmClassifier.Class)
assertEquals("kotlin/Int", returnTypeClassifier.name)
}
}
@Test
fun `single constant`() {
val property = createTrivialIntegerConstantProperty("meaning", 42)
val metadata = createMetadata("single_property", properties = listOf(property))
with (metadata) {
assertNotNull(pkg)
assertTrue(pkg!!.properties.size == 1)
val kmProperty = pkg!!.properties[0]
assertEquals(kmProperty.name, property.name)
val compileTimeValue = kmProperty.compileTimeValue
assertNotNull(compileTimeValue)
assertTrue(compileTimeValue is KmAnnotationArgument.IntValue)
assertEquals(42, compileTimeValue.value)
}
}
}
+245
View File
@@ -0,0 +1,245 @@
# Kotlin/Native libraries
## Kotlin compiler specifics
To produce a library with the Kotlin/Native compiler use the `-produce library` or `-p library` flag. For example:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ kotlinc foo.kt -p library -o bar
```
</div>
the above command will produce a `bar.klib` with the compiled contents of `foo.kt`.
To link to a library use the `-library <name>` or `-l <name>` flag. For example:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ kotlinc qux.kt -l bar
```
</div>
the above command will produce a `program.kexe` out of `qux.kt` and `bar.klib`
## cinterop tool specifics
The **cinterop** tool produces `.klib` wrappers for native libraries as its main output.
For example, using the simple `libgit2.def` native library definition file provided in your Kotlin/Native distribution
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ cinterop -def samples/gitchurn/src/nativeInterop/cinterop/libgit2.def -compiler-option -I/usr/local/include -o libgit2
```
</div>
we will obtain `libgit2.klib`.
See more details in [INTEROP.md](INTEROP.md)
## klib utility
The **klib** library management utility allows you to inspect and install the libraries.
The following commands are available.
To list library contents:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib contents <name>
```
</div>
To inspect the bookkeeping details of the library
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib info <name>
```
</div>
To install the library to the default location use
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib install <name>
```
</div>
To remove the library from the default repository use
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib remove <name>
```
</div>
All of the above commands accept an additional `-repository <directory>` argument for specifying a repository different to the default one.
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib <command> <name> -repository <directory>
```
</div>
## Several examples
First let's create a library.
Place the tiny library source code into `kotlinizer.kt`:
<div class="sample" markdown="1" theme="idea" mode="shell">
```kotlin
package kotlinizer
val String.kotlinized
get() = "Kotlin $this"
```
```bash
$ kotlinc kotlinizer.kt -p library -o kotlinizer
```
</div>
The library has been created in the current directory:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ ls kotlinizer.klib
kotlinizer.klib
```
</div>
Now let's check out the contents of the library:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib contents kotlinizer
```
</div>
We can install `kotlinizer` to the default repository:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ klib install kotlinizer
```
</div>
Remove any traces of it from the current directory:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ rm kotlinizer.klib
```
</div>
Create a very short program and place it into a `use.kt` :
<div class="sample" markdown="1" theme="idea" data-highlight-only>
```kotlin
import kotlinizer.*
fun main(args: Array<String>) {
println("Hello, ${"world".kotlinized}!")
}
```
</div>
Now compile the program linking with the library we have just created:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ kotlinc use.kt -l kotlinizer -o kohello
```
</div>
And run the program:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
$ ./kohello.kexe
Hello, Kotlin world!
```
</div>
Have fun!
# Advanced topics
## Library search sequence
When given a `-library foo` flag, the compiler searches the `foo` library in the following order:
* Current compilation directory or an absolute path.
* All repositories specified with `-repo` flag.
* Libraries installed in the default repository (For now the default is `~/.konan`, however it could be changed by setting **KONAN_DATA_DIR** environment variable).
* Libraries installed in `$installation/klib` directory.
## The library format
Kotlin/Native libraries are zip files containing a predefined
directory structure, with the following layout:
**foo.klib** when unpacked as **foo/** gives us:
```yaml
- foo/
- $component_name/
- ir/
- Seriaized Kotlin IR.
- targets/
- $platform/
- kotlin/
- Kotlin compiled to LLVM bitcode.
- native/
- Bitcode files of additional native objects.
- $another_platform/
- There can be several platform specific kotlin and native pairs.
- linkdata/
- A set of ProtoBuf files with serialized linkage metadata.
- resources/
- General resources such as images. (Not used yet).
- manifest - A file in *java property* format describing the library.
```
An example layout can be found in `klib/stdlib` directory of your installation.

Some files were not shown because too many files have changed in this diff Show More