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:
@@ -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
|
||||
...
|
||||
|
||||
@@ -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/
|
||||
@@ -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).
|
||||
@@ -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 Kotlin’s 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
|
||||
@@ -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 haven’t 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 haven’t 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).
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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`.
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
Generated
+22
@@ -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>
|
||||
Generated
+23
@@ -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>
|
||||
Generated
+11
@@ -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>
|
||||
Generated
+11
@@ -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>
|
||||
Generated
+10
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
+35
@@ -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)
|
||||
}
|
||||
}
|
||||
+1046
File diff suppressed because it is too large
Load Diff
+322
@@ -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
|
||||
}
|
||||
+138
@@ -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
|
||||
}
|
||||
+331
@@ -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
|
||||
+770
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
+41
@@ -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>
|
||||
+97
@@ -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
|
||||
}
|
||||
}
|
||||
+134
@@ -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`?
|
||||
}
|
||||
+94
@@ -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)
|
||||
}
|
||||
}
|
||||
+103
@@ -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()
|
||||
}
|
||||
+91
@@ -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)"
|
||||
+72
@@ -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))
|
||||
+346
@@ -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 + "_")
|
||||
+44
@@ -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()
|
||||
|
||||
+46
@@ -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
|
||||
}
|
||||
+207
@@ -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
|
||||
}
|
||||
}
|
||||
+578
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
+602
@@ -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
|
||||
}
|
||||
+101
@@ -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>
|
||||
}
|
||||
|
||||
+253
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+83
@@ -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
|
||||
}
|
||||
+529
@@ -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)
|
||||
}
|
||||
+319
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
+407
@@ -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())
|
||||
}
|
||||
}
|
||||
+171
@@ -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("}")
|
||||
}
|
||||
}
|
||||
}
|
||||
+727
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -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
|
||||
}
|
||||
+600
@@ -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
|
||||
}
|
||||
}
|
||||
+663
@@ -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
|
||||
}
|
||||
}
|
||||
+223
@@ -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
|
||||
}
|
||||
}
|
||||
+22
@@ -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
|
||||
}
|
||||
+100
@@ -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(")")
|
||||
}
|
||||
}
|
||||
+164
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+136
@@ -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")
|
||||
}
|
||||
+13
@@ -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
|
||||
}
|
||||
+41
@@ -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
|
||||
}
|
||||
+108
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -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")}"
|
||||
}
|
||||
}
|
||||
+527
@@ -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
|
||||
)
|
||||
}
|
||||
+410
@@ -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())
|
||||
}
|
||||
+50
@@ -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))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
+37
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
+73
@@ -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))
|
||||
)
|
||||
)
|
||||
+102
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user