I posted this in <#C7L3JB43G|compiler> but maybe t...
# getting-started
s
I posted this in #compiler but maybe that was the wrong place to post it. I’m completely new here and not sure what should go where. I’m trying to make the concept of a finite field library for a variety of purposes I have. I would like to have an
AbstractField
class with an inner
AbstractFieldElement
class. When I do this, compiling using 1.9.22, I’m getting a compiler error. Can anyone see something I am doing wrong? I’ve reduced the code down to the minimal example that causes the crash in the comment to this post to avoid taking up too much room along with the compiler error / stack trace.
Here’s the code:
Copy code
package org.example

abstract class AbstractField<F: AbstractField<F, E>, E: AbstractField<F, E>.AbstractFieldElement<E>> {
    abstract val zero: E

    abstract inner class AbstractFieldElement<E: AbstractFieldElement<E>> {
        abstract operator fun plus(other: E): E
    }
}

class Fp(val prime: Int): AbstractField<Fp, Fp.EFp>() {
    override val zero = EFp(0)

    inner class EFp(val value: Int): AbstractFieldElement<EFp>() {
        override operator fun plus(other: EFp): EFp =
            EFp((value + other.value) % prime)

        override fun toString(): String =
            "$value (mod $prime)"
    }
}

fun main() {
    val field = Fp(13)
    val elem1 = field.EFp(8)
    val elem2 = field.EFp(10)
    println("$elem1 + $elem2 = ${elem1 + elem2}")
}
And here’s what happens when I try to compile it. Even taking out the
zero
member results in the same
BackendException
message: I included the
zero
here in the sample code to demonstrate why I am trying to do this.
Copy code
org.jetbrains.kotlin.backend.common.BackendException: Backend Internal error: Exception during IR lowering
File being compiled: /Users/vorpal/dev/kotlin/field_bug/src/main/kotlin/Main.kt
The root cause java.lang.RuntimeException was thrown at: org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate(FunctionCodegen.kt:51)
	at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException(CodegenUtil.kt:253)
	at org.jetbrains.kotlin.backend.common.CodegenUtil.reportBackendException$default(CodegenUtil.kt:237)
	at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:65)
	at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:52)
	at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invoke(performByIrFile.kt:38)
	at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:147)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:94)
	at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:29)
	at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:16)
	at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:147)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:94)
	at org.jetbrains.kotlin.backend.common.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:43)
	at org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory.invokeCodegen(JvmIrCodegenFactory.kt:361)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runCodegen(KotlinToJVMBytecodeCompiler.kt:347)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:122)
	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:43)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:165)
	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:50)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:104)
	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:48)
	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:463)
	at org.jetbrains.kotlin.incremental.IncrementalJvmCompilerRunner.runCompiler(IncrementalJvmCompilerRunner.kt:62)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.doCompile(IncrementalCompilerRunner.kt:477)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileImpl(IncrementalCompilerRunner.kt:400)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:281)
	at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:125)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:657)
	at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:105)
	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1624)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
	at java.base/java.lang.reflect.Method.invoke(Method.java:578)
	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:714)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
	at java.base/java.lang.Thread.run(Thread.java:1623)
Caused by: java.lang.RuntimeException: Exception while generating code for:
FUN BRIDGE name:getZero visibility:public modality:OPEN <> ($this:org.example.Fp) returnType:org.example.AbstractField.AbstractFieldElement<*>
  $this: VALUE_PARAMETER INSTANCE_RECEIVER name:<this> type:org.example.Fp
  EXPRESSION_BODY
    TYPE_OP type=org.example.AbstractField.AbstractFieldElement<*, *, *> origin=IMPLICIT_CAST typeOperand=org.example.AbstractField.AbstractFieldElement<*, *, *>
      RETURN type=kotlin.Nothing from='public open fun getZero (): org.example.AbstractField.AbstractFieldElement<*> declared in org.example.Fp'
        CALL 'public open fun <get-zero> (): org.example.Fp.EFp declared in org.example.Fp' type=org.example.Fp.EFp origin=BRIDGE_DELEGATION
          $this: GET_VAR '<this>: org.example.Fp declared in org.example.Fp.getZero' type=org.example.Fp origin=null

	at org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate(FunctionCodegen.kt:51)
	at org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate$default(FunctionCodegen.kt:43)
	at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateMethodNode(ClassCodegen.kt:390)
	at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generateMethod(ClassCodegen.kt:407)
	at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen.generate(ClassCodegen.kt:169)
	at org.jetbrains.kotlin.backend.jvm.FileCodegen.lower(JvmPhases.kt:41)
	at org.jetbrains.kotlin.backend.common.phaser.FileLoweringPhaseAdapter.invoke(PhaseBuilders.kt:120)
	at org.jetbrains.kotlin.backend.common.phaser.FileLoweringPhaseAdapter.invoke(PhaseBuilders.kt:116)
	at org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase.phaseBody(CompilerPhase.kt:147)
	at org.jetbrains.kotlin.backend.common.phaser.AbstractNamedCompilerPhase.invoke(CompilerPhase.kt:94)
	at org.jetbrains.kotlin.backend.common.phaser.PerformByIrFilePhase.invokeSequential(performByIrFile.kt:62)
	... 42 more
Caused by: java.lang.AssertionError: -2 trailing arguments were found in this type: org.example.AbstractField.AbstractFieldElement<*>
	at org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMappingKt.buildPossiblyInnerType(IrTypeMapping.kt:71)
	at org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMappingKt.buildPossiblyInnerType(IrTypeMapping.kt:81)
	at org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMappingKt.buildPossiblyInnerType(IrTypeMapping.kt:64)
	at org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper.writeGenericType(IrTypeMapper.kt:148)
	at org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper.writeGenericType(IrTypeMapper.kt:41)
	at org.jetbrains.kotlin.types.AbstractTypeMapper.mapClassType(AbstractTypeMapper.kt:192)
	at org.jetbrains.kotlin.types.AbstractTypeMapper.mapType(AbstractTypeMapper.kt:89)
	at org.jetbrains.kotlin.types.AbstractTypeMapper.mapType(AbstractTypeMapper.kt:53)
	at org.jetbrains.kotlin.backend.jvm.mapping.IrTypeMapper.mapType(IrTypeMapper.kt:134)
	at org.jetbrains.kotlin.backend.jvm.codegen.ClassCodegen$typeMapper$1.mapType(ClassCodegen.kt:101)
	at org.jetbrains.kotlin.backend.jvm.mapping.MethodSignatureMapper.mapReturnType(MethodSignatureMapper.kt:189)
	at org.jetbrains.kotlin.backend.jvm.mapping.MethodSignatureMapper.mapReturnType(MethodSignatureMapper.kt:169)
	at org.jetbrains.kotlin.backend.jvm.mapping.MethodSignatureMapper.mapSignature(MethodSignatureMapper.kt:277)
	at org.jetbrains.kotlin.backend.jvm.mapping.MethodSignatureMapper.mapSignature$default(MethodSignatureMapper.kt:227)
	at org.jetbrains.kotlin.backend.jvm.mapping.MethodSignatureMapper.mapSignatureWithGeneric(MethodSignatureMapper.kt:225)
	at org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.doGenerate(FunctionCodegen.kt:55)
	at org.jetbrains.kotlin.backend.jvm.codegen.FunctionCodegen.generate(FunctionCodegen.kt:47)
	... 52 more
y
The code runs successfully on 1.9.22, but fails on 2.0, at least on the Kotlin playground. This seems like a regression, please report it: kotl.in/issue
s
This is incredibly strange… I tried it on the Kotlin Playground and yes, it runs fine there, but I can’t get it to run in IntelliJ IDEA on my machine, and as far as I can tell, I’m running Kotlin 1.9.22. 😕 If I compile by hand using
kotlinc
, everything is fine, but if I try to go through gradle, it borks like above. I’ve spent hours troubleshooting this and I have no idea what’s going on.
y
Looks like it's failing in the backend. Do you have K2 enabled on your local project? Or language level set to 2.0 or something? It might be that the new frontend is generating bad code for inner classes. In any case, I think there might be a neater way to achieve what you're trying to do using context receivers. More to follow soon
Actually, even easier: just drop the
E
from
AbstractFieldElement
. Because you're an inner class, it's not needed:
Copy code
package org.example

abstract class AbstractField<F: AbstractField<F, E>, E: AbstractField<F, E>.AbstractFieldElement> {
    abstract val zero: E

    abstract inner class AbstractFieldElement {
        abstract operator fun plus(other: E): E
    }
}

class Fp(val prime: Int): AbstractField<Fp, Fp.EFp>() {
    override val zero = EFp(0)

    inner class EFp(val value: Int): AbstractFieldElement() {
        override operator fun plus(other: EFp): EFp =
            EFp((value + other.value) % prime)

        override fun toString(): String =
            "$value (mod $prime)"
    }
}

fun main() {
    val field = Fp(13)
    val elem1 = field.EFp(8)
    val elem2 = field.EFp(10)
    println("$elem1 + $elem2 = ${elem1 + elem2}")
}
I would say this is more idiomatic though, and definitely provides better composition with contexts:
Copy code
package org.example

interface Field<F: Field<F, E>, E> {
    val zero: E
    operator fun E.plus(other: E): E
    val E.repr: String
}

@JvmInline value class EFp(val value: Int) {
	override fun toString(): String = "$value"
}

@JvmInline value class Fp(val prime: Int): Field<Fp, EFp> {
    override val zero get() = EFp(0)
	override operator fun EFp.plus(other: EFp): EFp =
		efp(value + other.value)
	override val EFp.repr get() = "$value (mod $prime)"
    fun efp(value: Int) = EFp(value % prime)
}

fun main() {
    with(Fp(13)) {
    	val elem1 = efp(8)
    	val elem2 = efp(10)
    	println("${elem1.repr} + ${elem2.repr} = ${(elem1 + elem2).repr}")   
    }
}
Out of interest, do you have an open-source project where you're doing this? I would love to chat and maybe make some contributions. Also check out kmath
s
Sorry for not replying. I’ve been fighting with Gradle and IntelliJ all night and it’s 4:00 am here now so I should be getting to bed. This is a project I’ve wanted to work on for a long time and the motivation for it has come up because I’m reading through a book on elliptic curve cryptography. It’s still very early days, so I don’t have anything yet that I could call an open-source project, but I would love to get to that point. I’ll keep you updated. I’ll absolutely check out kmath… I hadn’t heard of it before, but you’ve definitely caught my interest!
y
I would love to see even an uncommented repo with no docs or anything. I have some ideas within this domain that I'd love to explore, so do keep me updated! FYI, your original code would have weird bugs when/if one uses 2 different
Fp
fields with different primes. The result would depend on which argument was passed first, thus making
+
not commutative, and very weird. The code I provided makes the field you operate in an external choice based on what context you're in. I think that's more natural. It's also somewhat more performant because you can use `value class`es in a lot of places as a result of that decomposition. I find that `inner class`es rarily make sense in Kotlin nowadays
s
I’ll show you a couple different implementations I’ve been playing with later on today… the actual code is much longer and the main implementation I’ve been playing with does use uuids to check if the elements come from the same finite field
F_p
(although there’s no reason it couldn’t just check that the primes are the same). I’d love to get some feedback on it and run it by someone, and I’ll look at your code and give it a think… I will probably have some questions for you. I also have an implementation using JGMP - the Java bindings for GMP - so I can work with large primes effectively. It’s a little disorganized right now with a bunch of ideas, but the snippet I posted was just the minimal example I could find that represented the idea I was going for and that was crashing when I built it with Gradle. I’m very grateful that you reminded me of the Kotlin playground and I could see that there’s something wrong with my setup and not so much with the code itself. I hope you have a great day wherever you are and I’ll put together my stuff on GitHub and send you the link. Looking forward to chatting more!
❤️ 1
Hey! Sorry I haven’t gotten back to you yet… I made some breakthroughs I’m happy with… but I haven’t organized them into a PR. Been busy with work.
y
That's absolutely fine! Take whatever time you need! I've been focusing on university more recently and so I likely wouldn't have had time either