mirror of
https://github.com/fluencelabs/asmble
synced 2025-04-25 06:42:22 +00:00
More work on compiler
This commit is contained in:
parent
62ddcbc9a6
commit
d310acde44
@ -2,7 +2,8 @@ group 'asmble'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.1.0'
|
||||
ext.kotlin_version = '1.1.1'
|
||||
ext.asm_version = '5.2'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
@ -21,6 +22,9 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
testCompile "junit:junit:4.12"
|
||||
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
compile "org.ow2.asm:asm-tree:$asm_version"
|
||||
compile "org.ow2.asm:asm-util:$asm_version"
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ sealed class Node {
|
||||
interface Index : Args { val index: Int }
|
||||
interface Reserved : Args { val reserved: Boolean }
|
||||
interface ReservedIndex : Index, Reserved
|
||||
interface AlignOffset : Args { val align: Int; val offset: Int }
|
||||
interface AlignOffset : Args { val align: Int; val offset: Long }
|
||||
interface Const<out T : Number> : Args { val value: T }
|
||||
}
|
||||
|
||||
@ -159,29 +159,29 @@ sealed class Node {
|
||||
data class SetGlobal(override val index: Int) : Instr(), Args.Index
|
||||
|
||||
// Memory operators
|
||||
data class I32Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class F32Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class F64Load(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Load8S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Load8U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Load16S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Load16U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load8S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load8U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load16S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load16U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load32S(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Load32U(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class F32Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class F64Store(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Store8(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Store16(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Store8(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Store16(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I64Store32(override val align: Int, override val offset: Int) : Instr(), Args.AlignOffset
|
||||
data class I32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class F32Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class F64Load(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Load8S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Load8U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Load16S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Load16U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load8S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load8U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load16S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load16U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load32S(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Load32U(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class F32Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class F64Store(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Store8(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I32Store16(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Store8(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Store16(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class I64Store32(override val align: Int, override val offset: Long) : Instr(), Args.AlignOffset
|
||||
data class CurrentMemory(override val reserved: Boolean) : Instr(), Args.Reserved
|
||||
data class GrowMemory(override val reserved: Boolean) : Instr(), Args.Reserved
|
||||
|
||||
@ -351,7 +351,7 @@ sealed class Node {
|
||||
}
|
||||
|
||||
sealed class MemOp<out A : Instr.Args> : InstrOp<A>() {
|
||||
data class AlignOffsetArg(override val name: String, val create: (Int, Int) -> Instr) : MemOp<Instr.Args.AlignOffset>()
|
||||
data class AlignOffsetArg(override val name: String, val create: (Int, Long) -> Instr) : MemOp<Instr.Args.AlignOffset>()
|
||||
data class ReservedArg(override val name: String, val create: (Boolean) -> Instr) : MemOp<Instr.Args.Reserved>()
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
package asmble.ast
|
||||
|
||||
import asmble.io.SExprToStr
|
||||
|
||||
sealed class SExpr {
|
||||
data class Multi(val vals: List<SExpr> = emptyList()) : SExpr()
|
||||
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr()
|
||||
data class Multi(val vals: List<SExpr> = emptyList()) : SExpr() {
|
||||
override fun toString() = SExprToStr.Compact.fromSExpr(this)
|
||||
}
|
||||
data class Symbol(val contents: String = "", val quoted: Boolean = false) : SExpr() {
|
||||
override fun toString() = SExprToStr.Compact.fromSExpr(this)
|
||||
}
|
||||
}
|
47
src/main/kotlin/asmble/compile/jvm/AsmExt.kt
Normal file
47
src/main/kotlin/asmble/compile/jvm/AsmExt.kt
Normal file
@ -0,0 +1,47 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Type
|
||||
import org.objectweb.asm.tree.*
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.reflect.jvm.javaMethod
|
||||
import kotlin.reflect.jvm.reflect
|
||||
|
||||
val <R> KFunction<R>.asmDesc: String get() = Type.getMethodDescriptor(this.javaMethod)
|
||||
|
||||
val <R> KFunction<R>.declarer: Class<*> get() = this.javaMethod!!.declaringClass
|
||||
|
||||
fun KFunction<*>.invokeStatic() =
|
||||
MethodInsnNode(Opcodes.INVOKESTATIC, this.declarer.asmName, this.name, this.asmDesc, false)
|
||||
|
||||
fun KFunction<*>.invokeVirtual() =
|
||||
MethodInsnNode(Opcodes.INVOKEVIRTUAL, this.declarer.asmName, this.name, this.asmDesc, false)
|
||||
|
||||
inline fun <T> forceType(fn: T) = fn
|
||||
inline fun <T : Function<*>> forceFnType(fn: T) = fn.reflect()!!
|
||||
|
||||
val <T : Any> KClass<T>.asmName: String get() = this.java.asmName
|
||||
|
||||
val <T : Any> Class<T>.asmName: String get() = Type.getInternalName(this)
|
||||
|
||||
val KProperty<*>.declarer: Class<*> get() = this.javaField!!.declaringClass
|
||||
val KProperty<*>.asmDesc: String get() = Type.getDescriptor(this.javaField!!.type)
|
||||
|
||||
fun KProperty<*>.getStatic() =
|
||||
FieldInsnNode(Opcodes.GETSTATIC, this.declarer.asmName, this.name, this.asmDesc)
|
||||
|
||||
val Int.const: AbstractInsnNode get() = when (this) {
|
||||
-1 -> InsnNode(Opcodes.ICONST_M1)
|
||||
0 -> InsnNode(Opcodes.ICONST_0)
|
||||
1 -> InsnNode(Opcodes.ICONST_1)
|
||||
2 -> InsnNode(Opcodes.ICONST_2)
|
||||
3 -> InsnNode(Opcodes.ICONST_3)
|
||||
4 -> InsnNode(Opcodes.ICONST_4)
|
||||
5 -> InsnNode(Opcodes.ICONST_5)
|
||||
in 6..Byte.MAX_VALUE -> IntInsnNode(Opcodes.BIPUSH, this)
|
||||
in (Byte.MAX_VALUE + 1)..Short.MAX_VALUE -> IntInsnNode(Opcodes.SIPUSH, this)
|
||||
else -> LdcInsnNode(this)
|
||||
}
|
10
src/main/kotlin/asmble/compile/jvm/AstToAsm.kt
Normal file
10
src/main/kotlin/asmble/compile/jvm/AstToAsm.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import asmble.ast.Node
|
||||
import org.objectweb.asm.tree.ClassNode
|
||||
|
||||
open class AstToAsm {
|
||||
fun fromModule(n: Node.Module): ClassNode {
|
||||
TODO()
|
||||
}
|
||||
}
|
153
src/main/kotlin/asmble/compile/jvm/ByteBufferMem.kt
Normal file
153
src/main/kotlin/asmble/compile/jvm/ByteBufferMem.kt
Normal file
@ -0,0 +1,153 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import asmble.ast.Node
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.tree.InsnNode
|
||||
import org.objectweb.asm.tree.IntInsnNode
|
||||
import org.objectweb.asm.tree.TypeInsnNode
|
||||
import java.nio.Buffer
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.jvm.reflect
|
||||
|
||||
open class ByteBufferMem(val direct: Boolean = true, val defaultMax: Int = 5 * Mem.PAGE_SIZE) : Mem<ByteBuffer> {
|
||||
override val memClass = ByteBuffer::class.java
|
||||
|
||||
override fun create(func: Func, maximum: Int?) = func.addInsns(
|
||||
(maximum ?: defaultMax).const,
|
||||
(if (direct) ByteBuffer::allocateDirect else ByteBuffer::allocate).invokeStatic()
|
||||
).push(memClass)
|
||||
|
||||
override fun init(func: Func, initial: Int) = func.popExpecting(memClass).addInsns(
|
||||
// Set the limit to initial
|
||||
initial.const,
|
||||
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual(),
|
||||
TypeInsnNode(Opcodes.CHECKCAST, ByteBuffer::class.asmName),
|
||||
// Set it to use little endian
|
||||
ByteOrder::LITTLE_ENDIAN.getStatic(),
|
||||
forceFnType<ByteBuffer.(ByteOrder) -> ByteBuffer>(ByteBuffer::order).invokeVirtual()
|
||||
).push(ByteBuffer::class.java)
|
||||
|
||||
override fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func) = func.
|
||||
popExpecting(memClass).
|
||||
addInsns(
|
||||
bytes.size.const,
|
||||
IntInsnNode(Opcodes.NEWARRAY, Opcodes.T_BYTE),
|
||||
*bytes.withIndex().flatMap { (index, byte) ->
|
||||
listOf(InsnNode(Opcodes.DUP), index.const, byte.toInt().const)
|
||||
}.toTypedArray()
|
||||
).
|
||||
apply(buildOffset).popExpecting(Int::class.java).
|
||||
// BOO! https://discuss.kotlinlang.org/t/overload-resolution-ambiguity-function-reference-requiring-local-var/2425
|
||||
addInsns(forceFnType<ByteBuffer.(ByteArray, Int, Int) -> ByteBuffer>(ByteBuffer::put).invokeVirtual()).
|
||||
push(memClass)
|
||||
|
||||
override fun currentMemory(func: Func) = func.popExpecting(memClass).addInsns(
|
||||
forceFnType<ByteBuffer.() -> Int>(ByteBuffer::limit).invokeVirtual(),
|
||||
Mem.PAGE_SIZE.const,
|
||||
InsnNode(Opcodes.IDIV)
|
||||
).push(Int::class.java)
|
||||
|
||||
override fun growMemory(func: Func) = func.popExpecting(memClass).popExpecting(Int::class.java).addInsns(
|
||||
Mem.PAGE_SIZE.const,
|
||||
// TODO: overflow check, e.g. Math.multiplyExact
|
||||
InsnNode(Opcodes.IMUL),
|
||||
forceFnType<ByteBuffer.(Int) -> Buffer>(ByteBuffer::limit).invokeVirtual()
|
||||
).push(ByteBuffer::class.java)
|
||||
|
||||
override fun loadOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memClass).let { func ->
|
||||
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
|
||||
fun Func.load(fn: ByteBuffer.(Int) -> Any, retClass: KClass<*>) =
|
||||
this.addInsns(insn.offset.toInt().const, fn.reflect()!!.invokeVirtual()).push(retClass.java)
|
||||
fun Func.loadI32(fn: ByteBuffer.(Int) -> Any) =
|
||||
this.load(fn, Int::class)
|
||||
fun Func.loadI64(fn: ByteBuffer.(Int) -> Any) =
|
||||
this.load(fn, Long::class)
|
||||
fun Func.toUnsigned(fn: KFunction<*>) =
|
||||
this.addInsns(fn.invokeVirtual())
|
||||
fun Func.toUnsigned64(fn: KFunction<*>) =
|
||||
this.popExpecting(Int::class.java).toUnsigned(fn).push(Long::class.java)
|
||||
fun Func.i32ToI64() =
|
||||
this.popExpecting(Int::class.java).addInsns(InsnNode(Opcodes.I2L)).push(Long::class.java)
|
||||
when (insn) {
|
||||
is Node.Instr.I32Load ->
|
||||
func.loadI32(ByteBuffer::getInt)
|
||||
is Node.Instr.I64Load ->
|
||||
func.loadI64(ByteBuffer::getLong)
|
||||
is Node.Instr.F32Load ->
|
||||
func.load(ByteBuffer::getFloat, Float::class)
|
||||
is Node.Instr.F64Load ->
|
||||
func.load(ByteBuffer::getDouble, Double::class)
|
||||
is Node.Instr.I32Load8S ->
|
||||
func.loadI32(ByteBuffer::get)
|
||||
is Node.Instr.I32Load8U ->
|
||||
func.loadI32(ByteBuffer::get).toUnsigned(java.lang.Byte::toUnsignedInt)
|
||||
is Node.Instr.I32Load16S ->
|
||||
func.loadI32(ByteBuffer::getShort)
|
||||
is Node.Instr.I32Load16U ->
|
||||
func.loadI32(ByteBuffer::getShort).toUnsigned(java.lang.Short::toUnsignedInt)
|
||||
is Node.Instr.I64Load8S ->
|
||||
func.loadI32(ByteBuffer::get).i32ToI64()
|
||||
is Node.Instr.I64Load8U ->
|
||||
func.loadI32(ByteBuffer::get).toUnsigned64(java.lang.Byte::toUnsignedLong)
|
||||
is Node.Instr.I64Load16S ->
|
||||
func.loadI32(ByteBuffer::getShort).i32ToI64()
|
||||
is Node.Instr.I64Load16U ->
|
||||
func.loadI32(ByteBuffer::getShort).toUnsigned64(java.lang.Short::toUnsignedLong)
|
||||
is Node.Instr.I64Load32S ->
|
||||
func.loadI32(ByteBuffer::getInt).i32ToI64()
|
||||
is Node.Instr.I64Load32U ->
|
||||
func.loadI32(ByteBuffer::getInt).toUnsigned64(java.lang.Integer::toUnsignedLong)
|
||||
else -> throw IllegalArgumentException("Unknown load op $insn")
|
||||
}
|
||||
}
|
||||
|
||||
override fun storeOp(func: Func, insn: Node.Instr.Args.AlignOffset) = func.popExpecting(memClass).let { func ->
|
||||
require(insn.offset <= Int.MAX_VALUE, { "Offsets > ${Int.MAX_VALUE} unsupported" }).let { this }
|
||||
fun <T> Func.store32(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
|
||||
// We add the index and then swap with the value already on the stack
|
||||
this.popExpecting(inClass.java).addInsns(
|
||||
insn.offset.toInt().const,
|
||||
InsnNode(Opcodes.SWAP),
|
||||
fn.reflect()!!.invokeVirtual()
|
||||
).push(ByteBuffer::class.java)
|
||||
fun <T> Func.store64(fn: ByteBuffer.(Int, T) -> ByteBuffer, inClass: KClass<*>) =
|
||||
// We add the offset, dup_x2 to swap, and pop to get rid of the dup offset
|
||||
this.popExpecting(inClass.java).addInsns(
|
||||
insn.offset.toInt().const,
|
||||
InsnNode(Opcodes.DUP_X2),
|
||||
InsnNode(Opcodes.POP),
|
||||
fn.reflect()!!.invokeVirtual()
|
||||
).push(ByteBuffer::class.java)
|
||||
fun Func.changeI64ToI32() =
|
||||
this.popExpecting(Long::class.java).push(Int::class.java)
|
||||
when (insn) {
|
||||
is Node.Instr.I32Store ->
|
||||
func.store32(ByteBuffer::putInt, Int::class)
|
||||
is Node.Instr.I64Store ->
|
||||
func.store64(ByteBuffer::putLong, Long::class)
|
||||
is Node.Instr.F32Store ->
|
||||
func.store32(ByteBuffer::putFloat, Float::class)
|
||||
is Node.Instr.F64Store ->
|
||||
func.store64(ByteBuffer::putDouble, Double::class)
|
||||
is Node.Instr.I32Store8 ->
|
||||
func.addInsns(InsnNode(Opcodes.I2B)).store32(ByteBuffer::put, Int::class)
|
||||
is Node.Instr.I32Store16 ->
|
||||
func.addInsns(InsnNode(Opcodes.I2S)).store32(ByteBuffer::putShort, Int::class)
|
||||
is Node.Instr.I64Store8 ->
|
||||
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2B)).
|
||||
changeI64ToI32().store32(ByteBuffer::put, Int::class)
|
||||
is Node.Instr.I64Store16 ->
|
||||
func.addInsns(InsnNode(Opcodes.L2I), InsnNode(Opcodes.I2S)).
|
||||
changeI64ToI32().store32(ByteBuffer::putShort, Int::class)
|
||||
is Node.Instr.I64Store32 ->
|
||||
func.addInsns(InsnNode(Opcodes.L2I)).
|
||||
changeI64ToI32().store32(ByteBuffer::putInt, Int::class)
|
||||
else -> throw IllegalArgumentException("Unknown store op $insn")
|
||||
}
|
||||
}
|
||||
|
||||
companion object : ByteBufferMem()
|
||||
}
|
23
src/main/kotlin/asmble/compile/jvm/Func.kt
Normal file
23
src/main/kotlin/asmble/compile/jvm/Func.kt
Normal file
@ -0,0 +1,23 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import asmble.ast.Node
|
||||
import org.objectweb.asm.tree.AbstractInsnNode
|
||||
|
||||
data class Func(val insns: List<AbstractInsnNode>, val stack: List<Class<*>>) {
|
||||
|
||||
fun addInsns(vararg insns: AbstractInsnNode) = copy(insns = this.insns + insns)
|
||||
|
||||
fun apply(fn: (Func) -> Func) = fn(this)
|
||||
|
||||
fun push(vararg types: Class<*>) = copy(stack = stack + types)
|
||||
|
||||
fun popExpectingNum() = popExpecting(Int::class.java, Long::class.java, Float::class.java, Double::class.java)
|
||||
|
||||
fun popExpecting(vararg types: Class<*>) = popExpectingAny(types::contains)
|
||||
|
||||
fun popExpectingAny(pred: (Class<*>) -> Boolean): Func {
|
||||
require(stack.isNotEmpty(), { "Stack is empty" })
|
||||
require(pred(stack.last()), { "Stack var type ${stack.last()} unexpected" })
|
||||
return copy(stack = stack.dropLast(1))
|
||||
}
|
||||
}
|
40
src/main/kotlin/asmble/compile/jvm/Mem.kt
Normal file
40
src/main/kotlin/asmble/compile/jvm/Mem.kt
Normal file
@ -0,0 +1,40 @@
|
||||
package asmble.compile.jvm
|
||||
|
||||
import asmble.ast.Node
|
||||
|
||||
interface Mem<T : Any> {
|
||||
// The class to accept as "memory"
|
||||
val memClass: Class<T>
|
||||
|
||||
fun create(func: Func, maximum: Int?): Func
|
||||
|
||||
// Caller can trust the mem instance is on the stack and must handle it. If
|
||||
// it's already there after call anyways, this can leave the mem inst on the
|
||||
// stack and it will be reused or popped.
|
||||
fun init(func: Func, initial: Int): Func
|
||||
|
||||
// Caller can trust the mem instance is on the stack, buildOffset puts
|
||||
// offset on the stack. If it's already there after call anyways, this can
|
||||
// leave the mem inst on the stack and it will be reused or popped.
|
||||
fun data(func: Func, bytes: ByteArray, buildOffset: (Func) -> Func): Func
|
||||
|
||||
// Caller can trust the mem instance is on the stack.
|
||||
fun currentMemory(func: Func): Func
|
||||
|
||||
// Caller can trust the mem instance and then i32 page count is on the stack.
|
||||
// If it's already there after call anyways, this can leave the mem inst on
|
||||
// the stack and it will be reused or popped.
|
||||
fun growMemory(func: Func): Func
|
||||
|
||||
// Caller can trust the mem instance is on the stack
|
||||
fun loadOp(func: Func, insn: Node.Instr.Args.AlignOffset): Func
|
||||
|
||||
// Caller can trust the mem instance is on the stack followed
|
||||
// by the value. If it's already there after call anyways, this can
|
||||
// leave the mem inst on the stack and it will be reused or popped.
|
||||
fun storeOp(func: Func, insn: Node.Instr.Args.AlignOffset): Func
|
||||
|
||||
companion object {
|
||||
const val PAGE_SIZE = 65536
|
||||
}
|
||||
}
|
@ -2,9 +2,34 @@ package asmble.io
|
||||
|
||||
import asmble.ast.Node
|
||||
import asmble.ast.SExpr
|
||||
import asmble.ast.Script
|
||||
|
||||
open class AstToSExpr {
|
||||
|
||||
fun fromAction(v: Script.Cmd.Action) = when(v) {
|
||||
is Script.Cmd.Action.Invoke -> newMulti("invoke", v.name) + v.string + fromInstrs(v.exprs)
|
||||
is Script.Cmd.Action.Get -> newMulti("get", v.name) + v.string
|
||||
}
|
||||
|
||||
fun fromAssertion(v: Script.Cmd.Assertion) = when(v) {
|
||||
is Script.Cmd.Assertion.Return -> newMulti("assert_return") + fromAction(v.action) + fromInstrs(v.exprs)
|
||||
is Script.Cmd.Assertion.ReturnNan -> newMulti("assert_return_nan") + fromAction(v.action)
|
||||
is Script.Cmd.Assertion.Trap -> newMulti("assert_trap") + fromAction(v.action) + v.failure
|
||||
is Script.Cmd.Assertion.Malformed -> newMulti("assert_malformed") + fromModule(v.module) + v.failure
|
||||
is Script.Cmd.Assertion.Invalid -> newMulti("assert_invalid") + fromModule(v.module) + v.failure
|
||||
is Script.Cmd.Assertion.SoftInvalid -> newMulti("assert_soft_invalid") + fromModule(v.module) + v.failure
|
||||
is Script.Cmd.Assertion.Unlinkable -> newMulti("assert_unlinkable") + fromModule(v.module) + v.failure
|
||||
is Script.Cmd.Assertion.TrapModule -> newMulti("assert_trap") + fromModule(v.module) + v.failure
|
||||
}
|
||||
|
||||
fun fromCmd(v: Script.Cmd): SExpr.Multi = when(v) {
|
||||
is Script.Cmd.Module -> fromModule(v.module)
|
||||
is Script.Cmd.Register -> fromRegister(v)
|
||||
is Script.Cmd.Action -> fromAction(v)
|
||||
is Script.Cmd.Assertion -> fromAssertion(v)
|
||||
is Script.Cmd.Meta -> fromMeta(v)
|
||||
}
|
||||
|
||||
fun fromData(v: Node.Data) =
|
||||
(newMulti("data") + v.index) + (newMulti("offset") + fromInstrs(v.offset)) + v.data.toString(Charsets.UTF_8)
|
||||
|
||||
@ -100,16 +125,19 @@ open class AstToSExpr {
|
||||
|
||||
fun fromMemorySig(v: Node.Type.Memory) = fromResizableLimits(v.limits)
|
||||
|
||||
fun fromMeta(v: Script.Cmd.Meta) = when(v) {
|
||||
is Script.Cmd.Meta.Script -> newMulti("script", v.name) + fromScript(v.script)
|
||||
is Script.Cmd.Meta.Input -> newMulti("input", v.name) + v.str
|
||||
is Script.Cmd.Meta.Output -> newMulti("output", v.name) + v.str
|
||||
}
|
||||
|
||||
fun fromModule(v: Node.Module, name: String? = null): SExpr.Multi {
|
||||
var ret = newMulti("module", name)
|
||||
|
||||
// We only want types that are not referenced in import
|
||||
val ignoreTypeIndices = v.imports.mapNotNull {
|
||||
(it.kind as? Node.Import.Kind.Func)?.let { it.typeIndex }
|
||||
}.toSet()
|
||||
val ignoreTypeIndices = v.imports.mapNotNull { (it.kind as? Node.Import.Kind.Func)?.typeIndex }.toSet()
|
||||
|
||||
ret += v.types.filterIndexed { i, _ -> ignoreTypeIndices.contains(i) }.map { fromTypeDef(it) }
|
||||
ret += v.funcs.map { fromFunc(it) }
|
||||
ret += v.imports.map { fromImport(it, v.types) }
|
||||
ret += v.exports.map(this::fromExport)
|
||||
ret += v.tables.map { fromTable(it) }
|
||||
@ -118,13 +146,18 @@ open class AstToSExpr {
|
||||
ret += v.elems.map(this::fromElem)
|
||||
ret += v.data.map(this::fromData)
|
||||
ret += v.startFuncIndex?.let(this::fromStart)
|
||||
ret += v.funcs.map { fromFunc(it) }
|
||||
return ret
|
||||
}
|
||||
|
||||
fun fromNum(v: Number) = fromString(v.toString())
|
||||
|
||||
fun fromRegister(v: Script.Cmd.Register) = (newMulti("register") + v.string) + v.name
|
||||
|
||||
fun fromResizableLimits(v: Node.ResizableLimits) = listOfNotNull(fromNum(v.initial), v.maximum?.let(this::fromNum))
|
||||
|
||||
fun fromScript(v: Script) = v.commands.map(this::fromCmd)
|
||||
|
||||
fun fromStart(v: Int) = newMulti("start") + v
|
||||
|
||||
fun fromString(v: String, quoted: Boolean = false) = SExpr.Symbol(v, quoted)
|
||||
|
@ -224,7 +224,7 @@ open class SExprToAst {
|
||||
val name = exp.maybeName(currIndex)
|
||||
if (name != null) currIndex++
|
||||
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
||||
if (maybeImpExp == null) currIndex++
|
||||
if (maybeImpExp != null) currIndex++
|
||||
val sig = toGlobalSig(exp.vals[currIndex])
|
||||
currIndex++
|
||||
val (instrs, _) = toInstrs(exp, currIndex, ExprContext(nameMap))
|
||||
@ -268,7 +268,12 @@ open class SExprToAst {
|
||||
}
|
||||
}
|
||||
|
||||
fun toInstrs(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<List<Node.Instr>, Int> {
|
||||
fun toInstrs(
|
||||
exp: SExpr.Multi,
|
||||
offset: Int,
|
||||
ctx: ExprContext,
|
||||
mustCompleteExp: Boolean = true
|
||||
): Pair<List<Node.Instr>, Int> {
|
||||
var runningOffset = 0
|
||||
var ret = emptyList<Node.Instr>()
|
||||
while (offset + runningOffset < exp.vals.size) {
|
||||
@ -277,13 +282,16 @@ open class SExprToAst {
|
||||
ret += maybeInstrAndOffset.first
|
||||
runningOffset += maybeInstrAndOffset.second
|
||||
}
|
||||
if (mustCompleteExp) require(offset + runningOffset == exp.vals.size) {
|
||||
"Unrecognized instruction: ${exp.vals[offset + runningOffset]}"
|
||||
}
|
||||
return Pair(ret, runningOffset)
|
||||
}
|
||||
|
||||
fun toInstrMaybe(exp: SExpr.Multi, offset: Int, ctx: ExprContext): Pair<List<Node.Instr>, Int> {
|
||||
// <expr>
|
||||
if (exp.vals[offset] is SExpr.Multi) {
|
||||
val exprs = toExprMaybe(exp, ctx)
|
||||
val exprs = toExprMaybe(exp.vals[offset] as SExpr.Multi, ctx)
|
||||
return Pair(exprs, if (exprs.isEmpty()) 0 else 1)
|
||||
}
|
||||
// <op>
|
||||
@ -364,7 +372,7 @@ open class SExprToAst {
|
||||
val name = exp.maybeName(currIndex)
|
||||
if (name != null) currIndex++
|
||||
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
||||
if (maybeImpExp == null) currIndex++
|
||||
if (maybeImpExp != null) currIndex++
|
||||
// Try data approach
|
||||
if (exp.vals[currIndex] is SExpr.Multi) throw Exception("Data string not yet supported for memory")
|
||||
return Triple(name, toMemorySig(exp, currIndex), maybeImpExp)
|
||||
@ -537,11 +545,11 @@ open class SExprToAst {
|
||||
is InstrOp.VarOp.IndexArg -> Pair(op.create(oneVar()), 2)
|
||||
is InstrOp.MemOp.AlignOffsetArg -> {
|
||||
var count = 1
|
||||
var instrOffset = 0
|
||||
var instrOffset = 0L
|
||||
var instrAlign = 0
|
||||
if (exp.vals.size > offset + count) exp.vals[offset + count].symbolStr().also {
|
||||
if (it != null && it.startsWith("offset=")) {
|
||||
instrOffset = it.substring(7).toInt()
|
||||
instrOffset = it.substring(7).toLong()
|
||||
count++
|
||||
}
|
||||
}
|
||||
@ -605,7 +613,7 @@ open class SExprToAst {
|
||||
val name = exp.maybeName(currIndex)
|
||||
if (name != null) currIndex++
|
||||
val maybeImpExp = toImportOrExportMaybe(exp, currIndex)
|
||||
if (maybeImpExp == null) currIndex++
|
||||
if (maybeImpExp != null) currIndex++
|
||||
// Try elem type approach
|
||||
val elemType = toElemTypeMaybe(exp, currIndex)
|
||||
if (elemType != null) {
|
||||
|
@ -2,7 +2,11 @@ package asmble.io
|
||||
|
||||
import asmble.ast.SExpr
|
||||
|
||||
open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
|
||||
open class SExprToStr(val depthBeforeNewline: Int, val countBeforeNewlineAll: Int, val indent: String) {
|
||||
|
||||
@Suppress("UNCHECKED_CAST") // TODO: why?
|
||||
fun fromSExpr(vararg exp: SExpr): String = appendAll(exp.asList(), StringBuilder()).trim().toString()
|
||||
|
||||
@Suppress("UNCHECKED_CAST") // TODO: why?
|
||||
fun <T : Appendable> append(exp: SExpr, sb: T = StringBuilder() as T, indentLevel: Int = 0) = when(exp) {
|
||||
is SExpr.Symbol -> appendSymbol(exp, sb)
|
||||
@ -16,11 +20,12 @@ open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
|
||||
sb.append('"')
|
||||
exp.contents.forEach {
|
||||
sb.append(
|
||||
if (it in "'\"\\") "\\$it"
|
||||
else if (it == '\n') "\\n"
|
||||
else if (it == '\t') "\\t"
|
||||
else if (!it.requiresQuote) it.toString()
|
||||
else "\\" + (it.toInt() and 0xFF).toString(16)
|
||||
if (it in "'\"\\") "\\$it"
|
||||
else if (it == '\n') "\\n"
|
||||
else if (it == '\t') "\\t"
|
||||
else if (it == ' ') " "
|
||||
else if (!it.requiresQuote) it.toString()
|
||||
else "\\" + (it.toInt() and 0xFF).toString(16)
|
||||
)
|
||||
}
|
||||
sb.append('"')
|
||||
@ -31,17 +36,32 @@ open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
|
||||
@Suppress("UNCHECKED_CAST") // TODO: why?
|
||||
fun <T : Appendable> appendMulti(exp: SExpr.Multi, sb: T = StringBuilder() as T, indentLevel: Int = 0): T {
|
||||
sb.append('(')
|
||||
exp.vals.forEachIndexed { index, sub ->
|
||||
if (sub.maxDepth() <= depthBeforeNewline) {
|
||||
if (index > 0) sb.append(' ')
|
||||
appendAll(exp.vals, sb, indentLevel)
|
||||
sb.append(')')
|
||||
return sb
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST") // TODO: why?
|
||||
fun <T : Appendable> appendAll(exps: List<SExpr>, sb: T = StringBuilder() as T, indentLevel: Int = 0): T {
|
||||
val newlineAll = exps.sumBy { it.count() } >= countBeforeNewlineAll
|
||||
var wasLastNewline = false
|
||||
exps.forEachIndexed { index, sub ->
|
||||
// No matter what, if the first is a symbol
|
||||
val isFirstAsSymbol = index == 0 && sub is SExpr.Symbol
|
||||
val shouldNewline = !isFirstAsSymbol && (newlineAll || sub.maxDepth() > depthBeforeNewline)
|
||||
if (!shouldNewline) {
|
||||
if (index > 0 && !wasLastNewline) sb.append(' ')
|
||||
append(sub, sb, indentLevel)
|
||||
wasLastNewline = false
|
||||
} else {
|
||||
sb.append("\n").append(indent.repeat(indentLevel + 1))
|
||||
if (!wasLastNewline) sb.append("\n").append(indent.repeat(indentLevel))
|
||||
append(sub, sb, indentLevel + 1)
|
||||
sb.append("\n").append(indent.repeat(indentLevel))
|
||||
sb.append("\n")
|
||||
if (index < exps.size - 1) sb.append(indent.repeat(indentLevel))
|
||||
else if (indentLevel > 0) sb.append(indent.repeat(indentLevel - 1))
|
||||
wasLastNewline = true
|
||||
}
|
||||
}
|
||||
sb.append(')')
|
||||
return sb
|
||||
}
|
||||
|
||||
@ -50,10 +70,17 @@ open class SExprToStr(val depthBeforeNewline: Int, val indent: String) {
|
||||
val Char.requiresQuote: Boolean get() =
|
||||
this > '~' || (!this.isLetterOrDigit() && this !in "_.+-*/^~=<>!?@#$%&|:'`")
|
||||
|
||||
fun SExpr.count(): Int = when(this) {
|
||||
is SExpr.Symbol -> 1
|
||||
is SExpr.Multi -> this.vals.sumBy { it.count() }
|
||||
}
|
||||
|
||||
fun SExpr.maxDepth(): Int = when(this) {
|
||||
is SExpr.Symbol -> 0
|
||||
is SExpr.Multi -> 1 + (this.vals.map { it.maxDepth() }.max() ?: 0)
|
||||
}
|
||||
|
||||
companion object : SExprToStr(depthBeforeNewline = 3, indent = " ")
|
||||
companion object : SExprToStr(depthBeforeNewline = 3, countBeforeNewlineAll = 10, indent = " ") {
|
||||
val Compact = SExprToStr(depthBeforeNewline = Int.MAX_VALUE, countBeforeNewlineAll = Int.MAX_VALUE, indent = "")
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +1,38 @@
|
||||
package asmble.io
|
||||
|
||||
import asmble.ast.SExpr
|
||||
import asmble.util.Either
|
||||
import asmble.util.IdentityMap
|
||||
import asmble.util.plus
|
||||
|
||||
open class StrToSExpr {
|
||||
data class ParseError(val line: Int, val char: Int, val msg: String)
|
||||
sealed class ParseResult {
|
||||
data class Pos(val line: Int, val char: Int)
|
||||
data class Success(val vals: List<SExpr>, val exprOffsetMap: IdentityMap<SExpr, Pos>) : ParseResult()
|
||||
data class Error(val pos: Pos, val msg: String) : ParseResult()
|
||||
}
|
||||
|
||||
fun parse(str: CharSequence): Either<SExpr.Multi, ParseError> {
|
||||
fun parse(str: CharSequence): ParseResult {
|
||||
val state = ParseState(str)
|
||||
var ret = emptyList<SExpr>()
|
||||
while (!state.isEof) {
|
||||
ret += state.nextSExpr() ?: break
|
||||
if (state.err != null) {
|
||||
val line = str.substring(0, state.offset).substringCount("\n") + 1
|
||||
val char = state.offset - str.lastIndexOf('\n', state.offset)
|
||||
return Either.Right(ParseError(line, char, state.err!!))
|
||||
}
|
||||
if (state.err != null) return ParseResult.Error(str.posFromOffset(state.offset), state.err!!)
|
||||
}
|
||||
if (ret.size == 1 && ret[0] is SExpr.Multi) return Either.Left(ret[0] as SExpr.Multi)
|
||||
else return Either.Left(SExpr.Multi(ret))
|
||||
val retVals = if (ret.size == 1 && ret[0] is SExpr.Multi) (ret[0] as SExpr.Multi).vals else ret
|
||||
return ParseResult.Success(retVals, state.exprOffsetMap)
|
||||
}
|
||||
|
||||
private class ParseState(val str: CharSequence, var offset: Int = 0, var err: String? = null) {
|
||||
private class ParseState(
|
||||
val str: CharSequence,
|
||||
var exprOffsetMap: IdentityMap<SExpr, ParseResult.Pos> = IdentityMap(),
|
||||
var offset: Int = 0,
|
||||
var err: String? = null
|
||||
) {
|
||||
fun nextSExpr(): SExpr? {
|
||||
skipWhitespace()
|
||||
if (isEof) return null
|
||||
// What type of expr do we have here?
|
||||
val origOffset = offset
|
||||
when (str[offset]) {
|
||||
'(' -> {
|
||||
offset++
|
||||
@ -37,6 +44,7 @@ open class StrToSExpr {
|
||||
if (err == null) {
|
||||
if (str[offset] == ')') offset++ else err = "EOF when expected ')'"
|
||||
}
|
||||
exprOffsetMap += ret to str.posFromOffset(origOffset)
|
||||
return ret
|
||||
}
|
||||
'"' -> {
|
||||
@ -74,13 +82,17 @@ open class StrToSExpr {
|
||||
}
|
||||
if (err == null && str[offset] != '"') err = "EOF when expected '\"'"
|
||||
else if (err == null) offset++
|
||||
return SExpr.Symbol(retStr, true)
|
||||
val ret = SExpr.Symbol(retStr, true)
|
||||
exprOffsetMap += ret to str.posFromOffset(origOffset)
|
||||
return ret
|
||||
}
|
||||
else -> {
|
||||
// Go until next quote or whitespace or parens
|
||||
val origOffset = offset
|
||||
while (!isEof && !"();\"".contains(str[offset]) && !str[offset].isWhitespace()) offset++
|
||||
return if (origOffset == offset) null else SExpr.Symbol(str.substring(origOffset, offset))
|
||||
if (origOffset == offset) return null
|
||||
val ret = SExpr.Symbol(str.substring(origOffset, offset))
|
||||
exprOffsetMap += ret to str.posFromOffset(origOffset)
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,6 +127,11 @@ open class StrToSExpr {
|
||||
val isEof: Boolean inline get() = offset >= str.length
|
||||
}
|
||||
|
||||
fun CharSequence.posFromOffset(offset: Int) = ParseResult.Pos(
|
||||
line = this.substring(0, offset).substringCount("\n") + 1,
|
||||
char = offset - this.lastIndexOf('\n', offset)
|
||||
)
|
||||
|
||||
fun CharSequence.substringCount(str: String): Int {
|
||||
var lastIndex = 0
|
||||
var count = 0
|
||||
|
13
src/main/kotlin/asmble/util/IdentityMap.kt
Normal file
13
src/main/kotlin/asmble/util/IdentityMap.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package asmble.util
|
||||
|
||||
class IdentityMap<K, V>(override val entries: Set<Map.Entry<K, V>> = emptySet()) : AbstractMap<K, V>() {
|
||||
override fun containsKey(key: K) = entries.find { it.key === key } != null
|
||||
override operator fun get(key: K) = entries.find { it.key === key }?.value
|
||||
}
|
||||
|
||||
public operator fun <K, V> IdentityMap<K, V>.plus(pair: Pair<K, V>): IdentityMap<K, V> {
|
||||
return IdentityMap(this.entries + object : Map.Entry<K, V> {
|
||||
override val key: K get() = pair.first
|
||||
override val value: V get() = pair.second
|
||||
})
|
||||
}
|
@ -9,7 +9,11 @@ class CoreTest(val unit: CoreTestUnit) {
|
||||
|
||||
@Test
|
||||
fun testName() {
|
||||
println("\nYay: ${unit.name}: ${unit.ast}")
|
||||
println()
|
||||
println("AST SExpr: " + unit.ast)
|
||||
println("AST Str: " + SExprToStr.fromSExpr(*unit.ast.toTypedArray()))
|
||||
println("AST: " + unit.script)
|
||||
println("AST Str: " + SExprToStr.fromSExpr(*AstToSExpr.fromScript(unit.script).toTypedArray()))
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -1,36 +1,49 @@
|
||||
package asmble.io
|
||||
|
||||
import asmble.ast.SExpr
|
||||
import asmble.util.Either
|
||||
import asmble.ast.Script
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class CoreTestUnit(val name: String, val ast: SExpr.Multi, val expectedOutput: String?) {
|
||||
class CoreTestUnit(val name: String, val wast: String, val expectedOutput: String?) {
|
||||
|
||||
override fun toString() = "Spec unit: $name"
|
||||
|
||||
val parseResult: StrToSExpr.ParseResult.Success by lazy {
|
||||
StrToSExpr.parse(wast).let {
|
||||
when (it) {
|
||||
is StrToSExpr.ParseResult.Error -> throw Exception("$name[${it.pos}] Parse fail: ${it.msg}")
|
||||
is StrToSExpr.ParseResult.Success -> it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ast: List<SExpr> get() = parseResult.vals
|
||||
|
||||
val script: Script by lazy { SExprToAst.toScript(SExpr.Multi(ast)) }
|
||||
|
||||
companion object {
|
||||
val unitsPath = "/spec/test/core"
|
||||
fun loadAll(): List<CoreTestUnit> {
|
||||
return loadFromResourcePath("/local-spec") + loadFromResourcePath(unitsPath)
|
||||
}
|
||||
|
||||
fun loadFromResourcePath(basePath: String): List<CoreTestUnit> {
|
||||
require(basePath.last() != '/')
|
||||
val jcls = CoreTestUnit::class.java
|
||||
val uri = jcls.getResource(unitsPath).toURI()
|
||||
val uri = jcls.getResource(basePath).toURI()
|
||||
val fs = if (uri.scheme == "jar") FileSystems.newFileSystem(uri, emptyMap<String, Any>()) else null
|
||||
fs.use { fs ->
|
||||
val path = fs?.getPath(unitsPath) ?: Paths.get(uri)
|
||||
return Files.walk(path, 1).filter { it.toString().endsWith(".wast") }.map {
|
||||
println("Loading $it")
|
||||
val parseResult = StrToSExpr.parse(it.toUri().toURL().readText())
|
||||
when(parseResult) {
|
||||
is Either.Right -> throw Exception("Error loading $it: ${parseResult.v}")
|
||||
is Either.Left -> {
|
||||
val name = it.fileName.toString().substringBeforeLast(".wast")
|
||||
val expectedOutput =
|
||||
jcls.getResource("/spec/test/core/expected-output/$name.wast.log")?.readText()
|
||||
CoreTestUnit(name, parseResult.v, expectedOutput)
|
||||
}
|
||||
}
|
||||
val path = fs?.getPath(basePath) ?: Paths.get(uri)
|
||||
return Files.walk(path, 1).filter { it.toString().endsWith("address.wast") }.map {
|
||||
val name = it.fileName.toString().substringBeforeLast(".wast")
|
||||
CoreTestUnit(
|
||||
name = name,
|
||||
wast = it.toUri().toURL().readText(),
|
||||
expectedOutput = jcls.getResource("$basePath/expected-output/$name.wast.log")?.readText()
|
||||
)
|
||||
}.collect(Collectors.toList())
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user