[MERGE] KT: build-1.5.0-dev-1616 KT/N: abfbfcde4 OLD: 8bb76c67e

This commit is contained in:
Nikolay Krasko
2021-01-19 13:37:43 +03:00
1065 changed files with 33426 additions and 14933 deletions
@@ -10,6 +10,6 @@ import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.file.*
fun produceCAdapterBitcode(clang: ClangArgs, cppFileName: String, bitcodeFileName: String) {
val clangCommand = clang.clangCXX("-std=c++14", cppFileName, "-emit-llvm", "-c", "-o", bitcodeFileName)
val clangCommand = clang.clangCXX("-std=c++17", cppFileName, "-emit-llvm", "-c", "-o", bitcodeFileName)
Command(clangCommand).execute()
}
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.LinkerOutputKind
import org.jetbrains.kotlin.name.Name
internal fun TypeBridge.makeNothing() = when (this) {
@@ -274,6 +275,8 @@ internal class ObjCExportCodeGenerator(
emitSelectorsHolder()
emitStaticInitializers()
emitKt42254Hint()
}
private fun emitTypeAdapters() {
@@ -345,6 +348,24 @@ internal class ObjCExportCodeGenerator(
context.llvm.otherStaticInitializers += initializer
}
private fun emitKt42254Hint() {
if (determineLinkerOutput(context) == LinkerOutputKind.STATIC_LIBRARY) {
// Might be affected by https://youtrack.jetbrains.com/issue/KT-42254.
// The code below generally follows [replaceExternalWeakOrCommonGlobal] implementation.
if (context.llvmModuleSpecification.importsKotlinDeclarationsFromOtherObjectFiles()) {
// So the compiler uses caches. If a user is linking two such static frameworks into a single binary,
// the linker might fail with a lot of "duplicate symbol" errors due to KT-42254.
// Adding a similar symbol that would explicitly hint to take a look at the YouTrack issue if reported.
// Note: for some reason this symbol is reported as the last one, which is good for its purpose.
val name = "See https://youtrack.jetbrains.com/issue/KT-42254"
val global = staticData.placeGlobal(name, Int8(0), isExported = true)
context.llvm.usedGlobals += global.llvmGlobal
LLVMSetVisibility(global.llvmGlobal, LLVMVisibility.LLVMHiddenVisibility)
}
}
}
// TODO: consider including this into ObjCExportCodeSpec.
private val objCClassForAny = ObjCClassForKotlinClass(
namer.kotlinAnyName.binaryName,
@@ -488,6 +509,8 @@ private fun ObjCExportCodeGenerator.replaceExternalWeakOrCommonGlobal(
// but it is simpler to do this for all globals, considering that all usages can't be removed by DCE anyway.
context.llvm.usedGlobals += global.llvmGlobal
LLVMSetVisibility(global.llvmGlobal, LLVMVisibility.LLVMHiddenVisibility)
// See also [emitKt42254Hint].
}
}
}
@@ -5,35 +5,46 @@
package org.jetbrains.kotlin.backend.konan.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
import org.jetbrains.kotlin.backend.common.lower.at
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.renderCompilerError
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationBase
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrSymbolOwner
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.file
import org.jetbrains.kotlin.ir.util.irCall
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
/**
* This pass runs after inlining and performs the following additional transformations over some operations:
* - Convert immutableBlobOf() arguments to special IrConst.
* - Convert `obj::class` and `Class::class` to calls.
*/
internal class PostInlineLowering(val context: Context) : FileLoweringPass {
internal class PostInlineLowering(val context: Context) : BodyLoweringPass {
private val symbols get() = context.ir.symbols
override fun lower(irFile: IrFile) {
irFile.transformChildrenVoid(object : IrBuildingTransformer(context) {
override fun lower(irBody: IrBody, container: IrDeclaration) {
val irFile = container.file
irBody.transformChildren(object : IrElementTransformer<IrBuilderWithScope> {
override fun visitDeclaration(declaration: IrDeclarationBase, data: IrBuilderWithScope) =
super.visitDeclaration(declaration,
data = (declaration as? IrSymbolOwner)?.let { context.createIrBuilder(it.symbol, it.startOffset, it.endOffset) }
?: data
)
override fun visitClassReference(expression: IrClassReference): IrExpression {
expression.transformChildrenVoid()
override fun visitClassReference(expression: IrClassReference, data: IrBuilderWithScope): IrExpression {
expression.transformChildren(this, data)
return builder.at(expression).run {
return data.at(expression).run {
(expression.symbol as? IrClassSymbol)?.let { irKClass(this@PostInlineLowering.context, it) }
?:
// E.g. for `T::class` in a body of an inline function itself.
@@ -41,10 +52,10 @@ internal class PostInlineLowering(val context: Context) : FileLoweringPass {
}
}
override fun visitGetClass(expression: IrGetClass): IrExpression {
expression.transformChildrenVoid()
override fun visitGetClass(expression: IrGetClass, data: IrBuilderWithScope): IrExpression {
expression.transformChildren(this, data)
return builder.at(expression).run {
return data.at(expression).run {
irCall(symbols.kClassImplConstructor, listOf(expression.argument.type)).apply {
val typeInfo = irCall(symbols.getObjectTypeInfo).apply {
putValueArgument(0, expression.argument)
@@ -55,8 +66,8 @@ internal class PostInlineLowering(val context: Context) : FileLoweringPass {
}
}
override fun visitCall(expression: IrCall): IrExpression {
expression.transformChildrenVoid(this)
override fun visitCall(expression: IrCall, data: IrBuilderWithScope): IrExpression {
expression.transformChildren(this, data)
// Function inlining is changing function symbol at callsite
// and unbound symbol replacement is happening later.
@@ -83,13 +94,16 @@ internal class PostInlineLowering(val context: Context) : FileLoweringPass {
context.irBuiltIns.stringType,
IrConstKind.String, builder.toString()))
} else if (Symbols.isTypeOfIntrinsic(expression.symbol)) {
return with (KTypeGenerator(context, irFile, expression, needExactTypeParameters = true)) {
builder.at(expression).irKType(expression.getTypeArgument(0)!!, leaveReifiedForLater = false)
// Inline functions themselves are not called (they have been inlined at all call sites),
// so it is ok not to build exact type parameters for them.
val needExactTypeParameters = (container as? IrSimpleFunction)?.isInline != true
return with (KTypeGenerator(context, irFile, expression, needExactTypeParameters)) {
data.at(expression).irKType(expression.getTypeArgument(0)!!, leaveReifiedForLater = false)
}
}
return expression
}
})
}, data = context.createIrBuilder((container as IrSymbolOwner).symbol, irBody.startOffset, irBody.endOffset))
}
}
@@ -567,7 +567,8 @@ private class BackendChecker(val context: Context, val irFile: IrFile) : IrEleme
if (type !is IrSimpleType)
return
val classifier = type.classifier
if (classifier is IrTypeParameterSymbol)
if (classifier is IrTypeParameterSymbol
&& !classifier.owner.isReified /* Reified may be substituted with valid types later */)
checkIrKTypeParameter(irElement, classifier.owner, seenTypeParameters)
type.arguments.forEach {
@@ -265,7 +265,8 @@ private fun ObjCExportMapper.bridgeReturnType(
}
}
descriptor.containingDeclaration == descriptor.builtIns.any && descriptor.name.asString() == "hashCode" -> {
descriptor.containingDeclaration.let { it is ClassDescriptor && KotlinBuiltIns.isAny(it) } &&
descriptor.name.asString() == "hashCode" -> {
assert(!convertExceptionsToErrors)
MethodBridge.ReturnValue.HashCode
}
+1 -1
View File
@@ -16,7 +16,7 @@ linker = clang++
linkerOpts = -fvisibility-inlines-hidden \
-Wall -W -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers \
-pedantic -Wno-long-long -Wcovered-switch-default -Wnon-virtual-dtor -Wdelete-non-virtual-dtor \
-std=c++14 \
-std=c++17 \
-DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \
-ldebugInfo -lcoverageMapping
@@ -3723,7 +3723,7 @@ createInterop("concurrentTerminate") {
it.headers "$projectDir/interop/concurrentTerminate/async.h"
// TODO: Using `-Xcompile-source` does not imply dependency on that source, so the task will no re-run when the source is updated.
it.extraOpts "-Xcompile-source", "$projectDir/interop/concurrentTerminate/async.cpp"
it.extraOpts "-Xsource-compiler-option", "-std=c++11"
it.extraOpts "-Xsource-compiler-option", "-std=c++17"
}
createInterop("incomplete_types") {
@@ -76,3 +76,13 @@ fun test_reifiedUpperBound() {
assertTrue((t as KTypeParameter).isReified)
assertEquals("T", (t as KTypeParameter).name)
}
@OptIn(kotlin.ExperimentalStdlibApi::class)
inline fun <reified T : Comparable<T>> recursionInReified() = typeOf<List<T>>()
@Test
fun test_recursionInReified() {
val l = recursionInReified<Int>()
assertEquals(List::class, l.classifier)
assertEquals(Int::class, l.arguments.single().type!!.classifier)
}
@@ -1,7 +1,7 @@
import kotlin.reflect.*
@OptIn(kotlin.ExperimentalStdlibApi::class)
inline fun <reified T : Comparable<T>> foo() {
inline fun <T : Comparable<T>> foo() {
typeOf<List<T>>()
}
@@ -50,6 +50,22 @@ class ExecClang(private val project: Project) {
}
}
fun resolveToolchainExecutable(target: KonanTarget, executableOrNull: String?): String {
val executable = executableOrNull ?: "clang"
if (listOf("clang", "clang++").contains(executable)) {
// TODO: This is copied from `BitcodeCompiler`. Consider sharing the code instead.
val platform = platformManager.platform(target)
return if (target.family.isAppleFamily) {
"${platform.absoluteTargetToolchain}/usr/bin/$executable"
} else {
"${platform.absoluteTargetToolchain}/bin/$executable"
}
} else {
throw GradleException("unsupported clang executable: $executable")
}
}
// The bare ones invoke clang with system default sysroot.
fun execBareClang(action: Action<in ExecSpec>): ExecResult {
@@ -81,6 +97,30 @@ class ExecClang(private val project: Project) {
return this.execClang(konanArgs(target), closure)
}
// The toolchain ones execute clang from the toolchain.
fun execToolchainClang(target: String?, action: Action<in ExecSpec>): ExecResult {
return this.execToolchainClang(platformManager.targetManager(target).target, action)
}
fun execToolchainClang(target: String?, closure: Closure<in ExecSpec>): ExecResult {
return this.execToolchainClang(platformManager.targetManager(target).target, ConfigureUtil.configureUsing(closure))
}
fun execToolchainClang(target: KonanTarget, action: Action<in ExecSpec>): ExecResult {
val extendedAction = Action<ExecSpec> { execSpec ->
action.execute(execSpec)
execSpec.apply {
executable = resolveToolchainExecutable(target, executable)
}
}
return project.exec(extendedAction)
}
fun execToolchainClang(target: KonanTarget, closure: Closure<in ExecSpec>): ExecResult {
return this.execToolchainClang(target, ConfigureUtil.configureUsing(closure))
}
// These ones are private, so one has to choose either Bare or Konan.
private fun execClang(defaultArgs: List<String>, closure: Closure<in ExecSpec>): ExecResult {
@@ -68,10 +68,9 @@ open class CompileToBitcode @Inject constructor(
// Used flags provided by original build of allocator C code.
listOf("-std=gnu11", "-O3", "-Wall", "-Wextra", "-Werror")
Language.CPP ->
listOfNotNull("-std=c++14", "-Werror", "-O2",
listOfNotNull("-std=c++17", "-Werror", "-O2",
"-Wall", "-Wextra",
"-Wno-unused-parameter", // False positives with polymorphic functions.
"-Wno-unused-function", // TODO: Enable this warning when we have C++ runtime tests.
"-fPIC".takeIf { !HostManager().targetByName(target).isMINGW })
}
return commonFlags + languageFlags + compilerArgs
@@ -151,4 +150,4 @@ open class CompileToBitcode @Inject constructor(
}
}
}
}
}
@@ -17,10 +17,10 @@ import org.jetbrains.kotlin.konan.target.*
open class CompileNativeTest @Inject constructor(
@InputFile val inputFile: File,
@Input val target: String
@Input val target: KonanTarget,
) : DefaultTask() {
@OutputFile
var outputFile = project.buildDir.resolve("bin/test/$target/${inputFile.nameWithoutExtension}.o")
var outputFile = project.buildDir.resolve("bin/test/${target.name}/${inputFile.nameWithoutExtension}.o")
@Input
val clangArgs = mutableListOf<String>()
@@ -28,9 +28,16 @@ open class CompileNativeTest @Inject constructor(
@TaskAction
fun compile() {
val plugin = project.convention.getPlugin(ExecClang::class.java)
plugin.execBareClang {
it.executable = "clang++"
it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath)
if (target.family.isAppleFamily) {
plugin.execToolchainClang(target) {
it.executable = "clang++"
it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath)
}
} else {
plugin.execBareClang {
it.executable = "clang++"
it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath)
}
}
}
}
@@ -218,7 +225,7 @@ fun createTestTask(
"${testTaskName}Compile",
CompileNativeTest::class.java,
llvmLinkTask.outputFile,
target
konanTarget,
).apply {
dependsOn(llvmLinkTask)
clangArgs.addAll(clangFlags.clangFlags)
+1 -1
View File
@@ -17,4 +17,4 @@
DIR="${BASH_SOURCE[0]%/*}"
: ${DIR:="."}
"${DIR}"/run_konan konanc "$@"
"${DIR}"/run_konan kotlinc "$@"
+1 -1
View File
@@ -14,4 +14,4 @@ rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
rem See the License for the specific language governing permissions and
rem limitations under the License.
call %~dps0run_konan.bat konanc %*
call %~dps0run_konan.bat kotlinc %*
+4
View File
@@ -21,6 +21,10 @@
* Put implementation details inside `.h`/`.hpp` into a nested `namespace internal` (e.g. implementation details of module `mm` go into `namespace kotlin { namespace mm { namespace internal { ... } } }`)
* Put implementation details inside `.cpp`/`.mm` into a global anonymous `namespace`
* For `extern "C"` declarations emulate namespaces with `Kotlin_[module_name]_` prefixes.
* To mark type as move-only, privately inherit from `kotlin::MoveOnly`
* To mark type unmovable and uncopyable, privately inherit from `kotlin::Pinned`
* All heap-allocated classes should publicly inherit from `KonanAllocatorAware`
* Use `KStd*` containers and smart pointers instead of `std::*` ones.
## Naming
@@ -57,11 +57,6 @@ uint32_t Fetch32(const char *p) {
return uint32_in_expected_order(UNALIGNED_LOAD32(p));
}
uint32_t Rotate32(uint32_t val, int shift) {
// Avoid shifting by 32: doing so yields an undefined result.
return shift == 0 ? val : ((val >> shift) | (val << (32 - shift)));
}
// Bitwise right rotate. Normally this will compile to a single
// instruction, especially if the shift is a manifest constant.
uint64_t Rotate(uint64_t val, int shift) {
@@ -23,18 +23,6 @@
namespace {
constexpr uint32_t PrintableHexSize(uint32_t input_length) {
return input_length * 2;
}
void PrintableHex(const uint8_t* data, uint32_t data_length, char* hex) {
static const char* hex_digits = "0123456789ABCDEF";
for (uint32_t i = 0; i < data_length; ++i) {
*hex++ = hex_digits[(*data >> 4) & 0xf];
*hex++ = hex_digits[(*data++) & 0xf];
}
}
constexpr uint32_t PrintableBase64Size(uint32_t input_length) {
return ((input_length + 2) / 3 * 4) + 1;
}
+6 -6
View File
@@ -18,12 +18,12 @@
buildKotlinVersion=1.4.20-dev-2167
buildKotlinCompilerRepo=https://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:(id:Kotlin_KotlinPublic_Compiler),number:1.4.20-dev-2167,branch:default:any,pinned:true/artifacts/content/maven
remoteRoot=konan_tests
kotlinCompilerRepo=https://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:(id:Kotlin_KotlinPublic_Compiler),number:1.5.0-dev-1282,branch:default:any,pinned:true/artifacts/content/maven
kotlinVersion=1.5.0-dev-1282
kotlinStdlibRepo=https://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:(id:Kotlin_KotlinPublic_Compiler),number:1.5.0-dev-1282,branch:default:any,pinned:true/artifacts/content/maven
kotlinStdlibVersion=1.5.0-dev-1282
kotlinStdlibTestsVersion=1.5.0-dev-1282
testKotlinCompilerVersion=1.5.0-dev-1282
kotlinCompilerRepo=https://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:(id:Kotlin_KotlinPublic_Compiler),number:1.5.0-dev-1616,branch:default:any,pinned:true/artifacts/content/maven
kotlinVersion=1.5.0-dev-1616
kotlinStdlibRepo=https://teamcity.jetbrains.com/guestAuth/app/rest/builds/buildType:(id:Kotlin_KotlinPublic_Compiler),number:1.5.0-dev-1616,branch:default:any,pinned:true/artifacts/content/maven
kotlinStdlibVersion=1.5.0-dev-1616
kotlinStdlibTestsVersion=1.5.0-dev-1616
testKotlinCompilerVersion=1.5.0-dev-1616
konanVersion=1.5.0
# A version of Xcode required to build the Kotlin/Native compiler.
+1 -1
View File
@@ -42,7 +42,7 @@ model {
binaries.withType(StaticLibraryBinarySpec) { binary ->
if (!project.parent.convention.plugins.platformInfo.isWindows())
cppCompiler.args "-fPIC"
cppCompiler.args "--std=c++11", "-g", "-I${llvmDir}/include"
cppCompiler.args "--std=c++17", "-g", "-I${llvmDir}/include"
if (isEnabled) {
cppCompiler.args '-DLIBCLANGEXT_ENABLE=1'
}
@@ -37,7 +37,7 @@ model {
binaries.withType(StaticLibraryBinarySpec) { binary ->
if (!project.parent.convention.plugins.platformInfo.isWindows())
cppCompiler.args "-fPIC"
cppCompiler.args "--std=c++14", "-I${llvmDir}/include", "-I${projectDir}/src/main/include"
cppCompiler.args "--std=c++17", "-I${llvmDir}/include", "-I${projectDir}/src/main/include"
if (isMac()) {
cppCompiler.args "-DKONAN_MACOS=1"
} else if (isWindows()) {
+1 -1
View File
@@ -37,7 +37,7 @@ model {
binaries.withType(StaticLibraryBinarySpec) { binary ->
if (!project.parent.convention.plugins.platformInfo.isWindows())
cppCompiler.args "-fPIC"
cppCompiler.args "--std=c++14", "-I${llvmDir}/include", "-I${projectDir}/src/main/include"
cppCompiler.args "--std=c++17", "-I${llvmDir}/include", "-I${projectDir}/src/main/include"
linker.args "-L${llvmDir}/lib", "-lLLVMCore", "-lLLVMSupport"
}
binaries.withType(SharedLibraryBinarySpec) { binary ->
@@ -25,7 +25,7 @@
#include <llvm-c/DebugInfo.h>
#include "DebugInfoC.h"
/**
* c++ --std=c++14 llvmDebugInfoC/src/DebugInfoC.cpp -IllvmDebugInfoC/include/ -Idependencies/all/clang+llvm-3.9.0-darwin-macos/include -Ldependencies/all/clang+llvm-3.9.0-darwin-macos/lib -lLLVMCore -lLLVMSupport -lncurses -shared -o libLLVMDebugInfoC.dylib
* c++ --std=c++17 llvmDebugInfoC/src/DebugInfoC.cpp -IllvmDebugInfoC/include/ -Idependencies/all/clang+llvm-3.9.0-darwin-macos/include -Ldependencies/all/clang+llvm-3.9.0-darwin-macos/lib -lLLVMCore -lLLVMSupport -lncurses -shared -o libLLVMDebugInfoC.dylib
*/
namespace llvm {
@@ -129,10 +129,10 @@ class CyclicCollector {
public:
CyclicCollector() {
CHECK_CALL(pthread_mutex_init(&lock_, nullptr), "Cannot init collector mutex")
CHECK_CALL(pthread_mutex_init(&timestampLock_, nullptr), "Cannot init collector timestamp mutex")
CHECK_CALL(pthread_cond_init(&cond_, nullptr), "Cannot init collector condition")
CHECK_CALL(pthread_create(&gcThread_, nullptr, gcWorkerRoutine, this), "Cannot start collector thread")
CHECK_CALL(pthread_mutex_init(&lock_, nullptr), "Cannot init collector mutex");
CHECK_CALL(pthread_mutex_init(&timestampLock_, nullptr), "Cannot init collector timestamp mutex");
CHECK_CALL(pthread_cond_init(&cond_, nullptr), "Cannot init collector condition");
CHECK_CALL(pthread_create(&gcThread_, nullptr, gcWorkerRoutine, this), "Cannot start collector thread");
}
void clear() {
@@ -146,7 +146,7 @@ class CyclicCollector {
Locker locker(&lock_);
terminateCollector_ = true;
if (enabled) shallRunCollector_ = true;
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector")
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
}
// TODO: improve waiting for collector termination.
while (atomicGet(&terminateCollector_)) {}
@@ -173,7 +173,7 @@ class CyclicCollector {
KStdUnorderedMap<ObjHeader*, int> sideRefCounts;
int restartCount = 0;
while (!terminateCollector_) {
CHECK_CALL(pthread_cond_wait(&cond_, &lock_), "Cannot wait collector condition")
CHECK_CALL(pthread_cond_wait(&cond_, &lock_), "Cannot wait collector condition");
if (!shallRunCollector_) continue;
atomicSet(&gcRunning_, 1);
restartCount = 0;
@@ -325,7 +325,7 @@ class CyclicCollector {
// When exiting the worker - we shall collect the cyclic garbage here.
if (enabled) {
shallRunCollector_ = true;
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector")
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
}
currentAliveWorkers_--;
}
@@ -416,7 +416,7 @@ class CyclicCollector {
if (checkIfShallCollect()) {
Locker locker(&lock_);
shallRunCollector_ = true;
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector")
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
}
}
@@ -424,7 +424,7 @@ class CyclicCollector {
if (atomicGet(&gcRunning_) != 0) return;
Locker lock(&lock_);
shallRunCollector_ = true;
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector")
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
}
void localGC() {
@@ -192,7 +192,7 @@ struct CycleDetectorRootset {
KStdVector<ScopedRefHolder> heldRefs;
};
class CycleDetector : private kotlin::Pinned {
class CycleDetector : private kotlin::Pinned, public KonanAllocatorAware {
public:
static void insertCandidateIfNeeded(KRef object) {
if (canBeACandidate(object))
@@ -798,7 +798,6 @@ namespace {
void freeContainer(ContainerHeader* header) NO_INLINE;
#if USE_GC
void garbageCollect(MemoryState* state, bool force) NO_INLINE;
void cyclicGarbageCollect() NO_INLINE;
void rememberNewContainer(ContainerHeader* container);
#endif // USE_GC
@@ -1017,10 +1016,6 @@ inline FrameOverlay* asFrameOverlay(ObjHeader** slot) {
return reinterpret_cast<FrameOverlay*>(slot);
}
inline bool isRefCounted(KConstRef object) {
return isFreeable(containerFor(object));
}
inline void lock(KInt* spinlock) {
while (compareAndSwap(spinlock, 0, 1) != 0) {}
}
@@ -1169,7 +1164,7 @@ void freeAggregatingFrozenContainer(ContainerHeader* container) {
auto* state = memoryState;
RuntimeAssert(isAggregatingFrozenContainer(container), "expected fictitious frozen container");
MEMORY_LOG("%p is fictitious frozen container\n", container);
RuntimeAssert(!container->buffered(), "frozen objects must not participate in GC")
RuntimeAssert(!container->buffered(), "frozen objects must not participate in GC");
#if USE_GC
// Forbid finalizerQueue handling.
++state->finalizerQueueSuspendCount;
@@ -1698,6 +1693,7 @@ void collectWhite(MemoryState* state, ContainerHeader* start) {
}
#endif
#if COLLECT_STATISTIC
inline bool needAtomicAccess(ContainerHeader* container) {
return container->shareable();
}
@@ -1707,6 +1703,7 @@ inline bool canBeCyclic(ContainerHeader* container) {
if (container->color() == CONTAINER_TAG_GC_GREEN) return false;
return true;
}
#endif
inline void addHeapRef(ContainerHeader* container) {
MEMORY_LOG("AddHeapRef %p: rc=%d\n", container, container->refCount())
@@ -1774,6 +1771,11 @@ inline void releaseHeapRef(const ObjHeader* header) {
releaseHeapRef<Strict, CanCollect>(const_cast<ContainerHeader*>(container));
}
// TODO: Consider removing this unused stuff.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
// We use first slot as place to store frame-local arena container.
// TODO: create ArenaContainer object on the stack, so that we don't
// do two allocations per frame (ArenaContainer + actual container).
@@ -1800,6 +1802,8 @@ inline size_t containerSize(const ContainerHeader* container) {
return result;
}
#pragma clang diagnostic pop
#if USE_GC
void incrementStack(MemoryState* state) {
FrameOverlay* frame = currentFrame;
@@ -2052,7 +2056,7 @@ MemoryState* initMemory(bool firstRuntime) {
==
offsetof(MetaObjHeader, typeInfo_),
"Layout mismatch");
RuntimeAssert(sizeof(FrameOverlay) % sizeof(ObjHeader**) == 0, "Frame overlay should contain only pointers")
RuntimeAssert(sizeof(FrameOverlay) % sizeof(ObjHeader**) == 0, "Frame overlay should contain only pointers");
RuntimeAssert(memoryState == nullptr, "memory state must be clear");
memoryState = konanConstructInstance<MemoryState>();
INIT_EVENT(memoryState)
@@ -119,4 +119,42 @@ bool operator!=(
return !(x == y);
}
template <class T>
class KonanDeleter {
public:
void operator()(T* instance) noexcept { konanDestructInstance(instance); }
};
// Force a class to be heap-allocated using `konanAllocMemory`. Does not prevent stack allocation, or
// allocation as part of another object.
// Usage:
// class A : public KonanAllocatorAware {
// ...
// };
class KonanAllocatorAware {
public:
static void* operator new(size_t count) noexcept { return konanAllocMemory(count); }
static void* operator new[](size_t count) noexcept { return konanAllocMemory(count); }
static void* operator new(size_t count, void* ptr) noexcept { return ptr; }
static void* operator new[](size_t count, void* ptr) noexcept { return ptr; }
static void operator delete(void* ptr) noexcept { konanFreeMemory(ptr); }
static void operator delete[](void* ptr) noexcept { konanFreeMemory(ptr); }
protected:
// Hide constructors, assignments and destructor to discourage operating on instance of `KonanAllocatorAware`
KonanAllocatorAware() = default;
KonanAllocatorAware(const KonanAllocatorAware&) = default;
KonanAllocatorAware(KonanAllocatorAware&&) = default;
KonanAllocatorAware& operator=(const KonanAllocatorAware&) = default;
KonanAllocatorAware& operator=(KonanAllocatorAware&&) = default;
// Not virtual by design. Since this class hides this destructor, no one can destroy an
// instance of `KonanAllocatorAware` directly, so this destructor is never called in a virtual manner.
~KonanAllocatorAware() = default;
};
#endif // RUNTIME_ALLOC_H
@@ -0,0 +1,133 @@
/*
* Copyright 2010-2020 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.
*/
#include "Alloc.h"
#include <array>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "Types.h"
namespace {
class A : public KonanAllocatorAware {
public:
using DestructorHook = testing::StrictMock<testing::MockFunction<void(int)>>;
static thread_local DestructorHook* destructorHook;
explicit A(int value = -1) : value_(value) {}
~A() { destructorHook->Call(value_); }
int value() const { return value_; }
bool operator==(const A& rhs) const { return value_ == rhs.value_; }
private:
int value_;
};
// static
thread_local A::DestructorHook* A::destructorHook = nullptr;
struct B {
explicit B(int value) : a(value) {}
A a;
};
} // namespace
class KonanAllocatorAwareTest : public testing::Test {
public:
KStdUniquePtr<A::DestructorHook> destructorHook;
void SetUp() override {
Test::SetUp();
destructorHook = make_unique<A::DestructorHook>();
A::destructorHook = destructorHook.get();
}
void TearDown() override {
A::destructorHook = nullptr;
destructorHook.reset();
Test::TearDown();
}
};
TEST_F(KonanAllocatorAwareTest, AllocatedOnStack) {
A a(42);
EXPECT_THAT(a.value(), 42);
EXPECT_CALL(*destructorHook, Call(42));
}
TEST_F(KonanAllocatorAwareTest, AllocatedInAnotherObject) {
// We do not control how `B` is allocated.
B* b = new B(42);
EXPECT_THAT(b->a.value(), 42);
EXPECT_CALL(*destructorHook, Call(42));
delete b;
}
TEST_F(KonanAllocatorAwareTest, AllocatedByItself) {
A* a = new A(42);
EXPECT_THAT(a->value(), 42);
EXPECT_CALL(*destructorHook, Call(42));
delete a;
}
TEST_F(KonanAllocatorAwareTest, AllocateArray) {
constexpr size_t kCount = 5;
A* as = new A[kCount];
std::vector<int> actual;
for (A* a = as; a != as + kCount; ++a) {
actual.push_back(a->value());
}
std::array<int, kCount> expected;
for (int& element : expected) {
element = -1;
}
EXPECT_THAT(actual, testing::ElementsAreArray(expected));
EXPECT_CALL(*destructorHook, Call(-1)).Times(kCount);
delete[] as;
}
TEST_F(KonanAllocatorAwareTest, PlacementAllocated) {
std::array<uint8_t, sizeof(A)> buffer;
A* a = new (buffer.data()) A(42);
EXPECT_THAT(a->value(), 42);
EXPECT_CALL(*destructorHook, Call(42));
a->~A();
testing::Mock::VerifyAndClearExpectations(destructorHook.get());
}
TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) {
constexpr size_t kCount = 5;
std::array<uint8_t, sizeof(A) * kCount> buffer;
A* as = new (buffer.data()) A[kCount];
std::vector<int> actual;
for (A* a = as; a != as + kCount; ++a) {
actual.push_back(a->value());
}
std::array<int, kCount> expected;
for (int& element : expected) {
element = -1;
}
EXPECT_THAT(actual, testing::ElementsAreArray(expected));
EXPECT_CALL(*destructorHook, Call(-1)).Times(kCount);
for (A* a = as; a != as + kCount; ++a) {
a->~A();
}
testing::Mock::VerifyAndClearExpectations(destructorHook.get());
}
@@ -6,7 +6,6 @@
#include "Cleaner.h"
#include <future>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -14,6 +13,7 @@
#include "Atomic.h"
#include "TestSupport.hpp"
#include "TestSupportCompilerGenerated.hpp"
#include "Types.h"
using testing::_;
@@ -31,7 +31,7 @@ TEST(CleanerTest, ConcurrentCreation) {
int startedThreads = 0;
bool allowRunning = false;
std::vector<std::future<KInt>> futures;
KStdVector<std::future<KInt>> futures;
for (int i = 0; i < threadCount; ++i) {
auto future = std::async(std::launch::async, [&startedThreads, &allowRunning]() {
atomicAdd(&startedThreads, 1);
@@ -44,7 +44,7 @@ TEST(CleanerTest, ConcurrentCreation) {
while (atomicGet(&startedThreads) != threadCount) {
}
atomicSet(&allowRunning, true);
std::vector<KInt> values;
KStdVector<KInt> values;
for (auto& future : futures) {
values.push_back(future.get());
}
@@ -6,45 +6,11 @@
#ifndef RUNTIME_CPP_SUPPORT_H
#define RUNTIME_CPP_SUPPORT_H
#include <type_traits>
#include <memory>
// A collection of backported utilities from future C++ versions.
namespace kotlin {
namespace std_support {
////////////////////////// C++14 //////////////////////////
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template <typename T>
using make_unsigned_t = typename std::make_unsigned<T>::type;
////////////////////////// C++17 //////////////////////////
template <typename T>
constexpr bool is_trivially_destructible_v = std::is_trivially_destructible<T>::value;
template <typename T>
constexpr bool is_nothrow_default_constructible_v = std::is_nothrow_default_constructible<T>::value;
template <typename T>
constexpr bool is_nothrow_destructible_v = std::is_nothrow_destructible<T>::value;
template <typename T>
constexpr bool is_copy_constructible_v = std::is_copy_constructible<T>::value;
template <typename T>
constexpr bool is_copy_assignable_v = std::is_copy_assignable<T>::value;
template <typename T>
constexpr bool is_move_constructible_v = std::is_move_constructible<T>::value;
template <typename T>
constexpr bool is_move_assignable_v = std::is_move_assignable<T>::value;
template <typename T>
constexpr bool is_nothrow_move_constructible_v = std::is_nothrow_move_constructible<T>::value;
template <typename T>
constexpr bool is_nothrow_move_assignable_v = std::is_nothrow_move_assignable<T>::value;
} // namespace std_support
} // namespace kotlin
@@ -107,11 +107,13 @@ _Unwind_Reason_Code unwindCallback(
THREAD_LOCAL_VARIABLE bool disallowSourceInfo = false;
#if !OMIT_BACKTRACE && !USE_GCC_UNWIND
SourceInfo getSourceInfo(KConstRef stackTrace, int index) {
return disallowSourceInfo
? SourceInfo { .fileName = nullptr, .lineNumber = -1, .column = -1 }
: Kotlin_getSourceInfo(*PrimitiveArrayAddressOfElementAt<KNativePtr>(stackTrace->array(), index));
}
#endif
} // namespace
+26 -12
View File
@@ -25,7 +25,14 @@
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
RUNTIME_NORETURN void RuntimeAssertFailed(const char* location, const char* message, ...);
#if KONAN_ENABLE_ASSERT
#define CURRENT_SOURCE_LOCATION __FILE__ ":" TOSTRING(__LINE__)
#else
// Do not generate location strings, when asserts are disabled to reduce code size.
#define CURRENT_SOURCE_LOCATION nullptr
#endif
RUNTIME_NORETURN void RuntimeAssertFailed(const char* location, const char* format, ...) __attribute__((format(printf, 2, 3)));
namespace internal {
@@ -35,7 +42,7 @@ inline RUNTIME_NORETURN void TODOImpl(const char* location) {
// TODO: Support format string when `RuntimeAssertFailed` supports it.
inline RUNTIME_NORETURN void TODOImpl(const char* location, const char* message) {
RuntimeAssertFailed(location, message);
RuntimeAssertFailed(location, "%s", message);
}
} // namespace internal
@@ -46,24 +53,31 @@ extern "C" const int KonanNeedDebugInfo;
#if KONAN_ENABLE_ASSERT
// Use RuntimeAssert() in internal state checks, which could be ignored in production.
#define RuntimeAssert(condition, format, ...) \
if (KonanNeedDebugInfo && (!(condition))) { \
RuntimeAssertFailed( __FILE__ ":" TOSTRING(__LINE__), format, ##__VA_ARGS__); \
}
#define RuntimeAssert(condition, format, ...) \
do { \
if (KonanNeedDebugInfo && (!(condition))) { \
RuntimeAssertFailed(CURRENT_SOURCE_LOCATION, format, ##__VA_ARGS__); \
} \
} while (false)
#else
#define RuntimeAssert(condition, message)
#define RuntimeAssert(condition, format, ...) \
do { \
} while (false)
#endif
// Use RuntimeCheck() in runtime checks that could fail due to external condition and shall lead
// to program termination. Never compiled out.
#define RuntimeCheck(condition, format, ...) \
if (!(condition)) { \
RuntimeAssertFailed(nullptr, format, ##__VA_ARGS__); \
}
// TODO: Consider using `CURRENT_SOURCE_LOCATION` when `KonanNeedDebugInfo` is `true`.
#define RuntimeCheck(condition, format, ...) \
do { \
if (!(condition)) { \
RuntimeAssertFailed(nullptr, format, ##__VA_ARGS__); \
} \
} while (false)
#define TODO(...) \
do { \
::internal::TODOImpl(__FILE__ ":" TOSTRING(__LINE__), ##__VA_ARGS__); \
::internal::TODOImpl(CURRENT_SOURCE_LOCATION, ##__VA_ARGS__); \
} while (false)
#endif // RUNTIME_ASSERT_H
@@ -177,7 +177,7 @@ void BackRefFromAssociatedObject::releaseRef() {
}
void BackRefFromAssociatedObject::detach() {
RuntimeAssert(atomicGet(&refCount) == 0, "unexpected refCount")
RuntimeAssert(atomicGet(&refCount) == 0, "unexpected refCount");
obj_ = nullptr; // Handled in addRef/tryAddRef/releaseRef/ref.
}
@@ -8,7 +8,6 @@
#include <type_traits>
#include "CppSupport.hpp"
#include "Memory.h"
// TODO: Generalize for uses outside this file.
@@ -38,8 +37,7 @@ class KRefSharedHolder {
ForeignRefContext context_;
};
static_assert(
kotlin::std_support::is_trivially_destructible_v<KRefSharedHolder>, "KRefSharedHolder destructor is not guaranteed to be called.");
static_assert(std::is_trivially_destructible_v<KRefSharedHolder>, "KRefSharedHolder destructor is not guaranteed to be called.");
class BackRefFromAssociatedObject {
public:
@@ -68,7 +66,7 @@ class BackRefFromAssociatedObject {
};
static_assert(
kotlin::std_support::is_trivially_destructible_v<BackRefFromAssociatedObject>,
std::is_trivially_destructible_v<BackRefFromAssociatedObject>,
"BackRefFromAssociatedObject destructor is not guaranteed to be called.");
#endif // RUNTIME_MEMORYSHAREDREFS_HPP
@@ -11,6 +11,7 @@
#include <mutex>
#include "Mutex.hpp"
#include "Types.h"
namespace kotlin {
@@ -20,9 +21,9 @@ class MultiSourceQueue {
public:
class Producer;
// TODO: Consider switching from `std::list` to `SingleLockList` to hide the constructor
// TODO: Consider switching from `KStdList` to `SingleLockList` to hide the constructor
// and to not store the iterator.
class Node : private Pinned {
class Node : private Pinned, public KonanAllocatorAware {
public:
Node(const T& value, Producer* owner) noexcept : value_(value), owner_(owner) {}
@@ -33,7 +34,7 @@ public:
T value_;
std::atomic<Producer*> owner_; // `nullptr` signifies that `MultiSourceQueue` owns it.
typename std::list<Node>::iterator position_;
typename KStdList<Node>::iterator position_;
};
class Producer {
@@ -72,8 +73,8 @@ public:
private:
MultiSourceQueue& owner_; // weak
std::list<Node> queue_;
std::list<Node*> deletionQueue_;
KStdList<Node> queue_;
KStdList<Node*> deletionQueue_;
};
class Iterator {
@@ -92,9 +93,9 @@ public:
private:
friend class MultiSourceQueue;
explicit Iterator(const typename std::list<Node>::iterator& position) noexcept : position_(position) {}
explicit Iterator(const typename KStdList<Node>::iterator& position) noexcept : position_(position) {}
typename std::list<Node>::iterator position_;
typename KStdList<Node>::iterator position_;
};
class Iterable : MoveOnly {
@@ -118,7 +119,7 @@ public:
// Lock `MultiSourceQueue` and apply deletions. Only deletes elements that were published.
void ApplyDeletions() noexcept {
std::lock_guard<SpinLock> guard(mutex_);
std::list<Node*> remainingDeletions;
KStdList<Node*> remainingDeletions;
auto it = deletionQueue_.begin();
while (it != deletionQueue_.end()) {
@@ -139,10 +140,10 @@ public:
}
private:
// Using `std::list` as it allows to implement `Collect` without memory allocations,
// Using `KStdList` as it allows to implement `Collect` without memory allocations,
// which is important for GC mark phase.
std::list<Node> queue_;
std::list<Node*> deletionQueue_;
KStdList<Node> queue_;
KStdList<Node*> deletionQueue_;
SpinLock mutex_;
};
@@ -12,14 +12,15 @@
#include "gtest/gtest.h"
#include "TestSupport.hpp"
#include "Types.h"
using namespace kotlin;
namespace {
template <typename T>
std::vector<T> Collect(MultiSourceQueue<T>& queue) {
std::vector<T> result;
KStdVector<T> Collect(MultiSourceQueue<T>& queue) {
KStdVector<T> result;
for (const auto& element : queue.Iter()) {
result.push_back(element);
}
@@ -192,8 +193,8 @@ TEST(MultiSourceQueueTest, ConcurrentPublish) {
constexpr int kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
std::vector<int> expected;
KStdVector<std::thread> threads;
KStdVector<int> expected;
for (int i = 0; i < kThreadCount; ++i) {
expected.push_back(i);
@@ -223,8 +224,8 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) {
constexpr int kStartCount = 50;
constexpr int kThreadCount = kDefaultThreadCount;
std::vector<int> expectedBefore;
std::vector<int> expectedAfter;
KStdVector<int> expectedBefore;
KStdVector<int> expectedAfter;
IntQueue::Producer producer(queue);
for (int i = 0; i < kStartCount; ++i) {
expectedBefore.push_back(i);
@@ -236,7 +237,7 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) {
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
int j = i + kStartCount;
expectedAfter.push_back(j);
@@ -251,7 +252,7 @@ TEST(MultiSourceQueueTest, IterWhileConcurrentPublish) {
});
}
std::vector<int> actualBefore;
KStdVector<int> actualBefore;
{
auto iter = queue.Iter();
while (readyCount < kThreadCount) {
@@ -282,7 +283,7 @@ TEST(MultiSourceQueueTest, ConcurrentPublishAndApplyDeletions) {
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
threads.emplace_back([&queue, i, &canStart, &readyCount, &startedCount]() {
IntQueue::Producer producer(queue);
@@ -21,7 +21,6 @@
#include <limits>
#include <type_traits>
#include "CppSupport.hpp"
#include "KAssert.h"
#include "Exceptions.h"
#include "Memory.h"
@@ -51,7 +50,7 @@ OBJ_GETTER0(Kotlin_native_internal_undefined) {
}
void* Kotlin_interop_malloc(KLong size, KInt align) {
if (size < 0 || static_cast<kotlin::std_support::make_unsigned_t<decltype(size)>>(size) > std::numeric_limits<size_t>::max()) {
if (size < 0 || static_cast<std::make_unsigned_t<decltype(size)>>(size) > std::numeric_limits<size_t>::max()) {
return nullptr;
}
RuntimeAssert(align > 0, "Unsupported alignment");
@@ -124,7 +124,6 @@ extern "C" OBJ_GETTER(Kotlin_ObjCExport_AllocInstanceWithAssociatedObject,
}
static Class getOrCreateClass(const TypeInfo* typeInfo);
static void initializeClass(Class clazz);
extern "C" id objc_retainAutoreleaseReturnValue(id self);
@@ -768,16 +767,6 @@ static KStdVector<const TypeInfo*> getProtocolsAsInterfaces(Class clazz) {
return result;
}
static const TypeInfo* getMostSpecificKotlinClass(const TypeInfo* typeInfo) {
const TypeInfo* result = typeInfo;
while (getTypeAdapter(result) == nullptr) {
result = result->superType_;
RuntimeAssert(result != nullptr, "");
}
return result;
}
static int getVtableSize(const TypeInfo* typeInfo) {
for (const TypeInfo* current = typeInfo; current != nullptr; current = current->superType_) {
auto typeAdapter = getTypeAdapter(current);
+4 -4
View File
@@ -27,8 +27,8 @@ namespace konan {
// Console operations.
void consoleInit();
void consolePrintf(const char* format, ...);
void consoleErrorf(const char* format, ...);
void consolePrintf(const char* format, ...) __attribute__((format(printf, 1, 2)));
void consoleErrorf(const char* format, ...) __attribute__((format(printf, 1, 2)));
void consoleWriteUtf8(const void* utf8, uint32_t sizeBytes);
void consoleErrorUtf8(const void* utf8, uint32_t sizeBytes);
// Negative return value denotes that read wasn't successful.
@@ -46,8 +46,8 @@ void onThreadExit(void (*destructor)(void*), void* destructorParameter);
// memcpy/memmove/memcmp are not here intentionally, as frequently implemented/optimized
// by C compiler.
void* memmem(const void *big, size_t bigLen, const void *little, size_t littleLen);
int snprintf(char* buffer, size_t size, const char* format, ...);
int vsnprintf(char* buffer, size_t size, const char* format, va_list args);
int snprintf(char* buffer, size_t size, const char* format, ...) __attribute__((format(printf, 3, 4)));
int vsnprintf(char* buffer, size_t size, const char* format, va_list args) __attribute__((format(printf, 3, 0)));
size_t strnlen(const char* buffer, size_t maxSize);
@@ -10,8 +10,9 @@
#include <memory>
#include <mutex>
#include "CppSupport.hpp"
#include "Alloc.h"
#include "Mutex.hpp"
#include "Types.h"
#include "Utils.hpp"
namespace kotlin {
@@ -20,29 +21,43 @@ namespace kotlin {
template <typename Value, typename Mutex = SpinLock>
class SingleLockList : private Pinned {
public:
class Node : Pinned {
class Node;
private:
class NodeDeleter {
public:
Value* Get() noexcept { return &value; }
void operator()(Node* node) const { delete node; }
};
using NodeOwner = std::unique_ptr<Node, NodeDeleter>;
public:
class Node : private Pinned, public KonanAllocatorAware {
public:
Value* Get() noexcept { return &value_; }
private:
friend class SingleLockList;
template <typename... Args>
Node(Args... args) noexcept : value(args...) {}
Node(Args&&... args) noexcept : value_(std::forward<Args>(args)...) {}
Value value;
std::unique_ptr<Node> next;
Node* previous = nullptr; // weak
// Make sure `Node` can only be deleted by `SingleLockList` itself.
~Node() = default;
Value value_;
NodeOwner next_;
Node* previous_ = nullptr; // weak
};
class Iterator {
public:
explicit Iterator(Node* node) noexcept : node_(node) {}
Value& operator*() noexcept { return node_->value; }
Value& operator*() noexcept { return node_->value_; }
Iterator& operator++() noexcept {
node_ = node_->next.get();
node_ = node_->next_.get();
return *this;
}
@@ -67,36 +82,56 @@ public:
std::unique_lock<Mutex> guard_;
};
template <typename... Args>
Node* Emplace(Args... args) noexcept {
auto* nodePtr = new Node(args...);
std::unique_ptr<Node> node(nodePtr);
std::lock_guard<Mutex> guard(mutex_);
if (root_) {
root_->previous = node.get();
~SingleLockList() {
AssertCorrectUnsafe();
// Make sure not to blow up the stack by nested `~Node` calls.
for (auto node = std::move(root_); node != nullptr; node = std::move(node->next_)) {
}
node->next = std::move(root_);
last_ = nullptr;
AssertCorrectUnsafe();
}
// TODO: Consider making `Emplace` append to `last_`.
template <typename... Args>
Node* Emplace(Args&&... args) noexcept {
auto* nodePtr = new Node(std::forward<Args>(args)...);
NodeOwner node(nodePtr);
std::lock_guard<Mutex> guard(mutex_);
AssertCorrectUnsafe();
if (root_) {
root_->previous_ = node.get();
} else {
last_ = nodePtr;
}
node->next_ = std::move(root_);
root_ = std::move(node);
AssertCorrectUnsafe();
return nodePtr;
}
// Using `node` including its referred `Value` after `Erase` is undefined behaviour.
void Erase(Node* node) noexcept {
std::lock_guard<Mutex> guard(mutex_);
AssertCorrectUnsafe();
if (last_ == node) {
last_ = node->previous_;
}
if (root_.get() == node) {
root_ = std::move(node->next);
root_ = std::move(node->next_);
if (root_) {
root_->previous = nullptr;
root_->previous_ = nullptr;
}
AssertCorrectUnsafe();
return;
}
auto* previous = node->previous;
auto* previous = node->previous_;
RuntimeAssert(previous != nullptr, "Only the root node doesn't have the previous node");
auto ownedNode = std::move(previous->next);
previous->next = std::move(node->next);
if (auto& next = previous->next) {
next->previous = previous;
auto ownedNode = std::move(previous->next_);
previous->next_ = std::move(node->next_);
if (auto& next = previous->next_) {
next->previous_ = previous;
}
AssertCorrectUnsafe();
}
// Returned value locks `this` to perform safe iteration. `this` unlocks when
@@ -109,7 +144,19 @@ public:
Iterable Iter() noexcept { return Iterable(this); }
private:
std::unique_ptr<Node> root_;
// Expects `mutex_` to be held by the current thread.
ALWAYS_INLINE void AssertCorrectUnsafe() const noexcept {
if (root_ == nullptr) {
RuntimeAssert(last_ == nullptr, "last_ must be null");
} else {
RuntimeAssert(root_->previous_ == nullptr, "root_ must not have previous_");
RuntimeAssert(last_ != nullptr, "last_ must not be null");
RuntimeAssert(last_->next_ == nullptr, "last_ must not have next_");
}
}
NodeOwner root_;
Node* last_ = nullptr;
Mutex mutex_;
};
@@ -6,13 +6,14 @@
#include "SingleLockList.hpp"
#include <atomic>
#include <deque>
#include <functional>
#include <thread>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "TestSupport.hpp"
#include "Types.h"
using namespace kotlin;
@@ -47,7 +48,7 @@ TEST(SingleLockListTest, EmplaceAndIter) {
list.Emplace(kSecond);
list.Emplace(kThird);
std::vector<int> actual;
KStdVector<int> actual;
for (int element : list.Iter()) {
actual.push_back(element);
}
@@ -65,7 +66,7 @@ TEST(SingleLockListTest, EmplaceEraseAndIter) {
list.Emplace(kThird);
list.Erase(secondNode);
std::vector<int> actual;
KStdVector<int> actual;
for (int element : list.Iter()) {
actual.push_back(element);
}
@@ -76,7 +77,7 @@ TEST(SingleLockListTest, EmplaceEraseAndIter) {
TEST(SingleLockListTest, IterEmpty) {
IntList list;
std::vector<int> actual;
KStdVector<int> actual;
for (int element : list.Iter()) {
actual.push_back(element);
}
@@ -97,7 +98,7 @@ TEST(SingleLockListTest, EraseToEmptyEmplaceAndIter) {
list.Emplace(kThird);
list.Emplace(kFourth);
std::vector<int> actual;
KStdVector<int> actual;
for (int element : list.Iter()) {
actual.push_back(element);
}
@@ -110,8 +111,8 @@ TEST(SingleLockListTest, ConcurrentEmplace) {
constexpr int kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
std::vector<int> expected;
KStdVector<std::thread> threads;
KStdVector<int> expected;
for (int i = 0; i < kThreadCount; ++i) {
expected.push_back(i);
threads.emplace_back([i, &list, &canStart, &readyCount]() {
@@ -129,7 +130,7 @@ TEST(SingleLockListTest, ConcurrentEmplace) {
t.join();
}
std::vector<int> actual;
KStdVector<int> actual;
for (int element : list.Iter()) {
actual.push_back(element);
}
@@ -140,14 +141,14 @@ TEST(SingleLockListTest, ConcurrentEmplace) {
TEST(SingleLockListTest, ConcurrentErase) {
IntList list;
constexpr int kThreadCount = kDefaultThreadCount;
std::vector<IntList::Node*> items;
KStdVector<IntList::Node*> items;
for (int i = 0; i < kThreadCount; ++i) {
items.push_back(list.Emplace(i));
}
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (auto* item : items) {
threads.emplace_back([item, &list, &canStart, &readyCount]() {
++readyCount;
@@ -164,7 +165,7 @@ TEST(SingleLockListTest, ConcurrentErase) {
t.join();
}
std::vector<int> actual;
KStdVector<int> actual;
for (int element : list.Iter()) {
actual.push_back(element);
}
@@ -177,8 +178,8 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
constexpr int kStartCount = 50;
constexpr int kThreadCount = kDefaultThreadCount;
std::deque<int> expectedBefore;
std::vector<int> expectedAfter;
KStdDeque<int> expectedBefore;
KStdVector<int> expectedAfter;
for (int i = 0; i < kStartCount; ++i) {
expectedBefore.push_front(i);
expectedAfter.push_back(i);
@@ -187,7 +188,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
std::atomic<bool> canStart(false);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
int j = i + kStartCount;
expectedAfter.push_back(j);
@@ -199,7 +200,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
});
}
std::vector<int> actualBefore;
KStdVector<int> actualBefore;
{
auto iter = list.Iter();
canStart = true;
@@ -217,7 +218,7 @@ TEST(SingleLockListTest, IterWhileConcurrentEmplace) {
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
std::vector<int> actualAfter;
KStdVector<int> actualAfter;
for (int element : list.Iter()) {
actualAfter.push_back(element);
}
@@ -229,8 +230,8 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
IntList list;
constexpr int kThreadCount = kDefaultThreadCount;
std::deque<int> expectedBefore;
std::vector<IntList::Node*> items;
KStdDeque<int> expectedBefore;
KStdVector<IntList::Node*> items;
for (int i = 0; i < kThreadCount; ++i) {
expectedBefore.push_front(i);
items.push_back(list.Emplace(i));
@@ -238,7 +239,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
std::atomic<bool> canStart(false);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (auto* item : items) {
threads.emplace_back([item, &list, &canStart, &startedCount]() {
while (!canStart) {
@@ -248,7 +249,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
});
}
std::vector<int> actualBefore;
KStdVector<int> actualBefore;
{
auto iter = list.Iter();
canStart = true;
@@ -266,7 +267,7 @@ TEST(SingleLockListTest, IterWhileConcurrentErase) {
EXPECT_THAT(actualBefore, testing::ElementsAreArray(expectedBefore));
std::vector<int> actualAfter;
KStdVector<int> actualAfter;
for (int element : list.Iter()) {
actualAfter.push_back(element);
}
@@ -298,10 +299,48 @@ TEST(SingleLockListTest, PinnedType) {
list.Erase(itemNode);
std::vector<PinnedType*> actualAfter;
KStdVector<PinnedType*> actualAfter;
for (auto& element : list.Iter()) {
actualAfter.push_back(&element);
}
EXPECT_THAT(actualAfter, testing::IsEmpty());
}
namespace {
class WithDestructorHook;
using DestructorHook = void(WithDestructorHook*);
class WithDestructorHook : private Pinned {
public:
explicit WithDestructorHook(std::function<DestructorHook> hook) : hook_(std::move(hook)) {}
~WithDestructorHook() { hook_(this); }
private:
std::function<DestructorHook> hook_;
};
} // namespace
TEST(SingleLockListTest, Destructor) {
testing::StrictMock<testing::MockFunction<DestructorHook>> hook;
{
SingleLockList<WithDestructorHook> list;
auto* first = list.Emplace(hook.AsStdFunction())->Get();
auto* second = list.Emplace(hook.AsStdFunction())->Get();
auto* third = list.Emplace(hook.AsStdFunction())->Get();
{
testing::InSequence seq;
// `list` is `third`->`second`->`first`. If destruction
// were to cause recursion, the order of destructors
// would've been backwards.
EXPECT_CALL(hook, Call(third));
EXPECT_CALL(hook, Call(second));
EXPECT_CALL(hook, Call(first));
}
}
testing::Mock::VerifyAndClear(&hook);
}
@@ -32,7 +32,7 @@ RUNTIME_USED RUNTIME_WEAK extern "C" char* Konan_cxa_demangle(
namespace std {
RUNTIME_WEAK void __throw_length_error(const char* __s __attribute__((unused))) {
RuntimeAssert(false, __s);
RuntimeCheck(false, "%s", __s);
}
} // namespace std
@@ -9,7 +9,6 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "CppSupport.hpp"
#include "Types.h"
#include "Utils.hpp"
@@ -21,7 +20,7 @@ public:
explicit ScopedStrictMockFunction(Mock** globalMockLocation) : globalMockLocation_(globalMockLocation) {
RuntimeCheck(globalMockLocation != nullptr, "ScopedStrictMockFunction needs non-null global mock location");
RuntimeCheck(*globalMockLocation == nullptr, "ScopedStrictMockFunction needs null global mock");
mock_ = kotlin::std_support::make_unique<Mock>();
mock_ = make_unique<Mock>();
*globalMockLocation_ = mock_.get();
}
@@ -57,7 +56,7 @@ public:
private:
// Can be null if moved-out of.
Mock** globalMockLocation_;
std::unique_ptr<Mock> mock_;
KStdUniquePtr<Mock> mock_;
};
ScopedStrictMockFunction<KInt()> ScopedCreateCleanerWorkerMock();
@@ -27,6 +27,7 @@
#include <deque>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <set>
#include <unordered_map>
@@ -60,6 +61,8 @@ typedef ObjHeader* KRef;
typedef const ObjHeader* KConstRef;
typedef const ArrayHeader* KString;
// TODO: Consider moving these into `kotlin::std_support` namespace keeping STL names.
// Definitions of STL classes used inside Konan runtime.
typedef std::basic_string<char, std::char_traits<char>,
KonanAllocator<char>> KStdString;
@@ -81,6 +84,13 @@ template<class Value>
using KStdVector = std::vector<Value, KonanAllocator<Value>>;
template<class Value>
using KStdList = std::list<Value, KonanAllocator<Value>>;
template <class Value>
using KStdUniquePtr = std::unique_ptr<Value, KonanDeleter<Value>>;
template <typename T, typename... Args>
KStdUniquePtr<T> make_unique(Args&&... args) noexcept {
return KStdUniquePtr<T>(konanConstructInstance<T>(std::forward<Args>(args)...));
}
#ifdef __cplusplus
extern "C" {
@@ -6,8 +6,6 @@
#ifndef RUNTIME_UTILS_H
#define RUNTIME_UTILS_H
#include "CppSupport.hpp"
namespace kotlin {
// A helper for implementing classes with disabled copy constructor and copy assignment.
@@ -5,9 +5,9 @@
#include "Utils.hpp"
#include "gtest/gtest.h"
#include <type_traits>
#include "CppSupport.hpp"
#include "gtest/gtest.h"
using namespace kotlin;
@@ -30,21 +30,21 @@ public:
} // namespace
TEST(UtilsTest, MoveOnlyImpl) {
static_assert(std_support::is_nothrow_default_constructible_v<MoveOnlyImpl>, "Must be nothrow default constructible");
static_assert(std_support::is_nothrow_destructible_v<MoveOnlyImpl>, "Must be nothrow destructible");
static_assert(!std_support::is_copy_constructible_v<MoveOnlyImpl>, "Must not be copy constructible");
static_assert(!std_support::is_copy_assignable_v<MoveOnlyImpl>, "Must not be copy assignable");
static_assert(std_support::is_nothrow_move_constructible_v<MoveOnlyImpl>, "Must be nothrow move constructible");
static_assert(std_support::is_nothrow_move_assignable_v<MoveOnlyImpl>, "Must be nothrow move assignable");
static_assert(std::is_nothrow_default_constructible_v<MoveOnlyImpl>, "Must be nothrow default constructible");
static_assert(std::is_nothrow_destructible_v<MoveOnlyImpl>, "Must be nothrow destructible");
static_assert(!std::is_copy_constructible_v<MoveOnlyImpl>, "Must not be copy constructible");
static_assert(!std::is_copy_assignable_v<MoveOnlyImpl>, "Must not be copy assignable");
static_assert(std::is_nothrow_move_constructible_v<MoveOnlyImpl>, "Must be nothrow move constructible");
static_assert(std::is_nothrow_move_assignable_v<MoveOnlyImpl>, "Must be nothrow move assignable");
static_assert(sizeof(MoveOnlyImpl) == sizeof(A), "Must not increase size");
}
TEST(UtilsTest, PinnedImpl) {
static_assert(std_support::is_nothrow_default_constructible_v<PinnedImpl>, "Must be nothrow default constructible");
static_assert(std_support::is_nothrow_destructible_v<PinnedImpl>, "Must be nothrow destructible");
static_assert(!std_support::is_copy_constructible_v<PinnedImpl>, "Must not be copy constructible");
static_assert(!std_support::is_copy_assignable_v<PinnedImpl>, "Must not be copy assignable");
static_assert(!std_support::is_move_constructible_v<PinnedImpl>, "Must not be move constructible");
static_assert(!std_support::is_move_assignable_v<PinnedImpl>, "Must not be move assignable");
static_assert(std::is_nothrow_default_constructible_v<PinnedImpl>, "Must be nothrow default constructible");
static_assert(std::is_nothrow_destructible_v<PinnedImpl>, "Must be nothrow destructible");
static_assert(!std::is_copy_constructible_v<PinnedImpl>, "Must not be copy constructible");
static_assert(!std::is_copy_assignable_v<PinnedImpl>, "Must not be copy assignable");
static_assert(!std::is_move_constructible_v<PinnedImpl>, "Must not be move constructible");
static_assert(!std::is_move_assignable_v<PinnedImpl>, "Must not be move assignable");
static_assert(sizeof(PinnedImpl) == sizeof(A), "Must not increase size");
}
@@ -476,7 +476,7 @@ class State {
template <typename F>
void waitNativeWorkersTerminationUnlocked(bool checkLeaks, F waitForWorker) {
std::vector<std::pair<KInt, pthread_t>> workersToWait;
KStdVector<std::pair<KInt, pthread_t>> workersToWait;
{
Locker locker(&lock_);
@@ -511,7 +511,7 @@ class State {
if (remainingNativeWorkers != 0) {
konan::consoleErrorf(
"Unfinished workers detected, %lu workers leaked!\n"
"Unfinished workers detected, %zu workers leaked!\n"
"Use `Platform.isMemoryLeakCheckerActive = false` to avoid this check.\n",
remainingNativeWorkers);
konan::consoleFlush();
@@ -9,6 +9,7 @@
#include <cstddef>
#include <cstdint>
#include "Alloc.h"
#include "Memory.h"
#include "TypeInfo.h"
#include "Utils.hpp"
@@ -17,7 +18,7 @@ namespace kotlin {
namespace mm {
// Optional data that's lazily allocated only for objects that need it.
class ExtraObjectData : private Pinned {
class ExtraObjectData : private Pinned, public KonanAllocatorAware {
public:
MetaObjHeader* AsMetaObjHeader() noexcept { return reinterpret_cast<MetaObjHeader*>(this); }
static ExtraObjectData& FromMetaObjHeader(MetaObjHeader* header) noexcept { return *reinterpret_cast<ExtraObjectData*>(header); }
@@ -35,7 +35,7 @@ public:
void ProcessThread(mm::ThreadData* threadData) noexcept;
// Lock registry for safe iteration.
// TODO: Iteration over `globals_` will be slow, because it's `std::list` collected at different times from
// TODO: Iteration over `globals_` will be slow, because it's `KStdList` collected at different times from
// different threads, and so the nodes are all over the memory. Use metrics to understand how
// much of a problem is it.
Iterable Iter() noexcept { return globals_.Iter(); }
@@ -9,12 +9,13 @@
#include <algorithm>
#include <memory>
#include <mutex>
#include <type_traits>
#include "Alignment.hpp"
#include "Alloc.h"
#include "CppSupport.hpp"
#include "Memory.h"
#include "Mutex.hpp"
#include "Types.h"
#include "Utils.hpp"
namespace kotlin {
@@ -31,15 +32,14 @@ class ObjectFactoryStorage : private Pinned {
static_assert(IsValidAlignment(DataAlignment), "DataAlignment is not a valid alignment");
public:
// This class does not know its size at compile-time.
// This class does not know its size at compile-time. Does not inherit from `KonanAllocatorAware` because
// in `KonanAllocatorAware::operator new(size_t size, KonanAllocTag)` `size` would be incorrect.
class Node : private Pinned {
constexpr static size_t DataOffset() noexcept { return AlignUp(sizeof(Node), DataAlignment); }
public:
~Node() = default;
static void operator delete(void* ptr) noexcept { konanFreeMemory(ptr); }
// Note: This can only be trivially destructible data, as nobody can invoke its destructor.
void* Data() noexcept {
constexpr size_t kDataOffset = DataOffset();
@@ -59,7 +59,7 @@ public:
Node() noexcept = default;
static void* operator new(size_t size, size_t dataSize) noexcept {
static KStdUniquePtr<Node> Create(size_t dataSize) noexcept {
size_t dataSizeAligned = AlignUp(dataSize, DataAlignment);
size_t totalAlignment = std::max(alignof(Node), DataAlignment);
size_t totalSize = AlignUp(sizeof(Node) + dataSizeAligned, totalAlignment);
@@ -73,10 +73,10 @@ public:
konan::abort();
}
RuntimeAssert(IsAligned(ptr, totalAlignment), "Allocator returned unaligned to %zu pointer %p", totalAlignment, ptr);
return ptr;
return KStdUniquePtr<Node>(new (ptr) Node());
}
std::unique_ptr<Node> next_;
KStdUniquePtr<Node> next_;
// There's some more data of an unknown (at compile-time) size here, but it cannot be represented
// with C++ members.
};
@@ -89,13 +89,11 @@ public:
Node& Insert(size_t dataSize) noexcept {
AssertCorrect();
auto* nodePtr = new (dataSize) Node();
std::unique_ptr<Node> node(nodePtr);
auto node = Node::Create(dataSize);
auto* nodePtr = node.get();
if (!root_) {
RuntimeAssert(last_ == nullptr, "Unsynchronized root_ and last_");
root_ = std::move(node);
} else {
RuntimeAssert(last_ != nullptr, "Unsynchronized root_ and last_");
last_->next_ = std::move(node);
}
@@ -108,7 +106,7 @@ public:
template <typename T, typename... Args>
Node& Insert(Args&&... args) noexcept {
static_assert(alignof(T) <= DataAlignment, "Cannot insert type with alignment bigger than DataAlignment");
static_assert(std_support::is_trivially_destructible_v<T>, "Type must be trivially destructible");
static_assert(std::is_trivially_destructible_v<T>, "Type must be trivially destructible");
auto& node = Insert(sizeof(T));
new (node.Data()) T(std::forward<Args>(args)...);
return node;
@@ -155,7 +153,7 @@ public:
}
ObjectFactoryStorage& owner_; // weak
std::unique_ptr<Node> root_;
KStdUniquePtr<Node> root_;
Node* last_ = nullptr;
};
@@ -197,6 +195,11 @@ public:
std::unique_lock<SpinLock> guard_;
};
~ObjectFactoryStorage() {
// Make sure not to blow up the stack by nested `~Node` calls.
for (auto node = std::move(root_); node != nullptr; node = std::move(node->next_)) {}
}
// Lock `ObjectFactoryStorage` for safe iteration.
Iterable Iter() noexcept { return Iterable(*this); }
@@ -236,7 +239,7 @@ private:
}
}
std::unique_ptr<Node> root_;
KStdUniquePtr<Node> root_;
Node* last_ = nullptr;
SpinLock mutex_;
};
@@ -7,12 +7,13 @@
#include <atomic>
#include <thread>
#include <type_traits>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "CppSupport.hpp"
#include "TestSupport.hpp"
#include "Types.h"
using namespace kotlin;
@@ -24,8 +25,8 @@ using ObjectFactoryStorageRegular = ObjectFactoryStorage<alignof(void*)>;
namespace {
template <size_t DataAlignment>
std::vector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
std::vector<void*> result;
KStdVector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
KStdVector<void*> result;
for (auto& node : storage.Iter()) {
result.push_back(node.Data());
}
@@ -33,8 +34,8 @@ std::vector<void*> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
}
template <typename T, size_t DataAlignment>
std::vector<T> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
std::vector<T> result;
KStdVector<T> Collect(ObjectFactoryStorage<DataAlignment>& storage) {
KStdVector<T> result;
for (auto& node : storage.Iter()) {
result.push_back(*static_cast<T*>(node.Data()));
}
@@ -300,8 +301,8 @@ TEST(ObjectFactoryStorageTest, ConcurrentPublish) {
constexpr int kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
std::vector<int> expected;
KStdVector<std::thread> threads;
KStdVector<int> expected;
for (int i = 0; i < kThreadCount; ++i) {
expected.push_back(i);
@@ -332,8 +333,8 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
constexpr int kStartCount = 50;
constexpr int kThreadCount = kDefaultThreadCount;
std::vector<int> expectedBefore;
std::vector<int> expectedAfter;
KStdVector<int> expectedBefore;
KStdVector<int> expectedAfter;
ObjectFactoryStorageRegular::Producer producer(storage);
for (int i = 0; i < kStartCount; ++i) {
expectedBefore.push_back(i);
@@ -345,7 +346,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
int j = i + kStartCount;
expectedAfter.push_back(j);
@@ -360,7 +361,7 @@ TEST(ObjectFactoryStorageTest, IterWhileConcurrentPublish) {
});
}
std::vector<int> actualBefore;
KStdVector<int> actualBefore;
{
auto iter = storage.Iter();
while (readyCount < kThreadCount) {
@@ -391,7 +392,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
constexpr int kStartCount = 50;
constexpr int kThreadCount = kDefaultThreadCount;
std::vector<int> expectedAfter;
KStdVector<int> expectedAfter;
ObjectFactoryStorageRegular::Producer producer(storage);
for (int i = 0; i < kStartCount; ++i) {
if (i % 2 == 0) {
@@ -404,7 +405,7 @@ TEST(ObjectFactoryStorageTest, EraseWhileConcurrentPublish) {
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::atomic<int> startedCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
for (int i = 0; i < kThreadCount; ++i) {
int j = i + kStartCount;
expectedAfter.push_back(j);
@@ -449,15 +450,15 @@ using mm::ObjectFactory;
namespace {
std::unique_ptr<TypeInfo> MakeObjectTypeInfo(int32_t size) {
auto typeInfo = std_support::make_unique<TypeInfo>();
KStdUniquePtr<TypeInfo> MakeObjectTypeInfo(int32_t size) {
auto typeInfo = make_unique<TypeInfo>();
typeInfo->typeInfo_ = typeInfo.get();
typeInfo->instanceSize_ = size;
return typeInfo;
}
std::unique_ptr<TypeInfo> MakeArrayTypeInfo(int32_t elementSize) {
auto typeInfo = std_support::make_unique<TypeInfo>();
KStdUniquePtr<TypeInfo> MakeArrayTypeInfo(int32_t elementSize) {
auto typeInfo = make_unique<TypeInfo>();
typeInfo->typeInfo_ = typeInfo.get();
typeInfo->instanceSize_ = -elementSize;
return typeInfo;
@@ -537,9 +538,9 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
constexpr int kThreadCount = kDefaultThreadCount;
std::atomic<bool> canStart(false);
std::atomic<int> readyCount(0);
std::vector<std::thread> threads;
KStdVector<std::thread> threads;
std::mutex expectedMutex;
std::vector<ObjHeader*> expected;
KStdVector<ObjHeader*> expected;
for (int i = 0; i < kThreadCount; ++i) {
threads.emplace_back([&typeInfo, &objectFactory, &canStart, &readyCount, &expected, &expectedMutex]() {
@@ -564,7 +565,7 @@ TEST(ObjectFactoryTest, ConcurrentPublish) {
}
auto iter = objectFactory.Iter();
std::vector<ObjHeader*> actual;
KStdVector<ObjHeader*> actual;
for (auto it = iter.begin(); it != iter.end(); ++it) {
actual.push_back(it.GetObjHeader());
}
@@ -40,7 +40,7 @@ public:
void ProcessDeletions() noexcept;
// Lock registry for safe iteration.
// TODO: Iteration over `stableRefs_` will be slow, because it's `std::list` collected at different times from
// TODO: Iteration over `stableRefs_` will be slow, because it's `KStdList` collected at different times from
// different threads, and so the nodes are all over the memory. Use metrics to understand how
// much of a problem is it.
Iterable Iter() noexcept { return stableRefs_.Iter(); }
@@ -11,6 +11,7 @@
#include <vector>
#include "Memory.h"
#include "Types.h"
#include "Utils.hpp"
namespace kotlin {
@@ -22,7 +23,7 @@ public:
class Iterator {
public:
explicit Iterator(std::vector<ObjHeader*>::iterator iterator) : iterator_(iterator) {}
explicit Iterator(KStdVector<ObjHeader*>::iterator iterator) : iterator_(iterator) {}
ObjHeader** operator*() noexcept { return &*iterator_; }
@@ -35,7 +36,7 @@ public:
bool operator!=(const Iterator& rhs) const noexcept { return iterator_ != rhs.iterator_; }
private:
std::vector<ObjHeader*>::iterator iterator_;
KStdVector<ObjHeader*>::iterator iterator_;
};
// Add TLS record. Can only be called before `Commit`.
@@ -64,9 +65,9 @@ private:
ObjHeader** Lookup(Entry entry, int index) noexcept;
std::vector<ObjHeader*> storage_;
// TODO: `std::unordered_map` is probably the wrong container here.
std::unordered_map<Key, Entry> map_;
KStdVector<ObjHeader*> storage_;
// TODO: `KStdUnorderedMap` is probably the wrong container here.
KStdUnorderedMap<Key, Entry> map_;
State state_ = State::kBuilding;
int size_ = 0; // Only used in `State::kBuilding`
std::pair<Key, Entry> lastKeyAndEntry_;
@@ -8,6 +8,8 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "Types.h"
using namespace kotlin;
namespace {
@@ -54,12 +56,12 @@ TEST(ThreadLocalStorageTest, Iterate) {
tls.AddRecord(&key2, 2);
tls.Commit();
std::vector<ObjHeader**> expected;
KStdVector<ObjHeader**> expected;
expected.push_back(tls.Lookup(&key1, 0));
expected.push_back(tls.Lookup(&key2, 0));
expected.push_back(tls.Lookup(&key2, 1));
std::vector<ObjHeader**> actual;
KStdVector<ObjHeader**> actual;
for (auto item : tls) {
actual.push_back(item);
}
@@ -78,12 +80,12 @@ TEST(ThreadLocalStorageTest, AddRecordEmpty) {
tls.AddRecord(&key3, 2);
tls.Commit();
std::vector<ObjHeader**> expected;
KStdVector<ObjHeader**> expected;
expected.push_back(tls.Lookup(&key1, 0));
expected.push_back(tls.Lookup(&key3, 0));
expected.push_back(tls.Lookup(&key3, 1));
std::vector<ObjHeader**> actual;
KStdVector<ObjHeader**> actual;
for (auto item : tls) {
actual.push_back(item);
}
@@ -99,10 +101,10 @@ TEST(ThreadLocalStorageTest, AddRecordSameSize) {
tls.AddRecord(&key1, 1);
tls.Commit();
std::vector<ObjHeader**> expected;
KStdVector<ObjHeader**> expected;
expected.push_back(tls.Lookup(&key1, 0));
std::vector<ObjHeader**> actual;
KStdVector<ObjHeader**> actual;
for (auto item : tls) {
actual.push_back(item);
}
@@ -115,7 +117,7 @@ TEST(ThreadLocalStorageTest, NoRecords) {
tls.Commit();
std::vector<ObjHeader**> actual;
KStdVector<ObjHeader**> actual;
for (auto item : tls) {
actual.push_back(item);
}
@@ -130,7 +132,7 @@ TEST(ThreadLocalStorageTest, ClearEmpty) {
tls.Clear();
std::vector<ObjHeader**> actual;
KStdVector<ObjHeader**> actual;
for (auto item : tls) {
actual.push_back(item);
}
@@ -147,7 +149,7 @@ TEST(ThreadLocalStorageTest, ClearNonEmpty) {
tls.Clear();
std::vector<ObjHeader**> actual;
KStdVector<ObjHeader**> actual;
for (auto item : tls) {
actual.push_back(item);
}
@@ -232,10 +232,13 @@ OBJ_GETTER(Kotlin_boxDouble, KDouble value);
@end;
static void injectToRuntime() {
RuntimeCheck(Kotlin_ObjCExport_toKotlinSelector == nullptr, "runtime injected twice");
// If the code below fails, then it is most likely caused by KT-42254.
constexpr const char* errorMessage = "runtime injected twice; https://youtrack.jetbrains.com/issue/KT-42254 might be related";
RuntimeCheck(Kotlin_ObjCExport_toKotlinSelector == nullptr, errorMessage);
Kotlin_ObjCExport_toKotlinSelector = @selector(toKotlin:);
RuntimeCheck(Kotlin_ObjCExport_releaseAsAssociatedObjectSelector == nullptr, "runtime injected twice");
RuntimeCheck(Kotlin_ObjCExport_releaseAsAssociatedObjectSelector == nullptr, errorMessage);
Kotlin_ObjCExport_releaseAsAssociatedObjectSelector = @selector(releaseAsAssociatedObject);
}
@@ -15,6 +15,25 @@ private fun mainImpl(args: Array<String>, konancMain: (Array<String>) -> Unit) {
when (utilityName) {
"konanc" ->
konancMain(utilityArgs)
"kotlinc" -> {
println("""
NOTE: you are running "kotlinc" CLI tool from Kotlin/Native distribution,
it runs Kotlin/Native compiler that produces native binaries from Kotlin code.
If your intention was to compile Kotlin code to JVM bytecode instead, then you
need to use "kotlinc" from the main Kotlin distribution (e.g. it can be
downloaded as kotlin-compiler-X.Y.ZZ.zip archive from
https://github.com/JetBrains/kotlin/releases/latest, or installed using various
package managers).
WARNING: if your intention was to run Kotlin/Native compiler, then please use
"kotlinc-native" CLI tool instead of "kotlinc". "kotlinc" tool will be removed
from Kotlin/Native distribution, so it will stop clashing with "kotlinc" from
the main Kotlin distribution.
""".trimIndent())
konancMain(utilityArgs)
}
"cinterop" -> {
val konancArgs = invokeInterop("native", utilityArgs)
konancArgs?.let { konancMain(it) }