BootImageBuilder.scala 13.5 KB
Newer Older
1
2
package uvm.refimpl.bootimg

Kunshan Wang's avatar
Kunshan Wang committed
3
4
5
6
import java.io._
import java.io.OutputStream
import java.nio.charset.StandardCharsets
import java.nio.file._
7
8
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
9

Kunshan Wang's avatar
Kunshan Wang committed
10
import scala.collection.mutable.ArrayBuffer
11
import scala.collection.mutable.HashMap
12

13
import org.slf4j.LoggerFactory
Kunshan Wang's avatar
Kunshan Wang committed
14

15
import com.typesafe.scalalogging.Logger
Kunshan Wang's avatar
Kunshan Wang committed
16
17

import uvm._
Kunshan Wang's avatar
Kunshan Wang committed
18
import uvm.ir.irbuilder.IRBuilder
19
20
import uvm.ir.textoutput.BundleSerializer
import uvm.ir.textoutput.EntityUtils
Kunshan Wang's avatar
Kunshan Wang committed
21
import uvm.refimpl._
22
23
24
import uvm.refimpl.itpr.FrameCursor
import uvm.refimpl.itpr.InterpreterStack
import uvm.refimpl.itpr.InterpreterThread
Kunshan Wang's avatar
Kunshan Wang committed
25
26
27
import uvm.refimpl.mem.HeaderUtils
import uvm.refimpl.mem.Space
import uvm.refimpl.mem.TypeSizes
28
29
import uvm.refimpl.mem.scanning.MemoryDataScanner
import uvm.refimpl.mem.scanning.MemoryFieldHandler
Kunshan Wang's avatar
Kunshan Wang committed
30
31
import uvm.refimpl.nat.NativeSupport
import uvm.types._
Kunshan Wang's avatar
Kunshan Wang committed
32
import uvm.utils.IOHelpers
33
import uvm.utils.WithUtils.tryWithResource
34
35

class BootImageBuilder(implicit microVM: MicroVM) {
Kunshan Wang's avatar
Kunshan Wang committed
36
  def makeBootImage(whiteList: Seq[TopLevel], primordial: PrimordialInfo,
37
    syms: Seq[FieldAndSymbol], relocs: Seq[FieldAndSymbol], outputFile: String): Unit = {
Kunshan Wang's avatar
Kunshan Wang committed
38
    implicit val tcb = new TransitiveClosureBuilder(whiteList, primordial)
Kunshan Wang's avatar
Kunshan Wang committed
39
40
41

    tcb.doTransitiveClosure()

Kunshan Wang's avatar
Kunshan Wang committed
42
    val biw = new BootImageWriter(tcb, syms, relocs, outputFile)
Kunshan Wang's avatar
Kunshan Wang committed
43
44
45
46
    biw.writeFile()
  }
}

Kunshan Wang's avatar
Kunshan Wang committed
47
48
49
50
51
52
53
54
55
case class PrimordialInfo(maybeFunc: Option[Function], maybeStack: Option[InterpreterStack], maybeThreadLocal: Option[Word])
object PrimordialInfo {
  val NO_PRIMORDIAL = PrimordialInfo(None, None, None)
}

case class FieldAndSymbol(objRef: Word, offset: Word, symbol: String) {
  def toAddrSymPair: (Word, String) = ((objRef + offset), symbol)
}

Kunshan Wang's avatar
Kunshan Wang committed
56
object BootImageWriter {
Kunshan Wang's avatar
Kunshan Wang committed
57
  import BootImageFile._
Kunshan Wang's avatar
Kunshan Wang committed
58
59
60
61
62
63
64
65
66
67
68
69
70
71
  val logger = Logger(LoggerFactory.getLogger(getClass.getName))

  class FileGroup(baseName: String, dir: Path) {
    val dataFile = dir.resolve(baseName + FILEEXT_DATA)
    val allocFile = dir.resolve(baseName + FILEEXT_ALLOC)
    val relocFile = dir.resolve(baseName + FILEEXT_RELOC)

    val dataOS = new AlignedOutputStream(new BufferedOutputStream(Files.newOutputStream(dataFile)))

    val allocRecs = new ArrayBuffer[UnitAllocRecord]
    val relocRecs = new ArrayBuffer[FieldRelocRecord]
  }
}

Kunshan Wang's avatar
Kunshan Wang committed
72
73
class BootImageWriter(tcb: TransitiveClosureBuilder, syms: Seq[FieldAndSymbol],
    relocs: Seq[FieldAndSymbol], outputFile: String)(implicit microVM: MicroVM) {
Kunshan Wang's avatar
Kunshan Wang committed
74
  import BootImageWriter._
Kunshan Wang's avatar
Kunshan Wang committed
75
  import BootImageFile._
Kunshan Wang's avatar
Kunshan Wang committed
76

Kunshan Wang's avatar
Kunshan Wang committed
77
78
  val bundleSerializer = new BundleSerializer(microVM.globalBundle, tcb.tls.set)

Kunshan Wang's avatar
Kunshan Wang committed
79
  val tempDir = Files.createTempDirectory("mu-bootimg")
80

Kunshan Wang's avatar
Kunshan Wang committed
81
82
83
84
  logger.info("Writing bootimg files to temp dir: %s".format(tempDir.toAbsolutePath().toString()))

  val globalGroup = new FileGroup("global", tempDir)
  val heapGroup = new FileGroup("heap", tempDir)
85
86
87

  val manualSymRecs = new ArrayBuffer[ManualSymRecord]

Kunshan Wang's avatar
Kunshan Wang committed
88
  val relocsMap = relocs.map(_.toAddrSymPair).toMap
89

Kunshan Wang's avatar
Kunshan Wang committed
90
91
92
  {
    logger.debug("Relocation map: " + relocsMap.toString)
  }
93

94
95
  val idNameMapPath = tempDir.resolve(IDNAMEMAP_FILE)
  val uirBundlePath = tempDir.resolve(UIRBUNDLE_FILE)
96
  val manualSymsPath = tempDir.resolve(MANUALSYMS_FILE)
Kunshan Wang's avatar
Kunshan Wang committed
97
  val metaInfoPath = tempDir.resolve(METAINFO_FILE)
Kunshan Wang's avatar
Kunshan Wang committed
98
  val zipPath = Paths.get(outputFile)
99

Kunshan Wang's avatar
Kunshan Wang committed
100
  val allZipContents = Seq(globalGroup.dataFile, heapGroup.dataFile, globalGroup.allocFile, heapGroup.allocFile,
101
    globalGroup.relocFile, heapGroup.relocFile, idNameMapPath, uirBundlePath, manualSymsPath, metaInfoPath)
Kunshan Wang's avatar
Kunshan Wang committed
102
103
104
105
106

  private val globalMemory: Space = microVM.memoryManager.globalMemory
  private val smallObjectSpace: Space = microVM.memoryManager.heap.space
  private val largeObjectSpace: Space = microVM.memoryManager.heap.los
  private implicit val memorySupport = microVM.memoryManager.memorySupport
107

Kunshan Wang's avatar
Kunshan Wang committed
108
109
  val addrToGlobalCell = tcb.globalCellMap
  val addrToHeapObjNum = new HashMap[Word, Long]()
Kunshan Wang's avatar
Kunshan Wang committed
110
111
112
113
114
115
116
117
118
119
120
121
122
123

  def writeFile(): Unit = {
    for (addr <- tcb.allocs.set) {
      if (globalMemory.isInSpace(addr)) {
        writeAllocUnit(addr, globalGroup, isGlobal = true)
      } else {
        writeAllocUnit(addr, heapGroup, isGlobal = false)
      }
    }
    globalGroup.dataOS.close()
    heapGroup.dataOS.close()

    writeAllocRecs(globalGroup)
    writeAllocRecs(heapGroup)
124
125
126
127

    locateManualSyms()
    writeManualSyms()

Kunshan Wang's avatar
reloc    
Kunshan Wang committed
128
129
130
    for (ar <- heapGroup.allocRecs) {
      addrToHeapObjNum(ar.addr) = ar.num
    }
131

Kunshan Wang's avatar
Kunshan Wang committed
132
133
    scanForRelocs(globalGroup)
    scanForRelocs(heapGroup)
134

Kunshan Wang's avatar
Kunshan Wang committed
135
    addPointerRelocs()
136

Kunshan Wang's avatar
Kunshan Wang committed
137
138
    writeRelocRecs(globalGroup)
    writeRelocRecs(heapGroup)
139

Kunshan Wang's avatar
Kunshan Wang committed
140
    writeIDNameMap()
141
    writeUIRBundle()
142

Kunshan Wang's avatar
Kunshan Wang committed
143
    writeMetaInfo()
144

Kunshan Wang's avatar
Kunshan Wang committed
145
    makeZipPackage()
Kunshan Wang's avatar
Kunshan Wang committed
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
  }

  private def writeAllocUnit(addr: Word, group: FileGroup, isGlobal: Boolean): Unit = {
    val tag = HeaderUtils.getTag(addr)
    val ty = HeaderUtils.getType(microVM, tag)
    val (size, align, varLen) = ty match {
      case t: TypeHybrid => {
        val len = HeaderUtils.getVarLength(addr)
        val sz = TypeSizes.hybridSizeOf(t, len)
        val al = TypeSizes.hybridAlignOf(t, len)
        (sz, al, len)
      }
      case t => {
        val sz = TypeSizes.sizeOf(t)
        val al = TypeSizes.alignOf(t)
        (sz, al, 0L)
      }
    }

165
    val num = if (isGlobal) addrToGlobalCell(addr).g.id else group.allocRecs.length
Kunshan Wang's avatar
Kunshan Wang committed
166
167
168

    group.dataOS.alignUp(align)
    val pos = group.dataOS.position
169
    group.dataOS.writeFromMemory(addr, size)
Kunshan Wang's avatar
Kunshan Wang committed
170

Kunshan Wang's avatar
Kunshan Wang committed
171
    val rec = UnitAllocRecord(num, pos, ty, varLen, addr)
Kunshan Wang's avatar
Kunshan Wang committed
172
173
174
175
176
177
    group.allocRecs += rec
  }

  private def writeAllocRecs(group: FileGroup): Unit = {
    tryWithResource(Files.newBufferedWriter(group.allocFile, StandardCharsets.UTF_8)) { writer =>
      for (ar <- group.allocRecs) {
Kunshan Wang's avatar
Kunshan Wang committed
178
        writer.write("%d,%d,%d,%d,%s\n".format(ar.num, ar.fileOffset, ar.ty.id, ar.varLen, EntityUtils.getName(ar.ty)))
Kunshan Wang's avatar
Kunshan Wang committed
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
207
208
209
210
211
212
213
  private def toManualSymRec(fns: FieldAndSymbol): ManualSymRecord = {
    val iref = fns.objRef + fns.offset
    if (fns.objRef != 0L || !globalMemory.isInSpace(iref)) {
      throw new BootImageBuilderException("Symbol can only be in global cells. Symbol: '%s', objRef: 0x%x, offset: 0x%x, addr: 0x%x".format(
        fns.symbol, fns.objRef, fns.offset, iref))
    }

    val gcr = tcb.getGlobalCellRec(iref)
    val gid = gcr.g.id
    val offset = fns.offset - gcr.begin

    val msr = ManualSymRecord(fns.symbol, gid, offset)

    msr
  }

  private def locateManualSyms(): Unit = {
    for (fns <- syms) {
      manualSymRecs += toManualSymRec(fns)
    }
  }

  private def writeManualSyms(): Unit = {
    tryWithResource(Files.newBufferedWriter(manualSymsPath, StandardCharsets.UTF_8)) { writer =>
      for (msr <- manualSymRecs) {
        writer.write("%s,%d,%d\n".format(
          msr.sym, msr.targetNum, msr.targetOffset))
      }
    }
  }

Kunshan Wang's avatar
Kunshan Wang committed
214
215
  private def scanForRelocs(group: FileGroup): Unit = {
    for (alloc <- group.allocRecs) {
216
217
218
219
      val num = alloc.num
      val begin = alloc.addr

      MemoryDataScanner.scanAllocUnit(begin, new MemoryFieldHandler {
Kunshan Wang's avatar
Kunshan Wang committed
220
221
        def visitUPtrField(objRef: Word, iRef: Word, toAddr: Word): Unit = {
          relocsMap.get(iRef) match {
222
223
224
225
226
            case Some(symbol) => {
              // by symbol
              val fieldOffset = iRef - objRef
              val reloc = FieldRelocRecord(num, fieldOffset, AS_UPTR, TO_SYM, 0, 0, symbol)
              group.relocRecs += reloc
Kunshan Wang's avatar
Kunshan Wang committed
227
              logger.debug("External uptr relocation: %d 0x%x -> %s".format(iRef, iRef, symbol))
228
229
230
231
232
233
234
235
236
237
238
239
            }
            case None => {
              // maybe a "reasonable" pointer to a mu global cell. Relocate it too.
              val fieldOffset = iRef - objRef
              val maybeGlobalCell = tcb.maybeGetGlobalCellRec(toAddr)
              maybeGlobalCell match {
                case Some(gcr) =>
                  val targetNum = gcr.g.id.toLong
                  val targetAddr = gcr.begin
                  val targetOffset = toAddr - targetAddr
                  val reloc = FieldRelocRecord(num, fieldOffset, AS_UPTR, TO_GLOBAL, targetNum, targetOffset, NO_SYM)
                  group.relocRecs += reloc
Kunshan Wang's avatar
Kunshan Wang committed
240
                  logger.info("Relocation entry for uptr field automatically generated: %d 0x%x -> %d 0x%x".format(
241
                    iRef, iRef, toAddr, toAddr))
242
243
                case None =>
              }
244
245
246
            }
          }
        }
Kunshan Wang's avatar
Kunshan Wang committed
247
248
249
250
251
252
253
254
255
        def visitUFPField(objRef: Word, iRef: Word, toAddr: Word): Unit = {
          relocsMap.get(iRef).foreach { symbol =>
            // by symbol
            val fieldOffset = iRef - objRef
            val reloc = FieldRelocRecord(num, fieldOffset, AS_UPTR, TO_SYM, 0, 0, symbol)
            group.relocRecs += reloc
            logger.debug("External ufuncptr relocation: %d 0x%x -> %s".format(iRef, iRef, symbol))
          }
        }
256

Kunshan Wang's avatar
reloc    
Kunshan Wang committed
257
258
259
        def visitRefField(objRef: Word, iRef: Word, toObj: Word, isWeak: Boolean): Unit = if (toObj != 0L) {
          val fieldOffset = iRef - objRef
          val targetNum = addrToHeapObjNum(toObj)
Kunshan Wang's avatar
Kunshan Wang committed
260
          val reloc = FieldRelocRecord(num, fieldOffset, AS_REF, TO_HEAP, targetNum, NO_OFFSET, NO_SYM)
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
261
262
263
264
265
266
          group.relocRecs += reloc
        }

        def visitIRefField(objRef: Word, iRef: Word, toObj: Word, toOffset: Word): Unit = if (toObj != 0L) {
          val fieldOffset = iRef - objRef
          val targetNum = addrToHeapObjNum(toObj)
Kunshan Wang's avatar
Kunshan Wang committed
267
          val reloc = FieldRelocRecord(num, fieldOffset, AS_IREF, TO_HEAP, targetNum, toOffset, NO_SYM)
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
268
269
270
271
272
273
274
          group.relocRecs += reloc
        } else {
          val fieldOffset = iRef - objRef
          val gcr = tcb.getGlobalCellRec(toOffset)
          val targetNum = gcr.g.id.toLong
          val targetAddr = gcr.begin
          val targetOffset = toOffset - targetAddr
Kunshan Wang's avatar
Kunshan Wang committed
275
          val reloc = FieldRelocRecord(num, fieldOffset, AS_IREF, TO_GLOBAL, targetNum, targetOffset, NO_SYM)
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
276
277
278
279
280
281
          group.relocRecs += reloc
        }

        def visitTagRefField(objRef: Word, iRef: Word, toObj: Word): Unit = if (toObj != 0L) {
          val fieldOffset = iRef - objRef
          val targetNum = addrToHeapObjNum(toObj)
Kunshan Wang's avatar
Kunshan Wang committed
282
          val reloc = FieldRelocRecord(num, fieldOffset, AS_TAGREF, TO_HEAP, targetNum, NO_OFFSET, NO_SYM)
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
283
284
285
286
287
288
          group.relocRecs += reloc
        }

        def visitFuncRefField(objRef: Word, iRef: Word, toFunc: Option[Function]): Unit = toFunc.foreach { func =>
          val fieldOffset = iRef - objRef
          val targetNum = func.id.toLong
Kunshan Wang's avatar
Kunshan Wang committed
289
          val reloc = FieldRelocRecord(num, fieldOffset, AS_FUNCREF, TO_FUNC, targetNum, NO_OFFSET, NO_SYM)
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
290
291
292
293
          group.relocRecs += reloc
        }

        // We already made sure in the transitive closure step that the following are all NULL.
294
295
296
        def visitStackRefField(objRef: Word, iRef: Word, toStack: Option[InterpreterStack]): Unit = {}
        def visitThreadRefField(objRef: Word, iRef: Word, toThread: Option[InterpreterThread]): Unit = {}
        def visitFCRefField(objRef: Word, iRef: Word, toFCRef: Option[FrameCursor]): Unit = {}
Kunshan Wang's avatar
Kunshan Wang committed
297
        def visitIRBuilderRefField(objRef: Word, iRef: Word, toIRNode: Option[IRBuilder]): Unit = {}
298
      })
Kunshan Wang's avatar
Kunshan Wang committed
299
300
    }
  }
301

Kunshan Wang's avatar
Kunshan Wang committed
302
  private def addPointerRelocs(): Unit = {
303
    // Seems uptrs are already handled when scanning fields. No further processing is needed at this moment.
Kunshan Wang's avatar
Kunshan Wang committed
304
  }
Kunshan Wang's avatar
Kunshan Wang committed
305
306

  private def writeRelocRecs(group: FileGroup): Unit = {
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
307
308
    tryWithResource(Files.newBufferedWriter(group.relocFile, StandardCharsets.UTF_8)) { writer =>
      for (rr <- group.relocRecs) {
309
        writer.write("%d,%d,%s,%s,%d,%d,%s\n".format(
310
          rr.num, rr.fieldOffset, rr.fieldKind, rr.targetKind, rr.targetNum, rr.targetOffset, rr.targetString))
Kunshan Wang's avatar
reloc    
Kunshan Wang committed
311
312
      }
    }
Kunshan Wang's avatar
Kunshan Wang committed
313
  }
314

Kunshan Wang's avatar
Kunshan Wang committed
315
  private def writeIDNameMap(): Unit = {
316
    tryWithResource(Files.newBufferedWriter(idNameMapPath, StandardCharsets.UTF_8)) { writer =>
Kunshan Wang's avatar
Kunshan Wang committed
317
      bundleSerializer.writeIDNameMap(writer)
318
319
320
321
322
    }
  }

  private def writeUIRBundle(): Unit = {
    tryWithResource(Files.newBufferedWriter(uirBundlePath, StandardCharsets.UTF_8)) { writer =>
Kunshan Wang's avatar
Kunshan Wang committed
323
      bundleSerializer.writeUIR(writer)
324
    }
Kunshan Wang's avatar
Kunshan Wang committed
325
  }
326

Kunshan Wang's avatar
Kunshan Wang committed
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
  private def writeMetaInfo(): Unit = {
    tryWithResource(Files.newBufferedWriter(metaInfoPath, StandardCharsets.UTF_8)) { writer =>
      val sb = new StringBuilder()
      tcb.primordial.maybeFunc.foreach { func =>
        sb.append("%s=%d\n".format(KEY_INITFUNC, func.id))
        tcb.primordial.maybeThreadLocal.foreach { addr =>
          val heapObjNum = heapGroup.allocRecs.find(_.addr == addr).getOrElse {
            throw new BootImageBuilderException("BUG: Thread local object is not in the boot image")
          }.num
          sb.append("%s=%d\n".format(KEY_THREADLOCAL, heapObjNum))
        }
      }
      writer.write(sb.toString)
    }
  }
342

Kunshan Wang's avatar
Kunshan Wang committed
343
344
345
346
347
348
349
350
351
352
353
  private def makeZipPackage(): Unit = {
    tryWithResource(new ZipOutputStream(Files.newOutputStream(zipPath))) { zos =>
      for (path <- allZipContents) {
        val entry = new ZipEntry(path.getFileName.toString)
        zos.putNextEntry(entry)
        tryWithResource(Files.newInputStream(path)) { fis =>
          IOHelpers.copy(fis, zos)
        }
      }
    }
  }
Kunshan Wang's avatar
Kunshan Wang committed
354
}
Kunshan Wang's avatar
Kunshan Wang committed
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369

class AlignedOutputStream(os: OutputStream) extends Closeable {
  var position: Long = 0L

  private val BUFFER_SZ = 16384
  private val buffer = new Array[Byte](BUFFER_SZ)

  def alignUp(alignment: Long): Unit = {
    val newPos = TypeSizes.alignUp(position, alignment)

    position until newPos foreach { _ => os.write(0) }

    position = newPos
  }

370
  def writeFromMemory(addr: Long, size: Long): Unit = {
Kunshan Wang's avatar
Kunshan Wang committed
371
372
373
374
375
376
377
378
379
380
381
382
    var nextAddr = addr
    var remain = size

    while (remain > BUFFER_SZ) {
      transfer(nextAddr, BUFFER_SZ)
      nextAddr += BUFFER_SZ
      remain -= BUFFER_SZ
    }

    if (remain > 0) {
      transfer(nextAddr, remain.toInt)
    }
383

384
    position += size
Kunshan Wang's avatar
Kunshan Wang committed
385
386
387
388
389
390
391
392
393
394
395
  }

  private def transfer(addr: Long, size: Int): Unit = {
    NativeSupport.theMemory.get(addr, buffer, 0, size)
    os.write(buffer, 0, size)
  }

  def close(): Unit = {
    os.close()
  }
}