forked from tanishiking/scala-wasm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWebAssemblyLinkerBackend.scala
206 lines (176 loc) · 6.72 KB
/
WebAssemblyLinkerBackend.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package wasm
import scala.concurrent.{ExecutionContext, Future}
import java.nio.ByteBuffer
import java.nio.charset.StandardCharsets
import org.scalajs.ir.Names._
import org.scalajs.logging.Logger
import org.scalajs.linker._
import org.scalajs.linker.interface._
import org.scalajs.linker.interface.unstable._
import org.scalajs.linker.standard._
import wasm.ir2wasm._
import wasm.ir2wasm.SpecialNames._
import wasm.wasm4s._
final class WebAssemblyLinkerBackend(
linkerConfig: StandardConfig,
val coreSpec: CoreSpec
) extends LinkerBackend {
require(
linkerConfig.moduleKind == ModuleKind.ESModule,
s"The WebAssembly backend only supports ES modules; was ${linkerConfig.moduleKind}."
)
require(
!linkerConfig.optimizer,
"The WebAssembly backend does not support the optimizer yet."
)
/* The symbol requirements of our back-end.
* The symbol requirements tell the LinkerFrontend that we need these
* symbols to always be reachable, even if no "user-land" IR requires them.
* They are roots for the reachability analysis, together with module
* initializers and top-level exports.
* If we don't do this, the linker frontend will dead-code eliminate our
* box classes.
*/
val symbolRequirements: SymbolRequirement = {
val factory = SymbolRequirement.factory("wasm")
factory.multiple(
factory.instantiateClass(ClassClass, ClassCtor),
factory.instantiateClass(CharBoxClass, CharBoxCtor),
factory.instantiateClass(LongBoxClass, LongBoxCtor),
// See genIdentityHashCode in HelperFunctions
factory.callMethodStatically(BoxedDoubleClass, hashCodeMethodName),
factory.callMethodStatically(BoxedStringClass, hashCodeMethodName)
)
}
// Our injected IR files are handled by WebAssemblyStandardLinkerImpl instead
def injectedIRFiles: Seq[IRFile] = Nil
def emit(moduleSet: ModuleSet, output: OutputDirectory, logger: Logger)(implicit
ec: ExecutionContext
): Future[Report] = {
val wasmModule = new WasmModule
val builder = new WasmBuilder(coreSpec)
implicit val context: WasmContext = new WasmContext(wasmModule)
val onlyModule = moduleSet.modules match {
case onlyModule :: Nil =>
onlyModule
case modules =>
throw new UnsupportedOperationException(
"The WebAssembly backend does not support multiple modules. Found: " +
modules.map(_.id.id).mkString(", ")
)
}
val moduleID = onlyModule.id.id
/* Sort by ancestor count so that superclasses always appear before
* subclasses, then tie-break by name for stability.
*/
val sortedClasses = onlyModule.classDefs.sortWith { (a, b) =>
val cmp = Integer.compare(a.ancestors.size, b.ancestors.size)
if (cmp != 0) cmp < 0
else a.className.compareTo(b.className) < 0
}
// sortedClasses.foreach(cls => println(utils.LinkedClassPrinters.showLinkedClass(cls)))
Preprocessor.preprocess(sortedClasses)(context)
HelperFunctions.genGlobalHelpers()
builder.genPrimitiveTypeDataGlobals()
sortedClasses.foreach { clazz =>
builder.transformClassDef(clazz)
}
// Array classes extend j.l.Object, so they must come after transformClassDef's
builder.genArrayClasses()
onlyModule.topLevelExports.foreach { tle =>
builder.transformTopLevelExport(tle)
}
val classesWithStaticInit =
sortedClasses.filter(_.hasStaticInitializer).map(_.className)
context.complete(
onlyModule.initializers.toList,
classesWithStaticInit,
onlyModule.topLevelExports
)
val outputImpl = OutputDirectoryImpl.fromOutputDirectory(output)
val watFileName = s"$moduleID.wat"
val wasmFileName = s"$moduleID.wasm"
val jsFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, moduleID)
val loaderJSFileName = OutputPatternsImpl.jsFile(linkerConfig.outputPatterns, "__loader")
val filesToProduce0 = Set(
wasmFileName,
loaderJSFileName,
jsFileName
)
val filesToProduce =
if (linkerConfig.prettyPrint) filesToProduce0 + watFileName
else filesToProduce0
def maybeWriteWatFile(): Future[Unit] = {
if (linkerConfig.prettyPrint) {
val textOutput = new converters.WasmTextWriter().write(wasmModule)
val textOutputBytes = textOutput.getBytes(StandardCharsets.UTF_8)
outputImpl.writeFull(watFileName, ByteBuffer.wrap(textOutputBytes))
} else {
Future.unit
}
}
def writeWasmFile(): Future[Unit] = {
val emitDebugInfo = !linkerConfig.minify
val binaryOutput = new converters.WasmBinaryWriter(wasmModule, emitDebugInfo).write()
outputImpl.writeFull(wasmFileName, ByteBuffer.wrap(binaryOutput))
}
def writeLoaderFile(): Future[Unit] =
outputImpl.writeFull(loaderJSFileName, ByteBuffer.wrap(LoaderContent.bytesContent))
def writeJSFile(): Future[Unit] = {
val jsFileOutput =
buildJSFileOutput(onlyModule, loaderJSFileName, wasmFileName, context.allImportedModules)
val jsFileOutputBytes = jsFileOutput.getBytes(StandardCharsets.UTF_8)
outputImpl.writeFull(jsFileName, ByteBuffer.wrap(jsFileOutputBytes))
}
for {
existingFiles <- outputImpl.listFiles()
_ <- Future.sequence(existingFiles.filterNot(filesToProduce).map(outputImpl.delete(_)))
_ <- maybeWriteWatFile()
_ <- writeWasmFile()
_ <- writeLoaderFile()
_ <- writeJSFile()
} yield {
val reportModule = new ReportImpl.ModuleImpl(
moduleID,
jsFileName,
None,
linkerConfig.moduleKind
)
new ReportImpl(List(reportModule))
}
}
private def buildJSFileOutput(
module: ModuleSet.Module,
loaderJSFileName: String,
wasmFileName: String,
importedModules: List[String]
): String = {
val (moduleImports, importedModulesItems) = (for {
(moduleName, idx) <- importedModules.zipWithIndex
} yield {
val identName = s"imported$idx"
val escapedModuleName = "\"" + moduleName + "\""
val moduleImport = s"import * as $identName from $escapedModuleName"
val item = s" $escapedModuleName: $identName,"
(moduleImport, item)
}).unzip
/* TODO This is not correct for exported *vars*, since they won't receive
* updates from mutations after loading.
*/
val reExportStats = for {
exportName <- module.topLevelExports.map(_.exportName)
} yield {
s"export let $exportName = __exports.$exportName;"
}
s"""
|${moduleImports.mkString("\n")}
|
|import { load as __load } from './${loaderJSFileName}';
|const __exports = await __load('./${wasmFileName}', {
|${importedModulesItems.mkString("\n")}
|});
|
|${reExportStats.mkString("\n")}
""".stripMargin.trim() + "\n"
}
}