Skip to content

Commit 68c765f

Browse files
committed
Wasm: Store the contents of constant primitive arrays in data segments.
Paradoxically, this makes the produced .wasm a bit larger. That is because the elements are never LEB-encoded. However, it should speed up decoding, compiling and executing the Wasm module.
1 parent d6d6a37 commit 68c765f

File tree

6 files changed

+164
-18
lines changed

6 files changed

+164
-18
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Scala.js (https://www.scala-js.org/)
3+
*
4+
* Copyright EPFL.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (https://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package org.scalajs.linker.backend.wasmemitter
14+
15+
import java.nio.{ByteBuffer, ByteOrder}
16+
17+
import scala.collection.mutable
18+
19+
import org.scalajs.ir.OriginalName
20+
21+
import org.scalajs.linker.backend.wasmemitter.VarGen.genDataID
22+
23+
import org.scalajs.linker.backend.webassembly.Identitities._
24+
import org.scalajs.linker.backend.webassembly.Modules._
25+
26+
/** Pool of constant arrays that we store in data segments. */
27+
final class ConstantArrayPool {
28+
/* We use 4 data segments; one for each byte size: 1, 2, 4 and 8.
29+
* This way, every sub-segment containing the contents of an array is aligned
30+
* to the byte size of elements of that array.
31+
*/
32+
33+
// Indexed by log2ByteSize
34+
private val constantArrays = Array.fill(4)(mutable.ListBuffer.empty[Array[Byte]])
35+
private val currentSizes = new Array[Int](4)
36+
37+
def addArray8[T](elems: List[T])(putElem: (ByteBuffer, T) => Unit): (DataID, Int) =
38+
addArrayInternal(log2ByteSize = 0, elems)(putElem)
39+
40+
def addArray16[T](elems: List[T])(putElem: (ByteBuffer, T) => Unit): (DataID, Int) =
41+
addArrayInternal(log2ByteSize = 1, elems)(putElem)
42+
43+
def addArray32[T](elems: List[T])(putElem: (ByteBuffer, T) => Unit): (DataID, Int) =
44+
addArrayInternal(log2ByteSize = 2, elems)(putElem)
45+
46+
def addArray64[T](elems: List[T])(putElem: (ByteBuffer, T) => Unit): (DataID, Int) =
47+
addArrayInternal(log2ByteSize = 3, elems)(putElem)
48+
49+
private def addArrayInternal[T](log2ByteSize: Int, elems: List[T])(
50+
putElem: (ByteBuffer, T) => Unit): (DataID, Int) = {
51+
52+
val length = elems.size
53+
val size = length << log2ByteSize // length * byteSize
54+
val array = new Array[Byte](size)
55+
val offset = currentSizes(log2ByteSize)
56+
57+
val buffer = ByteBuffer.wrap(array).order(ByteOrder.LITTLE_ENDIAN)
58+
elems.foreach(putElem(buffer, _))
59+
60+
constantArrays(log2ByteSize) += array
61+
currentSizes(log2ByteSize) += size
62+
63+
(genDataID.constantArrays(log2ByteSize), offset)
64+
}
65+
66+
def genPool(): List[Data] = {
67+
for {
68+
log2ByteSize <- constantArrays.indices.toList
69+
if constantArrays(log2ByteSize).nonEmpty
70+
} yield {
71+
val bytes = new Array[Byte](currentSizes(log2ByteSize))
72+
var offset = 0
73+
for (array <- constantArrays(log2ByteSize)) {
74+
System.arraycopy(array, 0, bytes, offset, array.length)
75+
offset += array.length
76+
}
77+
Data(genDataID.constantArrays(log2ByteSize),
78+
OriginalName(s"constantArrays${1 << log2ByteSize}"),
79+
bytes, Data.Mode.Passive)
80+
}
81+
}
82+
}

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/Emitter.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ final class Emitter(config: Emitter.Config) {
100100
val wtf16Strings = ctx.stringPool.genPool()
101101
genDeclarativeElements()
102102

103+
// Likewise, gen the constant array pool at the end
104+
for (data <- ctx.constantArrayPool.genPool())
105+
ctx.moduleBuilder.addData(data)
106+
103107
val wasmModule = ctx.moduleBuilder.build()
104108

105109
val jsFileContentInfo = new JSFileContentInfo(

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/FunctionEmitter.scala

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3137,20 +3137,71 @@ private class FunctionEmitter private (
31373137
private def genArrayValue(tree: ArrayValue): Type = {
31383138
val ArrayValue(arrayTypeRef, elems) = tree
31393139

3140-
val expectedElemType = arrayTypeRef match {
3141-
case ArrayTypeRef(base: PrimRef, 1) => base.tpe
3142-
case _ => AnyType
3143-
}
3144-
3145-
// Mark the position for the header of `genArrayValue`
31463140
markPosition(tree)
31473141

3148-
SWasmGen.genArrayValue(fb, arrayTypeRef, elems.size) {
3149-
// Create the underlying array
3150-
elems.foreach(genTree(_, expectedElemType))
3142+
arrayTypeRef match {
3143+
case ArrayTypeRef(base: PrimRef, 1) if elems.forall(_.isInstanceOf[Literal]) =>
3144+
// Use a constant array in a data segment
3145+
val length = elems.size
31513146

3152-
// Re-mark the position for the footer of `genArrayValue`
3153-
markPosition(tree)
3147+
val (dataID, offset) = base.tpe match {
3148+
case BooleanType =>
3149+
ctx.constantArrayPool.addArray8(elems) { (buffer, elem) =>
3150+
buffer.put(if (elem.asInstanceOf[BooleanLiteral].value) 1.toByte else 0.toByte)
3151+
}
3152+
case CharType =>
3153+
ctx.constantArrayPool.addArray16(elems) { (buffer, elem) =>
3154+
buffer.putChar(elem.asInstanceOf[CharLiteral].value)
3155+
}
3156+
case ByteType =>
3157+
ctx.constantArrayPool.addArray8(elems) { (buffer, elem) =>
3158+
buffer.put(elem.asInstanceOf[ByteLiteral].value)
3159+
}
3160+
case ShortType =>
3161+
ctx.constantArrayPool.addArray16(elems) { (buffer, elem) =>
3162+
buffer.putShort(elem.asInstanceOf[ShortLiteral].value)
3163+
}
3164+
case IntType =>
3165+
ctx.constantArrayPool.addArray32(elems) { (buffer, elem) =>
3166+
buffer.putInt(elem.asInstanceOf[IntLiteral].value)
3167+
}
3168+
case LongType =>
3169+
ctx.constantArrayPool.addArray64(elems) { (buffer, elem) =>
3170+
buffer.putLong(elem.asInstanceOf[LongLiteral].value)
3171+
}
3172+
case FloatType =>
3173+
ctx.constantArrayPool.addArray32(elems) { (buffer, elem) =>
3174+
// Explicitly use floatToIntBits for determinism
3175+
buffer.putInt(java.lang.Float.floatToIntBits(elem.asInstanceOf[FloatLiteral].value))
3176+
}
3177+
case DoubleType =>
3178+
ctx.constantArrayPool.addArray64(elems) { (buffer, elem) =>
3179+
// Explicitly use doubleToLongBits for determinism
3180+
buffer.putLong(java.lang.Double.doubleToLongBits(elem.asInstanceOf[DoubleLiteral].value))
3181+
}
3182+
case NothingType | NullType | VoidType =>
3183+
throw new AssertionError(s"Invalid array type $arrayTypeRef at ${tree.pos}")
3184+
}
3185+
3186+
SWasmGen.genArrayValueFromUnderlying(fb, arrayTypeRef) {
3187+
fb += wa.I32Const(offset)
3188+
fb += wa.I32Const(length)
3189+
fb += wa.ArrayNewData(genTypeID.underlyingOf(arrayTypeRef), dataID)
3190+
}
3191+
3192+
case _ =>
3193+
val expectedElemType = arrayTypeRef match {
3194+
case ArrayTypeRef(base: PrimRef, 1) => base.tpe
3195+
case _ => AnyType
3196+
}
3197+
3198+
SWasmGen.genArrayValue(fb, arrayTypeRef, elems.size) {
3199+
// Create the underlying array
3200+
elems.foreach(genTree(_, expectedElemType))
3201+
3202+
// Re-mark the position for the footer of `genArrayValue`
3203+
markPosition(tree)
3204+
}
31543205
}
31553206

31563207
tree.tpe

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/SWasmGen.scala

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,17 @@ object SWasmGen {
6464

6565
def genArrayValue(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef, length: Int)(
6666
genElems: => Unit): Unit = {
67-
genLoadArrayTypeData(fb, arrayTypeRef) // vtable
68-
69-
// Create the underlying array
70-
genElems
71-
val underlyingArrayType = genTypeID.underlyingOf(arrayTypeRef)
72-
fb += ArrayNewFixed(underlyingArrayType, length)
67+
genArrayValueFromUnderlying(fb, arrayTypeRef) {
68+
// Create the underlying array
69+
genElems
70+
fb += ArrayNewFixed(genTypeID.underlyingOf(arrayTypeRef), length)
71+
}
72+
}
7373

74-
// Create the array object
74+
def genArrayValueFromUnderlying(fb: FunctionBuilder, arrayTypeRef: ArrayTypeRef)(
75+
genUnderlying: => Unit): Unit = {
76+
genLoadArrayTypeData(fb, arrayTypeRef) // vtable
77+
genUnderlying
7578
fb += StructNew(genTypeID.forArrayClass(arrayTypeRef))
7679
}
7780

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/VarGen.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,4 +440,9 @@ object VarGen {
440440
case object exception extends TagID
441441
}
442442

443+
object genDataID {
444+
/** Data segment for constant arrays whose elements take 2^log2ByteSize bytes. */
445+
final case class constantArrays(log2ByteSize: Int) extends DataID
446+
}
447+
443448
}

linker/shared/src/main/scala/org/scalajs/linker/backend/wasmemitter/WasmContext.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ final class WasmContext(
9797
new mutable.LinkedHashSet()
9898

9999
val stringPool: StringPool = new StringPool
100+
val constantArrayPool: ConstantArrayPool = new ConstantArrayPool
100101

101102
/** The main `rectype` containing the object model types. */
102103
val mainRecType: ModuleBuilder.RecTypeBuilder = new ModuleBuilder.RecTypeBuilder

0 commit comments

Comments
 (0)