translator: add string constants, LLVMStringType, split local and global code generation in LLVMBuilder
This commit is contained in:
@@ -9,6 +9,7 @@ import org.jetbrains.kotlin.psi.*
|
|||||||
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.TypedCompileTimeConstant
|
||||||
import org.kotlinnative.translator.llvm.*
|
import org.kotlinnative.translator.llvm.*
|
||||||
import org.kotlinnative.translator.llvm.types.*
|
import org.kotlinnative.translator.llvm.types.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -107,12 +108,22 @@ class FunctionCodegen(val state: TranslationState, val function: KtNamedFunction
|
|||||||
is KtIfExpression -> evaluateIfOperator(expr.firstChild as LeafPsiElement, scopeDepth + 1, true)
|
is KtIfExpression -> evaluateIfOperator(expr.firstChild as LeafPsiElement, scopeDepth + 1, true)
|
||||||
is KtDotQualifiedExpression -> evaluateDotExpression(expr, scopeDepth)
|
is KtDotQualifiedExpression -> evaluateDotExpression(expr, scopeDepth)
|
||||||
is PsiWhiteSpace -> null
|
is PsiWhiteSpace -> null
|
||||||
|
is KtStringTemplateExpression -> evaluateStringTemplateExpression(expr, scopeDepth + 1)
|
||||||
is PsiElement -> evaluatePsiElement(expr, scopeDepth)
|
is PsiElement -> evaluatePsiElement(expr, scopeDepth)
|
||||||
null -> null
|
null -> null
|
||||||
else -> throw UnsupportedOperationException()
|
else -> throw UnsupportedOperationException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun evaluateStringTemplateExpression(expr: KtStringTemplateExpression, scope: Int): LLVMSingleValue? {
|
||||||
|
val receiveValue = state.bindingContext.get(BindingContext.COMPILE_TIME_VALUE, expr)
|
||||||
|
val type = (receiveValue as TypedCompileTimeConstant).type
|
||||||
|
val value = receiveValue.getValue(type) ?: return null
|
||||||
|
val variable = variableManager.receiveVariable(".str", LLVMStringType(value.toString().length), LLVMGlobalScope(), pointer = false)
|
||||||
|
codeBuilder.addStringConstant(variable, value.toString())
|
||||||
|
return variable
|
||||||
|
}
|
||||||
|
|
||||||
private fun evaluateDotExpression(expr: KtDotQualifiedExpression, scopeDepth: Int): LLVMSingleValue? {
|
private fun evaluateDotExpression(expr: KtDotQualifiedExpression, scopeDepth: Int): LLVMSingleValue? {
|
||||||
val receiverName = expr.receiverExpression.text
|
val receiverName = expr.receiverExpression.text
|
||||||
val selectorName = expr.selectorExpression!!.text
|
val selectorName = expr.selectorExpression!!.text
|
||||||
@@ -303,6 +314,11 @@ class FunctionCodegen(val state: TranslationState, val function: KtNamedFunction
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun copyVariable(from: LLVMVariable, to: LLVMVariable) = when (from.type) {
|
||||||
|
is LLVMStringType -> codeBuilder.storeString(to, from, 0)
|
||||||
|
else -> codeBuilder.copyVariableValue(to, from)
|
||||||
|
}
|
||||||
|
|
||||||
private fun evaluateValExpression(element: LeafPsiElement, scopeDepth: Int): LLVMVariable? {
|
private fun evaluateValExpression(element: LeafPsiElement, scopeDepth: Int): LLVMVariable? {
|
||||||
val identifier = element.getNextSiblingIgnoringWhitespaceAndComments()
|
val identifier = element.getNextSiblingIgnoringWhitespaceAndComments()
|
||||||
val eq = identifier?.getNextSiblingIgnoringWhitespaceAndComments() ?: return null
|
val eq = identifier?.getNextSiblingIgnoringWhitespaceAndComments() ?: return null
|
||||||
@@ -311,19 +327,19 @@ class FunctionCodegen(val state: TranslationState, val function: KtNamedFunction
|
|||||||
|
|
||||||
when (assignExpression) {
|
when (assignExpression) {
|
||||||
is LLVMVariable -> {
|
is LLVMVariable -> {
|
||||||
val allocVar = variableManager.receiveVariable(identifier!!.text, LLVMIntType(), pointer = true)
|
val allocVar = variableManager.receiveVariable(identifier!!.text, assignExpression.type, LLVMLocalScope(), pointer = true)
|
||||||
codeBuilder.allocVar(allocVar)
|
codeBuilder.allocVar(allocVar)
|
||||||
variableManager.addVariable(identifier.text, allocVar, scopeDepth)
|
variableManager.addVariable(identifier.text, allocVar, scopeDepth)
|
||||||
codeBuilder.copyVariableValue(assignExpression, allocVar)
|
copyVariable(assignExpression, allocVar)
|
||||||
}
|
}
|
||||||
is LLVMConstant -> {
|
is LLVMConstant -> {
|
||||||
val newVar = variableManager.receiveVariable(identifier!!.text, LLVMIntType(), pointer = true)
|
val newVar = variableManager.receiveVariable(identifier!!.text, LLVMIntType(), LLVMLocalScope(), pointer = true)
|
||||||
|
|
||||||
codeBuilder.addConstant(newVar, assignExpression)
|
codeBuilder.addConstant(newVar, assignExpression)
|
||||||
variableManager.addVariable(identifier.text, newVar, scopeDepth)
|
variableManager.addVariable(identifier.text, newVar, scopeDepth)
|
||||||
}
|
}
|
||||||
is LLVMConstructorCall -> {
|
is LLVMConstructorCall -> {
|
||||||
val result = variableManager.receiveVariable(identifier!!.text, assignExpression.type, pointer = false)
|
val result = variableManager.receiveVariable(identifier!!.text, assignExpression.type, LLVMLocalScope(), pointer = false)
|
||||||
codeBuilder.allocVar(result)
|
codeBuilder.allocVar(result)
|
||||||
result.pointer = true
|
result.pointer = true
|
||||||
codeBuilder.addLLVMCode(assignExpression.call(result).toString())
|
codeBuilder.addLLVMCode(assignExpression.call(result).toString())
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package org.kotlinnative.translator
|
package org.kotlinnative.translator
|
||||||
|
|
||||||
import org.kotlinnative.translator.llvm.LLVMLocalScope
|
import org.kotlinnative.translator.llvm.LLVMScope
|
||||||
import org.kotlinnative.translator.llvm.LLVMVariable
|
import org.kotlinnative.translator.llvm.LLVMVariable
|
||||||
import org.kotlinnative.translator.llvm.types.LLVMType
|
import org.kotlinnative.translator.llvm.types.LLVMType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -29,11 +29,11 @@ class VariableManager {
|
|||||||
globalVariableCollection.put(name, variable)
|
globalVariableCollection.put(name, variable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun receiveVariable(name: String, type: LLVMType, pointer: Boolean): LLVMVariable {
|
fun receiveVariable(name: String, type: LLVMType, scope: LLVMScope, pointer: Boolean): LLVMVariable {
|
||||||
val ourVersion = variableVersion.getOrDefault(name, 0) + 1
|
val ourVersion = variableVersion.getOrDefault(name, 0) + 1
|
||||||
variableVersion.put(name, ourVersion)
|
variableVersion.put(name, ourVersion)
|
||||||
|
|
||||||
return LLVMVariable("managed.$name.$ourVersion", type, name, LLVMLocalScope(), pointer)
|
return LLVMVariable("managed.$name.$ourVersion", type, name, scope, pointer)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,10 +3,12 @@ package org.kotlinnative.translator.llvm
|
|||||||
import com.intellij.psi.tree.IElementType
|
import com.intellij.psi.tree.IElementType
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.kotlinnative.translator.llvm.types.LLVMIntType
|
import org.kotlinnative.translator.llvm.types.LLVMIntType
|
||||||
|
import org.kotlinnative.translator.llvm.types.LLVMStringType
|
||||||
import org.kotlinnative.translator.llvm.types.LLVMType
|
import org.kotlinnative.translator.llvm.types.LLVMType
|
||||||
|
|
||||||
class LLVMBuilder(val arm: Boolean) {
|
class LLVMBuilder(val arm: Boolean) {
|
||||||
private var llvmCode: StringBuilder = StringBuilder()
|
private var llvmLocalCode: StringBuilder = StringBuilder()
|
||||||
|
private var llvmGlobalCode: StringBuilder = StringBuilder()
|
||||||
private var variableCount = 0
|
private var variableCount = 0
|
||||||
private var labelCount = 0
|
private var labelCount = 0
|
||||||
|
|
||||||
@@ -17,10 +19,10 @@ class LLVMBuilder(val arm: Boolean) {
|
|||||||
private fun initBuilder() {
|
private fun initBuilder() {
|
||||||
val memcpy = "declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture, i8* nocapture readonly, i64, i32, i1)"
|
val memcpy = "declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture, i8* nocapture readonly, i64, i32, i1)"
|
||||||
val funcAttributes = """attributes #0 = { nounwind "stack-protector-buffer-size"="8" "target-cpu"="cortex-m3" "target-features"="+hwdiv,+strict-align" }"""
|
val funcAttributes = """attributes #0 = { nounwind "stack-protector-buffer-size"="8" "target-cpu"="cortex-m3" "target-features"="+hwdiv,+strict-align" }"""
|
||||||
llvmCode.appendln(memcpy)
|
llvmLocalCode.appendln(memcpy)
|
||||||
|
|
||||||
if (arm) {
|
if (arm) {
|
||||||
llvmCode.appendln(funcAttributes)
|
llvmLocalCode.appendln(funcAttributes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,15 +37,15 @@ class LLVMBuilder(val arm: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addLLVMCode(code: String) {
|
fun addLLVMCode(code: String) {
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addStartExpression() {
|
fun addStartExpression() {
|
||||||
llvmCode.appendln("{")
|
llvmLocalCode.appendln("{")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addEndExpression() {
|
fun addEndExpression() {
|
||||||
llvmCode.appendln("}")
|
llvmLocalCode.appendln("}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun receiveNativeValue(firstOp: LLVMSingleValue): LLVMSingleValue = when (firstOp) {
|
fun receiveNativeValue(firstOp: LLVMSingleValue): LLVMSingleValue = when (firstOp) {
|
||||||
@@ -82,46 +84,57 @@ class LLVMBuilder(val arm: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clean() {
|
fun clean() {
|
||||||
llvmCode = StringBuilder()
|
llvmLocalCode = StringBuilder()
|
||||||
initBuilder()
|
initBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAssignment(llvmVariable: LLVMVariable, rhs: LLVMNode) {
|
fun addAssignment(llvmVariable: LLVMVariable, rhs: LLVMNode) {
|
||||||
llvmCode.appendln("$llvmVariable = $rhs")
|
llvmLocalCode.appendln("$llvmVariable = $rhs")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addReturnOperator(llvmVariable: LLVMSingleValue) {
|
fun addReturnOperator(llvmVariable: LLVMSingleValue) {
|
||||||
llvmCode.appendln("ret ${llvmVariable.type} $llvmVariable")
|
llvmLocalCode.appendln("ret ${llvmVariable.type} $llvmVariable")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addVoidReturn() {
|
fun addVoidReturn() {
|
||||||
llvmCode.appendln("ret void")
|
llvmLocalCode.appendln("ret void")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addStringConstant(variable: LLVMVariable, value: String) {
|
||||||
|
val type = variable.type as LLVMStringType
|
||||||
|
llvmGlobalCode.appendln("$variable = private unnamed_addr constant ${type.fullType()} c\"$value\\00\", align 1")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun storeString(target: LLVMVariable, source: LLVMVariable, offset: Int) {
|
||||||
|
val stringType = source.type as LLVMStringType
|
||||||
|
val code = "store ${target.type} getelementptr inbounds (${stringType.fullType()}, " +
|
||||||
|
"${stringType.fullType()}* $source, i32 0, i32 $offset), ${target.getType()} $target, align ${stringType.align}"
|
||||||
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadClassField(target: LLVMVariable, source: LLVMVariable, offset: Int) {
|
fun loadClassField(target: LLVMVariable, source: LLVMVariable, offset: Int) {
|
||||||
val code = "$target = getelementptr inbounds ${source.type}, ${source.type}* $source, i32 0, i32 $offset"
|
val code = "$target = getelementptr inbounds ${source.type}, ${source.type}* $source, i32 0, i32 $offset"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markWithLabel(label: LLVMLabel?) {
|
fun markWithLabel(label: LLVMLabel?) {
|
||||||
if (label != null)
|
if (label != null)
|
||||||
llvmCode.appendln("${label.label}:")
|
llvmLocalCode.appendln("${label.label}:")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addNopInstruction() {
|
fun addNopInstruction() {
|
||||||
llvmCode.appendln(getNewVariable(LLVMIntType()).toString() + " = add i1 0, 0 ; nop instruction")
|
llvmLocalCode.appendln(getNewVariable(LLVMIntType()).toString() + " = add i1 0, 0 ; nop instruction")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun storeVariable(target: LLVMVariable, source: LLVMSingleValue) {
|
fun storeVariable(target: LLVMVariable, source: LLVMSingleValue) {
|
||||||
val code = "store ${source.type} $source, ${target.getType()} $target, align ${source.type?.align!!}"
|
val code = "store ${source.type} $source, ${target.getType()} $target, align ${source.type?.align!!}"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copyVariableValue(from: LLVMVariable, to: LLVMVariable) {
|
fun copyVariableValue(target: LLVMVariable, source: LLVMVariable) {
|
||||||
val tmp = getNewVariable(from.type, from.pointer)
|
val tmp = getNewVariable(source.type, source.pointer)
|
||||||
llvmCode.appendln("$tmp = load ${tmp.type}, ${from.getType()} $from, align ${tmp.type.align}")
|
llvmLocalCode.appendln("$tmp = load ${tmp.type}, ${source.getType()} $source, align ${tmp.type.align}")
|
||||||
llvmCode.appendln("store ${to.type} $tmp, ${to.getType()} $to, align ${tmp.type.align}")
|
llvmLocalCode.appendln("store ${target.type} $tmp, ${target.getType()} $target, align ${tmp.type.align}")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadArgument(llvmVariable: LLVMVariable, store: Boolean = true): LLVMVariable {
|
fun loadArgument(llvmVariable: LLVMVariable, store: Boolean = true): LLVMVariable {
|
||||||
@@ -132,63 +145,63 @@ class LLVMBuilder(val arm: Boolean) {
|
|||||||
|
|
||||||
fun loadVariable(target: LLVMVariable, source: LLVMVariable) {
|
fun loadVariable(target: LLVMVariable, source: LLVMVariable) {
|
||||||
val code = "$target = load ${target.type}, ${source.getType()} $source, align ${target.type.align}"
|
val code = "$target = load ${target.type}, ${source.getType()} $source, align ${target.type.align}"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allocVar(target: LLVMVariable) {
|
fun allocVar(target: LLVMVariable) {
|
||||||
llvmCode.appendln("$target = alloca ${target.type}, align ${target.type.align}")
|
llvmLocalCode.appendln("$target = alloca ${target.type}, align ${target.type.align}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addVariableByRef(targetVariable: LLVMVariable, sourceVariable: LLVMVariable, store: Boolean) {
|
fun addVariableByRef(targetVariable: LLVMVariable, sourceVariable: LLVMVariable, store: Boolean) {
|
||||||
llvmCode.appendln("$targetVariable = alloca ${sourceVariable.type}, align ${sourceVariable.type.align}")
|
llvmLocalCode.appendln("$targetVariable = alloca ${sourceVariable.type}, align ${sourceVariable.type.align}")
|
||||||
|
|
||||||
if (store) {
|
if (store) {
|
||||||
llvmCode.appendln("store ${sourceVariable.getType()} $sourceVariable, ${targetVariable.getType()} $targetVariable, align ${targetVariable.type.align}")
|
llvmLocalCode.appendln("store ${sourceVariable.getType()} $sourceVariable, ${targetVariable.getType()} $targetVariable, align ${targetVariable.type.align}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addConstant(allocVariable: LLVMVariable, constantValue: LLVMConstant) {
|
fun addConstant(allocVariable: LLVMVariable, constantValue: LLVMConstant) {
|
||||||
llvmCode.appendln("$allocVariable = alloca ${allocVariable.type}, align ${allocVariable.type.align}")
|
llvmLocalCode.appendln("$allocVariable = alloca ${allocVariable.type}, align ${allocVariable.type.align}")
|
||||||
llvmCode.appendln("store ${allocVariable.type} $constantValue, ${allocVariable.getType()} $allocVariable, align ${allocVariable.type.align}")
|
llvmLocalCode.appendln("store ${allocVariable.type} $constantValue, ${allocVariable.getType()} $allocVariable, align ${allocVariable.type.align}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun declareGlovalVariable(variable: LLVMVariable, defaultValue: String = variable.type.defaultValue) {
|
fun declareGlovalVariable(variable: LLVMVariable, defaultValue: String = variable.type.defaultValue) {
|
||||||
llvmCode.appendln("$variable = global ${variable.type} $defaultValue, align ${variable.type.align}")
|
llvmLocalCode.appendln("$variable = global ${variable.type} $defaultValue, align ${variable.type.align}")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAndGetVariable(source: LLVMVariable): LLVMVariable {
|
fun loadAndGetVariable(source: LLVMVariable): LLVMVariable {
|
||||||
assert(!source.pointer)
|
assert(!source.pointer)
|
||||||
val target = getNewVariable(source.type, source.pointer, source.kotlinName)
|
val target = getNewVariable(source.type, source.pointer, source.kotlinName)
|
||||||
val code = "$target = load ${target.type}, ${source.getType()} $source, align ${target.type.align}"
|
val code = "$target = load ${target.type}, ${source.getType()} $source, align ${target.type.align}"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCondition(condition: LLVMSingleValue, thenLabel: LLVMLabel, elseLabel: LLVMLabel) {
|
fun addCondition(condition: LLVMSingleValue, thenLabel: LLVMLabel, elseLabel: LLVMLabel) {
|
||||||
llvmCode.appendln("br ${condition.getType()} $condition, label $thenLabel, label $elseLabel")
|
llvmLocalCode.appendln("br ${condition.getType()} $condition, label $thenLabel, label $elseLabel")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addUnconditionJump(label: LLVMLabel) {
|
fun addUnconditionJump(label: LLVMLabel) {
|
||||||
llvmCode.appendln("br label $label")
|
llvmLocalCode.appendln("br label $label")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createClass(name: String, fields: List<LLVMVariable>) {
|
fun createClass(name: String, fields: List<LLVMVariable>) {
|
||||||
val code = "%class.$name = type { ${fields.map { it.type }.joinToString()} }"
|
val code = "%class.$name = type { ${fields.map { it.type }.joinToString()} }"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bitcast(dst: LLVMVariable, llvmType: LLVMType): LLVMVariable {
|
fun bitcast(dst: LLVMVariable, llvmType: LLVMType): LLVMVariable {
|
||||||
val empty = getNewVariable(llvmType, true)
|
val empty = getNewVariable(llvmType, true)
|
||||||
val code = "$empty = bitcast ${dst.getType()} $dst to $llvmType*"
|
val code = "$empty = bitcast ${dst.getType()} $dst to $llvmType*"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
|
|
||||||
return empty
|
return empty
|
||||||
}
|
}
|
||||||
|
|
||||||
fun memcpy(castedDst: LLVMVariable, castedSrc: LLVMVariable, size: Int, align: Int = 4, volatile: Boolean = false) {
|
fun memcpy(castedDst: LLVMVariable, castedSrc: LLVMVariable, size: Int, align: Int = 4, volatile: Boolean = false) {
|
||||||
val code = "call void @llvm.memcpy.p0i8.p0i8.i64(i8* $castedDst, i8* $castedSrc, i64 $size, i32 $align, i1 $volatile)"
|
val code = "call void @llvm.memcpy.p0i8.p0i8.i64(i8* $castedDst, i8* $castedSrc, i64 $size, i32 $align, i1 $volatile)"
|
||||||
llvmCode.appendln(code)
|
llvmLocalCode.appendln(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString() = llvmCode.toString()
|
override fun toString() = llvmGlobalCode.toString() + llvmLocalCode.toString()
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.kotlinnative.translator.llvm.types
|
||||||
|
|
||||||
|
class LLVMStringType(val length : Int) : LLVMType() {
|
||||||
|
override val size: Byte = 1
|
||||||
|
override val align = 8
|
||||||
|
override val defaultValue = ""
|
||||||
|
|
||||||
|
override fun toString(): String = "i8*"
|
||||||
|
fun fullType() = "[${length+1} x i8]"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user