17 Commits

Author SHA1 Message Date
vms
3b1c314c35 fix some comments in FuncContext 2018-10-17 12:36:55 +03:00
vms
f1a2d14463 update outdated urls to WebAssembly specification 2018-09-14 18:48:30 +03:00
vms
ab269fdbd8 fix the issue with absence of bintrayUser and bintrayKey parameters 2018-09-14 18:35:30 +03:00
da70c9fca4 Fix for "lateinit property logger has not been initialized" 2018-08-28 16:50:37 +04:00
a1a5563367 Publishing the fork to binTray 2018-08-14 17:32:10 +04:00
e489e7c889 Publishing the fork to binTray 2018-08-14 17:22:30 +04:00
c1391b2701 Expected but isn't working version 2018-08-14 12:11:48 +04:00
6b28c5a93b merge original-master to master 2018-08-10 13:10:28 +04:00
1990f46743 merge asmble-master to master 2018-07-26 10:49:28 +04:00
559df45f09 Merge pull request #1 from cretz/master
Fetch master changes
2018-07-26 10:34:42 +04:00
80a8a1fbb9 Temporary disable rust-regex example 2018-07-25 10:35:37 +04:00
dd72c7124c Fix Rust Simple and Rust String examples 2018-07-25 10:21:38 +04:00
c04a3c4a9b Add some java docs 2018-07-24 11:00:41 +04:00
51520ac07d Add some java docs 2018-07-23 12:52:30 +04:00
97660de6ba Add some java docs 2018-07-20 12:52:21 +04:00
cee7a86773 Fix rust-regex example 2018-07-19 17:02:25 +04:00
cfa4a35af1 remove C example 2018-07-19 09:14:01 +04:00
26 changed files with 344 additions and 247 deletions

View File

@ -14,13 +14,14 @@ buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'me.champeau.gradle:jmh-gradle-plugin:0.4.5'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
}
}
allprojects {
apply plugin: 'java'
group 'com.github.cretz.asmble'
version '0.4.0-SNAPSHOT'
version '0.4.0-fl'
repositories {
mavenCentral()
@ -34,7 +35,7 @@ project(':annotations') {
options.addStringOption 'Xdoclint:all', '-Xdoclint:-missing'
}
publishSettings(project, 'asmble-annotations', 'Asmble WASM Annotations', true)
publishSettings(project, 'asmble-annotations', 'Asmble WASM Annotations')
}
project(':compiler') {
@ -58,7 +59,7 @@ project(':compiler') {
testCompile "org.ow2.asm:asm-debug-all:$asm_version"
}
publishSettings(project, 'asmble-compiler', 'Asmble WASM Compiler', false)
publishSettings(project, 'asmble-compiler', 'Asmble WASM Compiler')
}
project(':examples') {
@ -67,38 +68,6 @@ project(':examples') {
compileOnly project(':compiler')
}
// C/C++ example helpers
task cToWasm {
doFirst {
mkdir 'build'
exec {
def cFileName = fileTree(dir: 'src', includes: ['*.c']).files.iterator().next()
commandLine 'clang', '--target=wasm32-unknown-unknown-wasm', '-O3', cFileName, '-c', '-o', 'build/lib.wasm'
}
}
}
task showCWast(type: JavaExec) {
dependsOn cToWasm
classpath configurations.compileClasspath
main = 'asmble.cli.MainKt'
doFirst {
args 'translate', 'build/lib.wasm'
}
}
task compileCWasm(type: JavaExec) {
dependsOn cToWasm
classpath configurations.compileClasspath
main = 'asmble.cli.MainKt'
doFirst {
def outFile = 'build/wasm-classes/' + wasmCompiledClassName.replace('.', '/') + '.class'
file(outFile).parentFile.mkdirs()
args 'compile', 'build/lib.wasm', wasmCompiledClassName, '-out', outFile
}
}
// Go example helpers
task goToWasm {
@ -173,17 +142,6 @@ project(':examples') {
}
}
project(':examples:c-simple') {
apply plugin: 'application'
ext.wasmCompiledClassName = 'asmble.generated.CSimple'
dependencies {
compile files('build/wasm-classes')
}
compileJava {
dependsOn compileCWasm
}
mainClassName = 'asmble.examples.csimple.Main'
}
project(':examples:go-simple') {
apply plugin: 'application'
@ -197,28 +155,30 @@ project(':examples:go-simple') {
mainClassName = 'asmble.examples.gosimple.Main'
}
project(':examples:rust-regex') {
apply plugin: 'application'
apply plugin: 'me.champeau.gradle.jmh'
ext.wasmCompiledClassName = 'asmble.generated.RustRegex'
dependencies {
compile files('build/wasm-classes')
testCompile 'junit:junit:4.12'
}
compileJava {
dependsOn compileRustWasm
}
mainClassName = 'asmble.examples.rustregex.Main'
test {
testLogging.showStandardStreams = true
testLogging.events 'PASSED', 'SKIPPED'
}
jmh {
iterations = 5
warmupIterations = 5
fork = 3
}
}
// todo temporary disable Rust regex, because some strings in wasm code exceed the size in 65353 bytes.
// project(':examples:rust-regex') {
// apply plugin: 'application'
// apply plugin: 'me.champeau.gradle.jmh'
// ext.wasmCompiledClassName = 'asmble.generated.RustRegex'
// dependencies {
// compile files('build/wasm-classes')
// testCompile 'junit:junit:4.12'
// }
// compileJava {
// dependsOn compileRustWasm
// }
// mainClassName = 'asmble.examples.rustregex.Main'
// test {
// testLogging.showStandardStreams = true
// testLogging.events 'PASSED', 'SKIPPED'
// }
// jmh {
// iterations = 5
// warmupIterations = 5
// fork = 3
// }
// }
project(':examples:rust-simple') {
apply plugin: 'application'
@ -244,75 +204,60 @@ project(':examples:rust-string') {
mainClassName = 'asmble.examples.ruststring.Main'
}
def publishSettings(project, projectName, projectDescription, includeJavadoc) {
def publishSettings(project, projectName, projectDescription) {
project.with {
if (!project.hasProperty('ossrhUsername')) return
apply plugin: 'com.jfrog.bintray'
apply plugin: 'maven-publish'
apply plugin: 'maven'
apply plugin: 'signing'
archivesBaseName = projectName
task packageSources(type: Jar) {
task sourcesJar(type: Jar) {
from sourceSets.main.allJava
classifier = 'sources'
from sourceSets.main.allSource
}
if (includeJavadoc) {
task packageJavadoc(type: Jar, dependsOn: 'javadoc') {
from javadoc.destinationDir
classifier = 'javadoc'
}
} else {
task packageJavadoc(type: Jar) {
// Empty to satisfy Sonatype's javadoc.jar requirement
classifier 'javadoc'
}
}
artifacts {
archives packageSources, packageJavadoc
}
signing {
sign configurations.archives
}
uploadArchives {
repositories {
mavenDeployer {
beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
repository(url: 'https://oss.sonatype.org/service/local/staging/deploy/maven2/') {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
snapshotRepository(url: 'https://oss.sonatype.org/content/repositories/snapshots/') {
authentication(userName: ossrhUsername, password: ossrhPassword)
}
pom.project {
name projectName
packaging 'jar'
description projectDescription
url 'https://github.com/cretz/asmble'
scm {
connection 'scm:git:git@github.com:cretz/asmble.git'
developerConnection 'scm:git:git@github.com:cretz/asmble.git'
url 'git@github.com:cretz/asmble.git'
}
licenses {
license {
name 'MIT License'
url 'https://opensource.org/licenses/MIT'
}
}
developers {
developer {
id 'cretz'
name 'Chad Retz'
url 'https://github.com/cretz'
}
}
}
publishing {
publications {
MyPublication(MavenPublication) {
from components.java
groupId group
artifactId projectName
artifact sourcesJar
version version
}
}
}
bintray {
if(!hasProperty("bintrayUser") || !hasProperty("bintrayKey")) {
return
}
user = bintrayUser
key = bintrayKey
publications = ['MyPublication']
//[Default: false] Whether to override version artifacts already published
override = false
//[Default: false] Whether version should be auto published after an upload
publish = true
pkg {
repo = 'releases'
name = projectName
userOrg = 'fluencelabs'
licenses = ['MIT']
vcsUrl = 'https://github.com/fluencelabs/asmble'
version {
name = project.version
desc = projectDescription
released = new Date()
vcsTag = project.version
}
}
}
}
}
}

View File

@ -3,7 +3,19 @@ package asmble.ast
import java.util.*
import kotlin.reflect.KClass
/**
* All WebAssembly AST nodes as static inner classes.
*/
sealed class Node {
/**
* Wasm module definition.
*
* The unit of WebAssembly code is the module. A module collects definitions
* for types, functions, tables, memories, and globals. In addition, it can
* declare imports and exports and provide initialization logic in the form
* of data and element segments or a start function.
*/
data class Module(
val types: List<Type.Func> = emptyList(),
val imports: List<Import> = emptyList(),
@ -172,12 +184,15 @@ sealed class Node {
interface Const<out T : Number> : Args { val value: T }
}
// Control flow
// Control instructions [https://www.w3.org/TR/wasm-core-1/#control-instructions]
object Unreachable : Instr(), Args.None
object Nop : Instr(), Args.None
data class Block(override val type: Type.Value?) : Instr(), Args.Type
data class Loop(override val type: Type.Value?) : Instr(), Args.Type
data class If(override val type: Type.Value?) : Instr(), Args.Type
object Else : Instr(), Args.None
object End : Instr(), Args.None
data class Br(override val relativeDepth: Int) : Instr(), Args.RelativeDepth
@ -188,25 +203,27 @@ sealed class Node {
) : Instr(), Args.Table
object Return : Instr()
// Call operators
data class Call(override val index: Int) : Instr(), Args.Index
data class CallIndirect(
override val index: Int,
override val reserved: Boolean
) : Instr(), Args.ReservedIndex
// Parametric operators
// Parametric instructions [https://www.w3.org/TR/wasm-core-1/#parametric-instructions]
object Drop : Instr(), Args.None
object Select : Instr(), Args.None
// Variable access
// Variable instructions [https://www.w3.org/TR/wasm-core-1/#variable-instructions]
data class GetLocal(override val index: Int) : Instr(), Args.Index
data class SetLocal(override val index: Int) : Instr(), Args.Index
data class TeeLocal(override val index: Int) : Instr(), Args.Index
data class GetGlobal(override val index: Int) : Instr(), Args.Index
data class SetGlobal(override val index: Int) : Instr(), Args.Index
// Memory operators
// Memory instructions [https://www.w3.org/TR/wasm-core-1/#memory-instructions]
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
@ -233,7 +250,9 @@ sealed class Node {
data class MemorySize(override val reserved: Boolean) : Instr(), Args.Reserved
data class MemoryGrow(override val reserved: Boolean) : Instr(), Args.Reserved
// Constants
// Numeric instructions [https://www.w3.org/TR/wasm-core-1/#numeric-instructions]
// Constants operators
data class I32Const(override val value: Int) : Instr(), Args.Const<Int>
data class I64Const(override val value: Long) : Instr(), Args.Const<Long>
data class F32Const(override val value: Float) : Instr(), Args.Const<Float>

View File

@ -2,10 +2,16 @@ package asmble.ast
import asmble.io.SExprToStr
/**
* Ast representation of wasm S-expressions (wast format).
* see [[https://webassembly.github.io/spec/core/text/index.html]]
*/
sealed class 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,
@ -15,4 +21,5 @@ sealed class SExpr {
// This is basically the same as the deprecated java.lang.String#getBytes
fun rawContentCharsToBytes() = contents.toCharArray().map(Char::toByte)
}
}

View File

@ -1,6 +1,10 @@
package asmble.ast
/**
* Ast representation of wasm script.
*/
data class Script(val commands: List<Cmd>) {
sealed class Cmd {
data class Module(val module: Node.Module, val name: String?): Cmd()
data class Register(val string: String, val name: String?): Cmd()

View File

@ -3,6 +3,9 @@ package asmble.cli
import asmble.compile.jvm.javaIdent
import asmble.run.jvm.Module
/**
* This class provide ''invoke'' WASM code functionality.
*/
open class Invoke : ScriptCommand<Invoke.Args>() {
override val name = "invoke"
@ -34,6 +37,7 @@ open class Invoke : ScriptCommand<Invoke.Args>() {
).also { bld.done() }
override fun run(args: Args) {
// Compiles wasm to bytecode, do registrations and so on.
val ctx = prepareContext(args.scriptArgs)
// Instantiate the module
val module =
@ -41,11 +45,11 @@ open class Invoke : ScriptCommand<Invoke.Args>() {
else ctx.registrations[args.module] as? Module.Instance ?:
error("Unable to find module registered as ${args.module}")
// Just make sure the module is instantiated here...
module.instance(ctx)
val instance = module.instance(ctx)
// If an export is provided, call it
if (args.export != "<start-func>") args.export.javaIdent.let { javaName ->
val method = module.cls.declaredMethods.find { it.name == javaName } ?:
error("Unable to find export '${args.export}'")
// Finds java method(wasm fn) in class(wasm module) by name(declared in <start-func>)
val method = module.cls.declaredMethods.find { it.name == javaName } ?: error("Unable to find export '${args.export}'")
// Map args to params
require(method.parameterTypes.size == args.args.size) {
"Given arg count of ${args.args.size} is invalid for $method"
@ -59,11 +63,20 @@ open class Invoke : ScriptCommand<Invoke.Args>() {
else -> error("Unrecognized type for param ${index + 1}: $paramType")
}
}
val result = method.invoke(module.instance(ctx), *params.toTypedArray())
val result = method.invoke(instance, *params.toTypedArray())
if (args.resultToStdout && method.returnType != Void.TYPE) println(result)
}
}
/**
* Arguments for 'invoke' command.
*
* @param scriptArgs Common arguments for 'invoke' and 'run' ScriptCommands.
* @param module The module name to run. If it's a JVM class, it must have a no-arg constructor
* @param export The specific export function to invoke
* @param args Parameter for the export if export is present
* @param resultToStdout If true result will print to stout
*/
data class Args(
val scriptArgs: ScriptCommand.ScriptArgs,
val module: String,

View File

@ -5,6 +5,9 @@ import kotlin.system.exitProcess
val commands = listOf(Compile, Help, Invoke, Link, Run, SplitFunc, Translate)
/**
* Entry point of command line interface.
*/
fun main(args: Array<String>) {
if (args.isEmpty()) return println(
"""
@ -28,6 +31,7 @@ fun main(args: Array<String>) {
val globals = Main.globalArgs(argBuild)
logger = Logger.Print(globals.logLevel)
command.logger = logger
logger.info { "Running the command=${command.name} with args=${argBuild.args}" }
command.runWithArgs(argBuild)
} catch (e: Exception) {
logger.error { "Error ${command?.let { "in command '${it.name}'" } ?: ""}: ${e.message}" }

View File

@ -45,16 +45,18 @@ abstract class ScriptCommand<T> : Command<T>() {
)
fun prepareContext(args: ScriptArgs): ScriptContext {
var ctx = ScriptContext(
var context = ScriptContext(
packageName = "asmble.temp" + UUID.randomUUID().toString().replace("-", ""),
defaultMaxMemPages = args.defaultMaxMemPages
)
// Compile everything
ctx = args.inFiles.foldIndexed(ctx) { index, ctx, inFile ->
context = args.inFiles.foldIndexed(context) { index, ctx, inFile ->
try {
when (inFile.substringAfterLast('.')) {
// if input file is class file
"class" -> ctx.classLoader.addClass(File(inFile).readBytes()).let { ctx }
else -> Translate.inToAst(inFile, inFile.substringAfterLast('.')).let { inAst ->
// if input file is wasm file
else -> Translate.also { it.logger = logger }.inToAst(inFile, inFile.substringAfterLast('.')).let { inAst ->
val (mod, name) = (inAst.commands.singleOrNull() as? Script.Cmd.Module) ?:
error("Input file must only contain a single module")
val className = name?.javaIdent?.capitalize() ?:
@ -67,17 +69,28 @@ abstract class ScriptCommand<T> : Command<T>() {
}
}
}
} catch (e: Exception) { throw Exception("Failed loading $inFile - ${e.message}", e) }
} catch (e: Exception) {
throw Exception("Failed loading $inFile - ${e.message}", e)
}
}
// Do registrations
ctx = args.registrations.fold(ctx) { ctx, (moduleName, className) ->
context = args.registrations.fold(context) { ctx, (moduleName, className) ->
ctx.withModuleRegistered(moduleName,
Module.Native(Class.forName(className, true, ctx.classLoader).newInstance()))
}
if (args.specTestRegister) ctx = ctx.withHarnessRegistered()
return ctx
if (args.specTestRegister) context = context.withHarnessRegistered() // проверить что не так с "Cannot find compatible import for spectest::print"
return context
}
/**
* Common arguments for 'invoke' and 'run' ScriptCommands.
*
* @param inFiles Files to add to classpath. Can be wasm, wast, or class file
* @param registrations Register class name to a module name
* @param disableAutoRegister If set, this will not auto-register modules with names
* @param specTestRegister If true, registers the spec test harness as 'spectest'
* @param defaultMaxMemPages The maximum number of memory pages when a module doesn't say
*/
data class ScriptArgs(
val inFiles: List<String>,
val registrations: List<Pair<String, String>>,

View File

@ -58,7 +58,10 @@ open class Translate : Command<Translate.Args>() {
fun inToAst(inFile: String, inFormat: String): Script {
val inBytes =
if (inFile == "--") System.`in`.use { it.readBytes() }
else File(inFile).let { f -> FileInputStream(f).use { it.readBytes(f.length().toIntExact()) } }
else File(inFile).let { f ->
// Input file might not fit into the memory
FileInputStream(f).use { it.readBytes(f.length().toIntExact()) }
}
return when (inFormat) {
"wast" -> StrToSExpr.parse(inBytes.toString(Charsets.UTF_8)).let { res ->
when (res) {

View File

@ -30,10 +30,11 @@ open class AstToAsm {
}
fun addFields(ctx: ClsContext) {
// Mem field if present
// Mem field if present, adds `private final field memory` to
if (ctx.hasMemory)
ctx.cls.fields.add(FieldNode(Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL, "memory",
ctx.mem.memType.asmDesc, null, null))
ctx.cls.fields.add(
FieldNode((Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL), "memory", ctx.mem.memType.asmDesc, null, null)
)
// Table field if present...
// Private final for now, but likely won't be final in future versions supporting
// mutable tables, may be not even a table but a list (and final)

View File

@ -4,6 +4,24 @@ import asmble.ast.Node
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.*
/**
* Jvm representation of a function.
*
* @param name Name of the fn.
* @param params List of parameters of the fn.
* @param ret Type of the fn returner value.
* @param access The value of the access_flags item is a mask of flags used to
* denote access permissions to and properties of this class or
* interface [https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1-200-E.1].
* @param insns List of nodes that represents a bytecode instruction.
* @param stack A stack of operand types. Mirror of the operand stack(jvm stack)
* where types of operands instead operands.
* @param blockStack List of blocks of code
* @param ifStack Contains index of [org.objectweb.asm.tree.JumpInsnNode] that
* has a null label initially
* @param lastStackIsMemLeftover If there is the memory on the stack and we need it
* in the future, we mark it as leftover and reuse
*/
data class Func(
val name: String,
val params: List<TypeRef> = emptyList(),
@ -12,7 +30,6 @@ data class Func(
val insns: List<AbstractInsnNode> = emptyList(),
val stack: List<TypeRef> = emptyList(),
val blockStack: List<Block> = emptyList(),
// Contains index of JumpInsnNode that has a null label initially
val ifStack: List<Int> = emptyList(),
val lastStackIsMemLeftover: Boolean = false
) {
@ -110,10 +127,11 @@ data class Func(
}
}
fun pushBlock(insn: Node.Instr, labelType: Node.Type.Value?, endType: Node.Type.Value?) =
/** Creates new block with specified instruction and pushes it into the blockStack.*/
fun pushBlock(insn: Node.Instr, labelType: Node.Type.Value?, endType: Node.Type.Value?): Func =
pushBlock(insn, listOfNotNull(labelType?.typeRef), listOfNotNull(endType?.typeRef))
fun pushBlock(insn: Node.Instr, labelTypes: List<TypeRef>, endTypes: List<TypeRef>) =
fun pushBlock(insn: Node.Instr, labelTypes: List<TypeRef>, endTypes: List<TypeRef>): Func =
copy(blockStack = blockStack + Block(insn, insns.size, stack, labelTypes, endTypes))
fun popBlock() = copy(blockStack = blockStack.dropLast(1)) to blockStack.last()
@ -127,6 +145,22 @@ data class Func(
fun popIf() = copy(ifStack = ifStack.dropLast(1)) to peekIf()
/**
* Representation of code block.
*
* Blocks are composed of matched pairs of `block ... end` instructions, loops
* with matched pairs of `loop ... end` instructions, and ifs with either
* `if ... end` or if ... else ... end sequences. For each of these constructs
* the instructions in the ellipsis are said to be enclosed in the construct.
*
* @param isns Start instruction of this block, might be a 'Block', 'Loop'
* or 'If'
* @param startIndex Index of start instruction of this block in list of all
* instructions
* @param origStack Current block stack of operand types.
* @param labelTypes A type of label for this block
* @param endTypes A type of block return value
*/
class Block(
val insn: Node.Instr,
val startIndex: Int,

View File

@ -14,23 +14,31 @@ import java.lang.invoke.MethodHandle
// TODO: modularize
open class FuncBuilder {
fun fromFunc(ctx: ClsContext, f: Node.Func, index: Int): Func {
/**
* Converts wasm AST [asmble.ast.Node.Func] to Jvm bytecode representation [asmble.compile.jvm.Func].
*
* @param ctx A Global context for converting.
* @param fn AST of wasm fn.
* @param index Fn index, used for generating fn name
*/
fun fromFunc(ctx: ClsContext, fn: Node.Func, index: Int): Func {
ctx.debug { "Building function ${ctx.funcName(index)}" }
ctx.trace { "Function ast:\n${SExprToStr.fromSExpr(AstToSExpr.fromFunc(f))}" }
ctx.trace { "Function ast:\n${SExprToStr.fromSExpr(AstToSExpr.fromFunc(fn))}" }
var func = Func(
access = Opcodes.ACC_PRIVATE,
name = ctx.funcName(index),
params = f.type.params.map(Node.Type.Value::typeRef),
ret = f.type.ret?.let(Node.Type.Value::typeRef) ?: Void::class.ref
params = fn.type.params.map(Node.Type.Value::typeRef),
ret = fn.type.ret?.let(Node.Type.Value::typeRef) ?: Void::class.ref
)
// Rework the instructions
val reworkedInsns = ctx.reworker.rework(ctx, f)
val reworkedInsns = ctx.reworker.rework(ctx, fn)
// Start the implicit block
func = func.pushBlock(Node.Instr.Block(f.type.ret), f.type.ret, f.type.ret)
func = func.pushBlock(Node.Instr.Block(fn.type.ret), fn.type.ret, fn.type.ret)
// Create the context
val funcCtx = FuncContext(
cls = ctx,
node = f,
node = fn,
insns = reworkedInsns,
memIsLocalVar =
ctx.reworker.nonAdjacentMemAccesses(reworkedInsns) >= ctx.nonAdjacentMemAccessesRequiringLocalVar
@ -46,9 +54,9 @@ open class FuncBuilder {
// Add all instructions
ctx.debug { "Applying insns for function ${ctx.funcName(index)}" }
// All functions have an implicit block
func = funcCtx.insns.foldIndexed(func) { index, func, insn ->
func = funcCtx.insns.foldIndexed(func) { idx, f, insn ->
ctx.debug { "Applying insn $insn" }
val ret = applyInsn(funcCtx, func, insn, index)
val ret = applyInsn(funcCtx, f, insn, idx)
ctx.trace { "Resulting stack: ${ret.stack}"}
ret
}
@ -56,11 +64,11 @@ open class FuncBuilder {
// End the implicit block
val implicitBlock = func.currentBlock
func = applyEnd(funcCtx, func)
f.type.ret?.typeRef?.also { func = func.popExpecting(it, implicitBlock) }
fn.type.ret?.typeRef?.also { func = func.popExpecting(it, implicitBlock) }
// If the last instruction does not terminate, add the expected return
if (func.insns.isEmpty() || !func.insns.last().isTerminating) {
func = func.addInsns(InsnNode(when (f.type.ret) {
func = func.addInsns(InsnNode(when (fn.type.ret) {
null -> Opcodes.RETURN
Node.Type.Value.I32 -> Opcodes.IRETURN
Node.Type.Value.I64 -> Opcodes.LRETURN
@ -72,8 +80,10 @@ open class FuncBuilder {
}
fun applyInsn(ctx: FuncContext, fn: Func, i: Insn, index: Int) = when (i) {
is Insn.Node ->
applyNodeInsn(ctx, fn, i.insn, index)
is Insn.ImportFuncRefNeededOnStack ->
// Func refs are method handle fields
fn.addInsns(
@ -81,6 +91,7 @@ open class FuncBuilder {
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName,
ctx.cls.funcName(i.index), MethodHandle::class.ref.asmDesc)
).push(MethodHandle::class.ref)
is Insn.ImportGlobalSetRefNeededOnStack ->
// Import setters are method handle fields
fn.addInsns(
@ -88,13 +99,17 @@ open class FuncBuilder {
FieldInsnNode(Opcodes.GETFIELD, ctx.cls.thisRef.asmName,
ctx.cls.importGlobalSetterFieldName(i.index), MethodHandle::class.ref.asmDesc)
).push(MethodHandle::class.ref)
is Insn.ThisNeededOnStack ->
// load a reference onto the stack from a local variable
fn.addInsns(VarInsnNode(Opcodes.ALOAD, 0)).push(ctx.cls.thisRef)
is Insn.MemNeededOnStack ->
putMemoryOnStack(ctx, fn)
}
fun applyNodeInsn(ctx: FuncContext, fn: Func, i: Node.Instr, index: Int) = when (i) {
is Node.Instr.Unreachable ->
fn.addInsns(UnsupportedOperationException::class.athrow("Unreachable")).markUnreachable()
is Node.Instr.Nop ->
@ -127,18 +142,16 @@ open class FuncBuilder {
fn.pop().let { (fn, popped) ->
fn.addInsns(InsnNode(if (popped.stackSize == 2) Opcodes.POP2 else Opcodes.POP))
}
is Node.Instr.Select ->
applySelectInsn(ctx, fn)
is Node.Instr.GetLocal ->
applyGetLocal(ctx, fn, i.index)
is Node.Instr.SetLocal ->
applySetLocal(ctx, fn, i.index)
is Node.Instr.TeeLocal ->
applyTeeLocal(ctx, fn, i.index)
is Node.Instr.GetGlobal ->
applyGetGlobal(ctx, fn, i.index)
is Node.Instr.SetGlobal ->
applySetGlobal(ctx, fn, i.index)
is Node.Instr.Select -> applySelectInsn(ctx, fn)
// Variable access
is Node.Instr.GetLocal -> applyGetLocal(ctx, fn, i.index)
is Node.Instr.SetLocal -> applySetLocal(ctx, fn, i.index)
is Node.Instr.TeeLocal -> applyTeeLocal(ctx, fn, i.index)
is Node.Instr.GetGlobal -> applyGetGlobal(ctx, fn, i.index)
is Node.Instr.SetGlobal -> applySetGlobal(ctx, fn, i.index)
// Memory operators
is Node.Instr.I32Load, is Node.Instr.I64Load, is Node.Instr.F32Load, is Node.Instr.F64Load,
is Node.Instr.I32Load8S, is Node.Instr.I32Load8U, is Node.Instr.I32Load16U, is Node.Instr.I32Load16S,
is Node.Instr.I64Load8S, is Node.Instr.I64Load8U, is Node.Instr.I64Load16U, is Node.Instr.I64Load16S,
@ -461,18 +474,18 @@ open class FuncBuilder {
fun popUntilStackSize(
ctx: FuncContext,
fn: Func,
func: Func,
block: Func.Block,
untilStackSize: Int,
keepLast: Boolean
): Func {
ctx.debug { "For block ${block.insn}, popping until stack size $untilStackSize, keeping last? $keepLast" }
// Just get the latest, don't actually pop...
val type = if (keepLast) fn.pop().second else null
return (0 until Math.max(0, fn.stack.size - untilStackSize)).fold(fn) { fn, _ ->
val type = if (keepLast) func.pop().second else null
return (0 until Math.max(0, func.stack.size - untilStackSize)).fold(func) { fn, _ ->
// Essentially swap and pop if they want to keep the latest
(if (type != null && fn.stack.size > 1) fn.stackSwap(block) else fn).let { fn ->
fn.pop(block).let { (fn, poppedType) ->
(if (type != null && fn.stack.size > 1) fn.stackSwap(block) else fn).let { f ->
f.pop(block).let { (fn, poppedType) ->
fn.addInsns(InsnNode(if (poppedType.stackSize == 2) Opcodes.POP2 else Opcodes.POP))
}
}
@ -1076,10 +1089,12 @@ open class FuncBuilder {
putMemoryOnStack(ctx, fn).let { fn -> ctx.cls.mem.currentMemory(ctx, fn) }
}
/**
* Store is a special case where the memory ref is already pre-injected on
* the stack before this call. But it can have a memory leftover on the stack
* so we pop it if we need to
*/
fun applyStoreOp(ctx: FuncContext, fn: Func, insn: Node.Instr.Args.AlignOffset, insnIndex: Int) =
// Store is a special case where the memory ref is already pre-injected on
// the stack before this call. But it can have a memory leftover on the stack
// so we pop it if we need to
ctx.cls.assertHasMemory().let {
ctx.cls.mem.storeOp(ctx, fn, insn).let { fn ->
// As a special case, if this leaves the mem on the stack
@ -1268,11 +1283,11 @@ open class FuncBuilder {
}
}
fun applyReturnInsn(ctx: FuncContext, fn: Func): Func {
// If the current stakc is unreachable, we consider that our block since it
fun applyReturnInsn(ctx: FuncContext, func: Func): Func {
// If the current stack is unreachable, we consider that our block since it
// will pop properly.
val block = if (fn.currentBlock.unreachable) fn.currentBlock else fn.blockStack.first()
popForBlockEscape(ctx, fn, block).let { fn ->
val block = if (func.currentBlock.unreachable) func.currentBlock else func.blockStack.first()
popForBlockEscape(ctx, func, block).let { fn ->
return when (ctx.node.type.ret) {
null ->
fn.addInsns(InsnNode(Opcodes.RETURN))
@ -1284,9 +1299,9 @@ open class FuncBuilder {
fn.popExpecting(Float::class.ref, block).addInsns(InsnNode(Opcodes.FRETURN))
Node.Type.Value.F64 ->
fn.popExpecting(Double::class.ref, block).addInsns(InsnNode(Opcodes.DRETURN))
}.let { fn ->
if (fn.stack.isNotEmpty()) throw CompileErr.UnusedStackOnReturn(fn.stack)
fn.markUnreachable()
}.let { it ->
if (it.stack.isNotEmpty()) throw CompileErr.UnusedStackOnReturn(it.stack)
it.markUnreachable()
}
}
}

View File

@ -3,6 +3,15 @@ package asmble.compile.jvm
import asmble.ast.Node
import asmble.util.Logger
/**
* Jvm context of the function execution.
*
* @param cls class execution context
* @param node Ast of this function
* @param insns instructions list
* @param memIsLocalVar true if function uses only local variables and doesn't load
* or store to/from memory.
*/
data class FuncContext(
val cls: ClsContext,
val node: Node.Func,

View File

@ -2,6 +2,9 @@ package asmble.compile.jvm
import asmble.ast.Node
/**
* Does some special manipulations with instruction.
*/
open class InsnReworker {
fun rework(ctx: ClsContext, func: Node.Func): List<Insn> {
@ -29,6 +32,7 @@ open class InsnReworker {
// Note, while walking backwards up the insns to find set/tee, we do skip entire
// blocks/loops/if+else combined with "end"
var neededEagerLocalIndices = emptySet<Int>()
fun addEagerSetIfNeeded(getInsnIndex: Int, localIndex: Int) {
// Within the param range? nothing needed
if (localIndex < func.type.params.size) return
@ -71,6 +75,7 @@ open class InsnReworker {
(insn is Insn.Node && insn.insn !is Node.Instr.SetLocal && insn.insn !is Node.Instr.TeeLocal)
if (needsEagerInit) neededEagerLocalIndices += localIndex
}
insns.forEachIndexed { index, insn ->
if (insn is Insn.Node && insn.insn is Node.Instr.GetLocal) addEagerSetIfNeeded(index, insn.insn.index)
}
@ -86,11 +91,18 @@ open class InsnReworker {
} + insns
}
/**
* Puts into instruction list needed instructions for pushing local variables
* into the stack and returns list of resulting instructions.
*
* @param ctx The Execution context
* @param insns The original instructions
*/
fun injectNeededStackVars(ctx: ClsContext, insns: List<Node.Instr>): List<Insn> {
ctx.trace { "Calculating places to inject needed stack variables" }
// How we do this:
// We run over each insn, and keep a running list of stack
// manips. If there is an insn that needs something so far back,
// manips. If there is an ins'n that needs something so far back,
// we calc where it needs to be added and keep a running list of
// insn inserts. Then at the end we settle up.
//
@ -109,6 +121,14 @@ open class InsnReworker {
// guarantee the value will be in the right order if there are
// multiple for the same index
var insnsToInject = emptyMap<Int, List<Insn>>()
/**
* This function inject current instruction in stack.
*
* @param insn The instruction to inject
* @param count Number of step back on the stack that should we do for
* finding injection index.
*/
fun injectBeforeLastStackCount(insn: Insn, count: Int) {
ctx.trace { "Injecting $insn back $count stack values" }
fun inject(index: Int) {
@ -148,9 +168,11 @@ open class InsnReworker {
}
countSoFar += amountChanged
if (!foundUnconditionalJump) foundUnconditionalJump = insns[insnIndex].let { insn ->
insn is Node.Instr.Br || insn is Node.Instr.BrTable ||
insn is Node.Instr.Unreachable || insn is Node.Instr.Return
if (!foundUnconditionalJump) {
foundUnconditionalJump = insns[insnIndex].let { insn ->
insn is Node.Instr.Br || insn is Node.Instr.BrTable ||
insn is Node.Instr.Unreachable || insn is Node.Instr.Return
}
}
if (countSoFar == count) {
ctx.trace { "Found injection point as before insn #$insnIndex" }
@ -213,13 +235,19 @@ open class InsnReworker {
}
// Build resulting list
return insns.foldIndexed(emptyList<Insn>()) { index, ret, insn ->
return insns.foldIndexed(emptyList()) { index, ret, insn ->
val injections = insnsToInject[index] ?: emptyList()
ret + injections + Insn.Node(insn)
}
}
fun insnStackDiff(ctx: ClsContext, insn: Node.Instr) = when (insn) {
/**
* Calculate stack difference after calling instruction current instruction.
* Returns the difference from stack cursor position before instruction and after.
* `N = PUSH_OPS - POP_OPS.` '-n' mean that POP operation will be more than PUSH.
* If '0' then stack won't changed.
*/
fun insnStackDiff(ctx: ClsContext, insn: Node.Instr): Int = when (insn) {
is Node.Instr.Unreachable, is Node.Instr.Nop, is Node.Instr.Block,
is Node.Instr.Loop, is Node.Instr.If, is Node.Instr.Else,
is Node.Instr.End, is Node.Instr.Br, is Node.Instr.BrIf,
@ -292,7 +320,8 @@ open class InsnReworker {
is Node.Instr.F64ReinterpretI64 -> POP_PARAM + PUSH_RESULT
}
fun nonAdjacentMemAccesses(insns: List<Insn>) = insns.fold(0 to false) { (count, lastCouldHaveMem), insn ->
/** Returns number of memory accesses. */
fun nonAdjacentMemAccesses(insns: List<Insn>): Int = insns.fold(0 to false) { (count, lastCouldHaveMem), insn ->
val inc =
if (lastCouldHaveMem) 0
else if (insn == Insn.MemNeededOnStack) 1

View File

@ -2,14 +2,25 @@ package asmble.compile.jvm
import org.objectweb.asm.Type
/**
* A Java field or method type. This class can be used to make it easier to
* manipulate type and method descriptors.
*
* @param asm Wrapped [org.objectweb.asm.Type] from asm library
*/
data class TypeRef(val asm: Type) {
/** The internal name of the class corresponding to this object or array type. */
val asmName: String get() = asm.internalName
/** The descriptor corresponding to this Java type. */
val asmDesc: String get() = asm.descriptor
fun asMethodRetDesc(vararg args: TypeRef) = Type.getMethodDescriptor(asm, *args.map { it.asm }.toTypedArray())
/** Size of this type in stack, either 1 or 2 only allowed, where 1 = 2^32` bits */
val stackSize: Int get() = if (asm == Type.DOUBLE_TYPE || asm == Type.LONG_TYPE) 2 else 1
fun asMethodRetDesc(vararg args: TypeRef) = Type.getMethodDescriptor(asm, *args.map { it.asm }.toTypedArray())
fun equivalentTo(other: TypeRef) = this == other || this == Unknown || other == Unknown
object UnknownType

View File

@ -186,12 +186,14 @@ open class BinaryToAst(
require(sectionId > maxSectionId) { "Section ID $sectionId came after $maxSectionId" }.
also { maxSectionId = sectionId }
val sectionLen = b.readVarUInt32AsInt()
// each 'read' invocation creates new InputStream and don't closes it
sections += sectionId to b.read(sectionLen)
}
// Now build the module
fun <T> readSectionList(sectionId: Int, fn: (ByteReader) -> T) =
sections.find { it.first == sectionId }?.second?.readList(fn) ?: emptyList()
val types = readSectionList(1, this::toFuncType)
val funcIndices = readSectionList(3) { it.readVarUInt32AsInt() }
var nameSection: Node.NameSection? = null

View File

@ -100,6 +100,7 @@ abstract class ByteReader {
return result
}
// todo looks like this InputStream isn't ever closed
class InputStream(val ins: java.io.InputStream) : ByteReader() {
private var nextByte: Byte? = null
private var sawEof = false

View File

@ -19,6 +19,20 @@ import java.lang.invoke.MethodType
import java.lang.reflect.InvocationTargetException
import java.util.*
/**
* Script context. Contains all the information needed to execute this script.
*
* @param packageName Package name for this script
* @param modules List of all modules to be load for this script
* @param registrations Registered modules, key - module name, value - module instance
* @param logger Logger for this script
* @param adjustContext Fn for tuning context (looks redundant)
* @param classLoader ClassLoader for loading all classes for this script
* @param exceptionTranslator Converts exceptions to error messages
* @param defaultMaxMemPages The maximum number of memory pages when a module doesn't say
* @param includeBinaryInCompiledClass Store binary wasm code to compiled class
* file as annotation [asmble.annotation.WasmModule]
*/
data class ScriptContext(
val packageName: String,
val modules: List<Module.Compiled> = emptyList(),

View File

@ -9,9 +9,3 @@ Compile Rust to WASM and then to the JVM. In order of complexity:
* [rust-simple](rust-simple)
* [rust-string](rust-string)
* [rust-regex](rust-regex)
### C/C++
Compile C to WASM and then to the JVM. In order of complexity:
* [c-simple](c-simple)

View File

@ -1,14 +0,0 @@
### Example: C Simple
This shows a simple example of compiling C to WASM and then to the JVM. This is the C version of
[rust-simple](../rust-simple).
In order to run the C or C++ examples, the latest LLVM binaries must be on the `PATH`, built with the experimental
WebAssembly target. This can be built by passing `-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=WebAssembly` to `cmake` when
building WebAssembly. Or it can be downloaded from a nightly build site
([this one](http://gsdview.appspot.com/wasm-llvm/builds/) was used for these examples at the time of writing).
Everything else is basically the same as [rust-simple](../rust-simple) except with C code and using `clang`. To run
execute the following from the root `asmble` dir:
gradlew --no-daemon :examples:c-simple:run

View File

@ -1,3 +0,0 @@
int addOne(int x) {
return x + 1;
}

View File

@ -1,13 +0,0 @@
package asmble.examples.csimple;
import java.lang.invoke.MethodHandle;
import asmble.generated.CSimple;
class Main {
public static void main(String[] args) {
// Doesn't need memory or method table
CSimple simple = new CSimple(0, new MethodHandle[0]);
System.out.println("25 + 1 = " + simple.addOne(25));
}
}

View File

@ -2,8 +2,9 @@
extern crate regex;
use std::ptr::NonNull;
use regex::Regex;
use std::heap::{Alloc, Heap, Layout};
use std::alloc::{Alloc, Global, Layout};
use std::mem;
use std::str;
@ -37,17 +38,17 @@ pub extern "C" fn match_count(r: *mut Regex, str_ptr: *mut u8, len: usize) -> us
}
#[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut u8 {
pub extern "C" fn alloc(size: usize) -> NonNull<u8> {
unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.alloc(layout).unwrap()
Global.alloc(layout).unwrap()
}
}
#[no_mangle]
pub extern "C" fn dealloc(ptr: *mut u8, size: usize) {
pub extern "C" fn dealloc(ptr: NonNull<u8>, size: usize) {
unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.dealloc(ptr, layout);
Global.dealloc(ptr, layout);
}
}

View File

@ -3,6 +3,9 @@ package asmble.examples.rustregex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Implementation of {@link RegexLib} based on `java.util.regex`.
*/
public class JavaLib implements RegexLib<String> {
@Override
public JavaPattern compile(String str) {

View File

@ -5,6 +5,10 @@ import asmble.generated.RustRegex;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* Implementation of {@link RegexLib} based on `asmble.generated.RustRegex` that
* was composed from Rust code (see lib.rs).
*/
public class RustLib implements RegexLib<RustLib.Ptr> {
// 600 pages is enough for our use

View File

@ -1,6 +1,7 @@
#![feature(allocator_api)]
use std::heap::{Alloc, Heap, Layout};
use std::ptr::NonNull;
use std::alloc::{Alloc, Global, Layout};
use std::ffi::{CString};
use std::mem;
use std::os::raw::c_char;
@ -30,17 +31,17 @@ pub extern "C" fn prepend_from_rust(ptr: *mut u8, len: usize) -> *const c_char {
}
#[no_mangle]
pub extern "C" fn alloc(size: usize) -> *mut u8 {
pub extern "C" fn alloc(size: usize) -> NonNull<u8> {
unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.alloc(layout).unwrap()
Global.alloc(layout).unwrap()
}
}
#[no_mangle]
pub extern "C" fn dealloc(ptr: *mut u8, size: usize) {
pub extern "C" fn dealloc(ptr: NonNull<u8>, size: usize) {
unsafe {
let layout = Layout::from_size_align(size, mem::align_of::<u8>()).unwrap();
Heap.dealloc(ptr, layout);
Global.dealloc(ptr, layout);
}
}