All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalajs.linker.backend.webassembly.TextWriter.scala Maven / Gradle / Ivy

The newest version!
/*
 * Scala.js (https://www.scala-js.org/)
 *
 * Copyright EPFL.
 *
 * Licensed under Apache License 2.0
 * (https://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package org.scalajs.linker.backend.webassembly

import scala.collection.mutable

import org.scalajs.ir.OriginalName
import org.scalajs.ir.OriginalName.NoOriginalName

import Instructions._
import Identitities._
import Modules._
import Types._

private class TextWriter(module: Module) {
  import TextWriter._

  private val b = new WatBuilder()

  private val typeNames: Map[TypeID, String] = {
    val nameGen = new FreshNameGenerator
    module.types.flatMap(_.subTypes).map(st => st.id -> nameGen.genName(st.originalName)).toMap
  }

  private val dataNames: Map[DataID, String] = {
    val nameGen = new FreshNameGenerator
    module.datas.map(data => data.id -> nameGen.genName(data.originalName)).toMap
  }

  private val funcNames: Map[FunctionID, String] = {
    val nameGen = new FreshNameGenerator
    val importedFunctionNames = module.imports.collect {
      case Import(_, _, ImportDesc.Func(id, origName, _)) => id -> nameGen.genName(origName)
    }
    val definedFunctionNames = module.funcs.map(f => f.id -> nameGen.genName(f.originalName))
    (importedFunctionNames ::: definedFunctionNames).toMap
  }

  private val tagNames: Map[TagID, String] = {
    val nameGen = new FreshNameGenerator
    val importedTagNames = module.imports.collect {
      case Import(_, _, ImportDesc.Tag(id, origName, _)) =>
        id -> nameGen.genName(origName)
    }
    val definedTagNames = module.tags.map(t => t.id -> nameGen.genName(t.originalName))
    (importedTagNames ::: definedTagNames).toMap
  }

  private val globalNames: Map[GlobalID, String] = {
    val nameGen = new FreshNameGenerator
    val importedGlobalNames = module.imports.collect {
      case Import(_, _, ImportDesc.Global(id, origName, _, _)) => id -> nameGen.genName(origName)
    }
    val definedGlobalNames = module.globals.map(g => g.id -> nameGen.genName(g.originalName))
    (importedGlobalNames ::: definedGlobalNames).toMap
  }

  private val fieldNames: Map[TypeID, Map[FieldID, String]] = {
    (for {
      recType <- module.types
      SubType(typeID, _, _, _, StructType(fields)) <- recType.subTypes
    } yield {
      val nameGen = new FreshNameGenerator
      typeID -> fields.map(f => f.id -> nameGen.genName(f.originalName)).toMap
    }).toMap
  }

  private var localNames: Option[Map[LocalID, String]] = None
  private var labelNames: Option[mutable.Map[LabelID, String]] = None
  private var labelNameGen: Option[FreshNameGenerator] = None

  def write(): String = {
    b.topLevel("module") {
      module.types.foreach(writeRecType)
      module.imports.foreach(writeImport)
      module.funcs.foreach(writeFunction)
      module.tags.foreach(writeTag)
      module.globals.foreach(writeGlobal)
      module.exports.foreach(writeExport)
      module.start.foreach(writeStart)
      module.elems.foreach(writeElement)
      module.datas.foreach(writeData)
    }

    b.toString()
  }

  private def appendName(typeID: TypeID): Unit =
    b.appendElement(typeNames(typeID))

  private def appendName(dataID: DataID): Unit =
    b.appendElement(dataNames(dataID))

  private def appendName(functionID: FunctionID): Unit =
    b.appendElement(funcNames(functionID))

  private def appendName(tagID: TagID): Unit =
    b.appendElement(tagNames(tagID))

  private def appendName(globalID: GlobalID): Unit =
    b.appendElement(globalNames(globalID))

  private def appendName(typeID: TypeID, fieldID: FieldID): Unit =
    b.appendElement(fieldNames(typeID)(fieldID))

  private def appendName(localID: LocalID): Unit =
    b.appendElement(localNames.get(localID))

  private def appendName(labelID: LabelID): Unit =
    b.appendElement(labelNames.get(labelID))

  private def writeRecType(recType: RecType): Unit = {
    val RecType(subTypes) = recType

    subTypes match {
      case singleSubType :: Nil =>
        writeTypeDefinition(singleSubType)
      case subTypes =>
        b.newLineList("rec") {
          subTypes.foreach(writeTypeDefinition)
        }
    }
  }

  private def writeTypeDefinition(subType: SubType): Unit = {
    val SubType(id, _, isFinal, superType, compositeType) = subType

    b.newLineList("type") {
      appendName(id)
      subType match {
        case SubType(_, _, true, None, _) =>
          writeCompositeType(id, compositeType)
        case _ =>
          b.sameLineList("sub") {
            if (subType.isFinal)
              b.appendElement("final")
            superType.foreach(appendName(_))
            writeCompositeType(id, compositeType)
          }
      }
    }
  }

  private def writeCompositeType(typeID: TypeID, t: CompositeType): Unit = {
    def writeFieldType(fieldType: FieldType): Unit = {
      val FieldType(tpe, isMutable) = fieldType

      if (isMutable) {
        b.sameLineList("mut") {
          writeType(tpe)
        }
      } else {
        writeType(tpe)
      }
    }

    def writeField(field: StructField): Unit = {
      val StructField(id, _, fieldType) = field

      b.sameLineList("field") {
        appendName(typeID, id)
        writeFieldType(fieldType)
      }
    }

    t match {
      case FunctionType(params, results) =>
        b.sameLineList("func") {
          params.foreach { ty =>
            b.sameLineList("param")(writeType(ty))
          }
          results.foreach { ty =>
            b.sameLineList("result")(writeType(ty))
          }
        }

      case ArrayType(fieldType) =>
        b.sameLineList("array") {
          writeFieldType(fieldType)
        }

      case StructType(fields) =>
        b.sameLineList("struct") {
          fields.foreach(writeField)
        }
    }
  }

  private def writeImport(i: Import): Unit = {
    val Import(module, name, desc) = i

    b.newLineList("import") {
      b.appendStringElement(module)
      b.appendStringElement(name)

      desc match {
        case ImportDesc.Func(id, _, typeID) =>
          b.sameLineList("func") {
            appendName(id)
            writeTypeUse(typeID)
          }
        case ImportDesc.Global(id, _, isMutable, tpe) =>
          b.sameLineList("global") {
            appendName(id)
            if (isMutable)
              b.sameLineList("mut")(writeType(tpe))
            else
              writeType(tpe)
          }
        case ImportDesc.Tag(id, _, typeID) =>
          b.sameLineList("tag") {
            appendName(id)
            writeTypeUse(typeID)
          }
      }
    }
  }

  private def writeFunction(f: Function): Unit = {
    def writeParam(l: Local): Unit = {
      b.sameLineList("param") {
        appendName(l.id)
        writeType(l.tpe)
      }
    }

    def writeLocal(l: Local): Unit = {
      b.sameLineList("local") {
        appendName(l.id)
        writeType(l.tpe)
      }
    }

    val Function(id, _, typeID, params, results, locals, body, _) = f

    localNames = {
      val nameGen = new FreshNameGenerator
      Some((params ::: locals).map(l => l.id -> nameGen.genName(l.originalName)).toMap)
    }
    labelNames = Some(mutable.HashMap.empty)
    labelNameGen = Some(new FreshNameGenerator)

    b.newLineList("func") {
      appendName(id)
      writeTypeUse(typeID)

      b.newLine()
      params.foreach(writeParam)
      results.foreach(r => b.sameLineList("result")(writeType(r)))

      b.newLine()
      locals.foreach(writeLocal)
      writeExpr(body)
    }

    localNames = None
    labelNames = None
    labelNameGen = None
  }

  private def writeTag(tag: Tag): Unit = {
    val Tag(id, _, typeID) = tag

    b.newLineList("tag") {
      appendName(id)
      writeTypeUse(typeID)
    }
  }

  private def writeGlobal(g: Global): Unit = {
    val Global(id, _, isMutable, tpe, init) = g

    b.newLineList("global") {
      appendName(id)
      if (isMutable)
        b.sameLineList("mut")(writeType(tpe))
      else
        writeType(tpe)
      writeExpr(init)
    }
  }

  private def writeExport(e: Export): Unit = {
    val Export(name, desc) = e

    b.newLineList("export") {
      b.appendStringElement(name)
      desc match {
        case ExportDesc.Func(id) =>
          b.sameLineList("func") {
            appendName(id)
          }
        case ExportDesc.Global(id) =>
          b.sameLineList("global") {
            appendName(id)
          }
      }
    }
  }

  private def writeStart(startFunction: FunctionID): Unit = {
    b.newLineList("start") {
      appendName(startFunction)
    }
  }

  private def writeElement(element: Element): Unit = {
    val Element(tpe, init, mode) = element

    b.newLineList("elem") {
      mode match {
        case Element.Mode.Declarative => b.appendElement("declare")
      }
      writeType(tpe)
      init.foreach { item =>
        b.newLineList("item") {
          writeExpr(item)
        }
      }
    }
  }

  private def writeData(data: Data): Unit = {
    val Data(id, _, bytes, mode) = data

    b.newLineList("data") {
      appendName(id)
      mode match {
        case Data.Mode.Passive =>
          // do nothing
      }
      b.appendElement("\"" + bytes.map("\\%02x".format(_)).mkString + "\"")
    }
  }

  /** Writes a `typeuse`.
   *
   *  @see
   *    [[https://webassembly.github.io/gc/core/text/modules.html#type-uses]]
   */
  private def writeTypeUse(typeID: TypeID): Unit = {
    b.sameLineList("type")(appendName(typeID))
  }

  private def writeType(tpe: StorageType): Unit = {
    tpe match {
      case tpe: SimpleType => b.appendElement(tpe.textName)
      case tpe: PackedType => b.appendElement(tpe.textName)

      case RefType(true, heapType: HeapType.AbsHeapType) =>
        b.appendElement(heapType.nullableRefTextName)

      case RefType(nullable, heapType) =>
        b.sameLineList("ref") {
          if (nullable)
            b.appendElement("null")
          writeHeapType(heapType)
        }
    }
  }

  private def writeHeapType(heapType: HeapType): Unit = {
    heapType match {
      case HeapType.Type(typeID)          => appendName(typeID)
      case heapType: HeapType.AbsHeapType => b.appendElement(heapType.textName)
    }
  }

  private def writeFloatString(v: Double): Unit = {
    val stringRepr = {
      if (v.isNaN()) "nan"
      else if (v == Double.PositiveInfinity) "inf"
      else if (v == Double.NegativeInfinity) "-inf"
      else if (v.equals(-0.0)) "-0.0"
      else v.toString()
    }
    b.appendElement(stringRepr)
  }

  private def writeBlockType(blockType: BlockType): Unit = {
    blockType match {
      case BlockType.FunctionType(typeID) =>
        writeTypeUse(typeID)
      case BlockType.ValueType(None) =>
        // do nothing
      case BlockType.ValueType(Some(tpe)) =>
        b.sameLineList("result")(writeType(tpe))
    }
  }

  private def writeExpr(expr: Expr): Unit = {
    val Expr(instrs) = expr

    instrs.foreach(writeInstr(_))
  }

  private def writeInstr(instr: Instr): Unit = {
    instr match {
      case PositionMark(_) =>
        // ignore

      case _ =>
        instr match {
          case End | Else | _: Catch => b.deindent()
          case _                     => // do nothing
        }
        b.newLine()
        b.appendElement(instr.mnemonic)
        instr match {
          case instr: StructuredLabeledInstr =>
            for (label <- instr.label) {
              labelNames.get += label -> labelNameGen.get.genName(NoOriginalName)
              appendName(label)
            }
          case _ =>
            // do nothing
        }

        writeInstrImmediates(instr)

        instr match {
          case _: StructuredLabeledInstr | Else | _: Catch => b.indent()
          case _                                           => // do nothing
        }
    }
  }

  private def writeInstrImmediates(instr: Instr): Unit = {
    instr match {
      // Convenience categories

      case instr: SimpleInstr =>
        // do nothing
      case instr: BlockTypeLabeledInstr =>
        writeBlockType(instr.blockTypeArgument)
      case instr: LabelInstr =>
        appendName(instr.labelArgument)
      case instr: FuncInstr =>
        appendName(instr.funcArgument)
      case instr: TypeInstr =>
        appendName(instr.typeArgument)
      case instr: TagInstr =>
        appendName(instr.tagArgument)
      case instr: LocalInstr =>
        appendName(instr.localArgument)
      case instr: GlobalInstr =>
        appendName(instr.globalArgument)
      case instr: HeapTypeInstr =>
        writeHeapType(instr.heapTypeArgument)
      case instr: RefTypeInstr =>
        writeType(instr.refTypeArgument)
      case instr: StructFieldInstr =>
        appendName(instr.structTypeID)
        appendName(instr.structTypeID, instr.fieldID)

      // Specific instructions with unique-ish shapes

      case I32Const(v) => b.appendElement(v.toString())
      case I64Const(v) => b.appendElement(v.toString())
      case F32Const(v) => writeFloatString(v.toDouble)
      case F64Const(v) => writeFloatString(v)

      case Select(resultTypes) =>
        resultTypes.foreach(writeType(_))

      case BrTable(labelIdxVector, defaultLabelIdx) =>
        labelIdxVector.foreach(appendName(_))
        appendName(defaultLabelIdx)

      case TryTable(blockType, clauses, _) =>
        writeBlockType(blockType)
        for (clause <- clauses) {
          b.sameLineList(clause.mnemonic) {
            clause.tag.foreach(tag => appendName(tag))
            appendName(clause.label)
          }
        }

      case ArrayNewData(typeIdx, dataIdx) =>
        appendName(typeIdx)
        appendName(dataIdx)

      case ArrayNewFixed(typeIdx, length) =>
        appendName(typeIdx)
        b.appendElement(Integer.toUnsignedString(length))

      case ArrayCopy(destType, srcType) =>
        appendName(destType)
        appendName(srcType)

      case BrOnCast(labelIdx, from, to) =>
        appendName(labelIdx)
        writeType(from)
        writeType(to)
      case BrOnCastFail(labelIdx, from, to) =>
        appendName(labelIdx)
        writeType(from)
        writeType(to)

      case PositionMark(_) =>
        throw new AssertionError(s"Unexpected $instr")
    }
  }
}

object TextWriter {
  def write(module: Module): String =
    new TextWriter(module).write()

  private class FreshNameGenerator {
    private val generated = mutable.HashSet.empty[String]

    def genName(originalName: OriginalName): String = {
      val base =
        if (originalName.isDefined) "$" + sanitizeWatIdentifier(originalName.get.toString())
        else "$"
      if (originalName.isDefined && generated.add(base)) {
        base
      } else {
        var index = 1
        while (!generated.add(base + index))
          index += 1
        base + index
      }
    }
  }

  private class WatBuilder {
    private val builder = new StringBuilder
    private var level = 0
    private val indentStr = "  "

    private def indented(body: => Unit): Unit = {
      indent()
      body
      deindent()
    }

    def indent(): Unit = level += 1
    def deindent(): Unit = level -= 1

    def newLine(): Unit = {
      builder.append("\n")
      builder.append(indentStr * level)
    }

    def topLevel(name: String)(body: => Unit): Unit = {
      builder.append(s"($name")
      indented(body)
      builder.append(")")
      newLine()
    }

    def newLineList(name: String)(body: => Unit): Unit = {
      newLine()
      builder.append(s"($name")
      indented(body)
      builder.append(")")
    }

    def sameLineList(name: String)(body: => Unit): Unit = {
      builder.append(s" ($name")
      body
      builder.append(")")
    }

    def sameLineListOne(name: String)(value: String): Unit =
      sameLineList(name)(appendElement(value))

    def appendElement(value: String): Unit = {
      builder.append(" ")
      builder.append(value)
    }

    /** Appends a `string` element.
     *
     *  @see
     *    [[https://webassembly.github.io/gc/core/text/values.html#strings]]
     */
    def appendStringElement(str: String): Unit = {
      builder.append(" \"")
      val len = str.length()
      var i = 0
      while (i != len) {
        str.charAt(i) match {
          case '"'                        => builder.append("\\\"")
          case '\\'                       => builder.append("\\\\")
          case c if c < 0x20 || c == 0x7f => builder.append("\\%02x".format(c))
          case c                          => builder.append(c)
        }
        i += 1
      }
      builder.append("\"")
    }

    override def toString: String =
      builder.toString()
  }

  /** @see https://webassembly.github.io/spec/core/text/values.html#text-id */
  private def sanitizeWatIdentifier(name: String): String = {
    if (name.isEmpty) "_"
    else if (name.forall(isValidWatIdentifierChar)) name
    else name.map(c => if (isValidWatIdentifierChar(c)) c else '_').mkString
  }

  private def isValidWatIdentifierChar(c: Char): Boolean = {
    c.isDigit || c.isLetter ||
    "!#$%&'*+-./:<=>?@\\^_`|~".contains(c) ||
    "$.@_".contains(c)
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy