Skip to content

Commit e270316

Browse files
committed
Refactor System to allow better DCE and to use it in the minilib.
1 parent e7706c6 commit e270316

File tree

3 files changed

+186
-145
lines changed

3 files changed

+186
-145
lines changed

javalanglib/src/main/scala/java/lang/System.scala

Lines changed: 184 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -20,45 +20,80 @@ import scala.scalajs.runtime.linkingInfo
2020

2121
import java.{util => ju}
2222

23-
import Utils._
24-
2523
object System {
26-
var out: PrintStream = new JSConsoleBasedPrintStream(isErr = false)
27-
var err: PrintStream = new JSConsoleBasedPrintStream(isErr = true)
28-
var in: InputStream = null
24+
/* System contains a bag of unrelated features. If we naively implement
25+
* everything inside System, reaching any of these features can reach
26+
* unrelated code. For example, using `nanoTime()` would reach
27+
* `JSConsoleBasedPrintStream` and therefore a bunch of `java.io` classes.
28+
*
29+
* Instead, every feature that requires its own fields is extracted in a
30+
* separate private object, and corresponding methods of System delegate to
31+
* methods of that private object.
32+
*
33+
* All non-intrinsic methods are marked `@inline` so that the module accessor
34+
* of `System` can always be completely elided.
35+
*/
36+
37+
// Standard streams (out, err, in) ------------------------------------------
38+
39+
private object Streams {
40+
var out: PrintStream = new JSConsoleBasedPrintStream(isErr = false)
41+
var err: PrintStream = new JSConsoleBasedPrintStream(isErr = true)
42+
var in: InputStream = null
43+
}
2944

45+
@inline
46+
def out: PrintStream = Streams.out
47+
48+
@inline
49+
def err: PrintStream = Streams.err
50+
51+
@inline
52+
def in: InputStream = Streams.in
53+
54+
@inline
3055
def setIn(in: InputStream): Unit =
31-
this.in = in
56+
Streams.in = in
3257

58+
@inline
3359
def setOut(out: PrintStream): Unit =
34-
this.out = out
60+
Streams.out = out
3561

62+
@inline
3663
def setErr(err: PrintStream): Unit =
37-
this.err = err
64+
Streams.err = err
65+
66+
// System time --------------------------------------------------------------
3867

39-
def currentTimeMillis(): scala.Long = {
68+
@inline
69+
def currentTimeMillis(): scala.Long =
4070
(new js.Date).getTime().toLong
41-
}
4271

43-
private[this] val getHighPrecisionTime: js.Function0[scala.Double] = {
44-
import Utils.DynamicImplicits.truthValue
72+
private object NanoTime {
73+
val getHighPrecisionTime: js.Function0[scala.Double] = {
74+
import Utils.DynamicImplicits.truthValue
4575

46-
if (js.typeOf(global.performance) != "undefined") {
47-
if (global.performance.now) {
48-
() => global.performance.now().asInstanceOf[scala.Double]
49-
} else if (global.performance.webkitNow) {
50-
() => global.performance.webkitNow().asInstanceOf[scala.Double]
76+
if (js.typeOf(global.performance) != "undefined") {
77+
if (global.performance.now) {
78+
() => global.performance.now().asInstanceOf[scala.Double]
79+
} else if (global.performance.webkitNow) {
80+
() => global.performance.webkitNow().asInstanceOf[scala.Double]
81+
} else {
82+
() => new js.Date().getTime()
83+
}
5184
} else {
5285
() => new js.Date().getTime()
5386
}
54-
} else {
55-
() => new js.Date().getTime()
5687
}
5788
}
5889

90+
@inline
5991
def nanoTime(): scala.Long =
60-
(getHighPrecisionTime() * 1000000).toLong
92+
(NanoTime.getHighPrecisionTime() * 1000000).toLong
93+
94+
// arraycopy ----------------------------------------------------------------
6195

96+
// Intrinsic
6297
def arraycopy(src: Object, srcPos: scala.Int, dest: Object,
6398
destPos: scala.Int, length: scala.Int): Unit = {
6499

@@ -144,79 +179,93 @@ object System {
144179
})
145180
}
146181

147-
def identityHashCode(x: Object): scala.Int = {
148-
(x: Any) match {
149-
case null => 0
150-
case _:scala.Boolean | _:scala.Double | _:String | () =>
151-
x.hashCode()
152-
case _ =>
153-
import IDHashCode._
154-
if (x.getClass == null) {
155-
/* x is not a Scala.js object: we have delegate to x.hashCode().
156-
* This is very important, as we really need to go through
157-
* `$objectHashCode()` in `CoreJSLib` instead of using our own
158-
* `idHashCodeMap`. That's because `$objectHashCode()` uses the
159-
* intrinsic `$systemIdentityHashCode` for JS objects, regardless of
160-
* whether the optimizer is enabled or not. If we use our own
161-
* `idHashCodeMap`, we will get different hash codes when obtained
162-
* through `System.identityHashCode(x)` than with `x.hashCode()`.
163-
*/
164-
x.hashCode()
165-
} else if (linkingInfo.assumingES6 || idHashCodeMap != null) {
166-
// Use the global WeakMap of attributed id hash codes
167-
val hash = idHashCodeMap.get(x.asInstanceOf[js.Any])
168-
if (!Utils.isUndefined(hash)) {
169-
hash.asInstanceOf[Int]
170-
} else {
171-
val newHash = nextIDHashCode()
172-
idHashCodeMap.set(x.asInstanceOf[js.Any], newHash.asInstanceOf[js.Any])
173-
newHash
174-
}
175-
} else {
176-
val hash = x.asInstanceOf[js.Dynamic].selectDynamic("$idHashCode$0")
177-
if (!Utils.isUndefined(hash)) {
178-
/* Note that this can work even if x is sealed, if
179-
* identityHashCode() was called for the first time before x was
180-
* sealed.
181-
*/
182-
hash.asInstanceOf[Int]
183-
} else if (!js.Object.isSealed(x.asInstanceOf[js.Object])) {
184-
/* If x is not sealed, we can (almost) safely create an additional
185-
* field with a bizarre and relatively long name, even though it is
186-
* technically undefined behavior.
187-
*/
188-
val newHash = nextIDHashCode()
189-
x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")(newHash.asInstanceOf[js.Any])
190-
newHash
191-
} else {
192-
// Otherwise, we unfortunately have to return a constant.
193-
42
194-
}
195-
}
196-
}
197-
}
182+
// identityHashCode ---------------------------------------------------------
198183

199184
private object IDHashCode {
200-
private var lastIDHashCode: Int = 0
185+
private[this] var lastIDHashCode: Int = 0
201186

202-
val idHashCodeMap =
187+
private[this] val idHashCodeMap = {
203188
if (linkingInfo.assumingES6 || js.typeOf(global.WeakMap) != "undefined")
204189
js.Dynamic.newInstance(global.WeakMap)()
205190
else
206191
null
192+
}
207193

208-
def nextIDHashCode(): Int = {
194+
@inline
195+
private def nextIDHashCode(): Int = {
209196
val r = lastIDHashCode + 1
210197
lastIDHashCode = r
211198
r
212199
}
200+
201+
def idHashCode(x: Object): scala.Int = {
202+
(x: Any) match {
203+
case null =>
204+
0
205+
case _:scala.Boolean | _:scala.Double | _:String | () =>
206+
x.hashCode()
207+
case _ =>
208+
if (x.getClass == null) {
209+
/* x is not a Scala.js object: we have delegate to x.hashCode().
210+
* This is very important, as we really need to go through
211+
* `$objectHashCode()` in `CoreJSLib` instead of using our own
212+
* `idHashCodeMap`. That's because `$objectHashCode()` uses the
213+
* intrinsic `$systemIdentityHashCode` for JS objects, regardless
214+
* of whether the optimizer is enabled or not. If we use our own
215+
* `idHashCodeMap`, we will get different hash codes when obtained
216+
* through `System.identityHashCode(x)` than with `x.hashCode()`.
217+
*/
218+
x.hashCode()
219+
} else if (linkingInfo.assumingES6 || idHashCodeMap != null) {
220+
// Use the global WeakMap of attributed id hash codes
221+
val hash = idHashCodeMap.get(x.asInstanceOf[js.Any])
222+
if (hash ne ().asInstanceOf[AnyRef]) {
223+
hash.asInstanceOf[Int]
224+
} else {
225+
val newHash = nextIDHashCode()
226+
idHashCodeMap.set(x.asInstanceOf[js.Any],
227+
newHash.asInstanceOf[js.Any])
228+
newHash
229+
}
230+
} else {
231+
val hash = x.asInstanceOf[js.Dynamic].selectDynamic("$idHashCode$0")
232+
if (hash ne ().asInstanceOf[AnyRef]) {
233+
/* Note that this can work even if x is sealed, if
234+
* identityHashCode() was called for the first time before x was
235+
* sealed.
236+
*/
237+
hash.asInstanceOf[Int]
238+
} else if (!js.Object.isSealed(x.asInstanceOf[js.Object])) {
239+
/* If x is not sealed, we can (almost) safely create an
240+
* additional field with a bizarre and relatively long name, even
241+
* though it is technically undefined behavior.
242+
*/
243+
val newHash = nextIDHashCode()
244+
x.asInstanceOf[js.Dynamic].updateDynamic("$idHashCode$0")(
245+
newHash.asInstanceOf[js.Any])
246+
newHash
247+
} else {
248+
// Otherwise, we unfortunately have to return a constant.
249+
42
250+
}
251+
}
252+
}
253+
}
213254
}
214255

256+
// Intrinsic
257+
def identityHashCode(x: Object): scala.Int =
258+
IDHashCode.idHashCode(x)
259+
260+
// System properties --------------------------------------------------------
261+
215262
private object SystemProperties {
216-
var dict: js.Dictionary[String] = loadSystemProperties()
217-
var properties: ju.Properties = null
263+
import Utils._
264+
265+
private[this] var dict: js.Dictionary[String] = loadSystemProperties()
266+
private[this] var properties: ju.Properties = null
218267

219-
private[System] def loadSystemProperties(): js.Dictionary[String] = {
268+
private def loadSystemProperties(): js.Dictionary[String] = {
220269
val result = new js.Object().asInstanceOf[js.Dictionary[String]]
221270
dictSet(result, "java.version", "1.8")
222271
dictSet(result, "java.vm.specification.version", "1.8")
@@ -233,7 +282,7 @@ object System {
233282
result
234283
}
235284

236-
private[System] def forceProperties(): ju.Properties = {
285+
def getProperties(): ju.Properties = {
237286
if (properties eq null) {
238287
properties = new ju.Properties
239288
val keys = js.Object.keys(dict.asInstanceOf[js.Object])
@@ -244,60 +293,90 @@ object System {
244293
}
245294
properties
246295
}
296+
297+
def setProperties(properties: ju.Properties): Unit = {
298+
if (properties eq null) {
299+
dict = loadSystemProperties()
300+
this.properties = null
301+
} else {
302+
dict = null
303+
this.properties = properties
304+
}
305+
}
306+
307+
def getProperty(key: String): String =
308+
if (dict ne null) dictGetOrElse(dict, key, null)
309+
else properties.getProperty(key)
310+
311+
def getProperty(key: String, default: String): String =
312+
if (dict ne null) dictGetOrElse(dict, key, default)
313+
else properties.getProperty(key, default)
314+
315+
def clearProperty(key: String): String =
316+
if (dict ne null) dictGetOrElseAndRemove(dict, key, null)
317+
else properties.remove(key).asInstanceOf[String]
318+
319+
def setProperty(key: String, value: String): String = {
320+
if (dict ne null) {
321+
val oldValue = getProperty(key)
322+
dictSet(dict, key, value)
323+
oldValue
324+
} else {
325+
properties.setProperty(key, value).asInstanceOf[String]
326+
}
327+
}
247328
}
248329

330+
@inline
249331
def getProperties(): ju.Properties =
250-
SystemProperties.forceProperties()
332+
SystemProperties.getProperties()
251333

334+
@inline
252335
def lineSeparator(): String = "\n"
253336

254-
def setProperties(properties: ju.Properties): Unit = {
255-
if (properties eq null) {
256-
SystemProperties.dict = SystemProperties.loadSystemProperties()
257-
SystemProperties.properties = null
258-
} else {
259-
SystemProperties.dict = null
260-
SystemProperties.properties = properties
261-
}
262-
}
337+
@inline
338+
def setProperties(properties: ju.Properties): Unit =
339+
SystemProperties.setProperties(properties)
263340

341+
@inline
264342
def getProperty(key: String): String =
265-
if (SystemProperties.dict ne null) dictGetOrElse(SystemProperties.dict, key, null)
266-
else SystemProperties.properties.getProperty(key)
343+
SystemProperties.getProperty(key)
267344

345+
@inline
268346
def getProperty(key: String, default: String): String =
269-
if (SystemProperties.dict ne null) dictGetOrElse(SystemProperties.dict, key, default)
270-
else SystemProperties.properties.getProperty(key, default)
347+
SystemProperties.getProperty(key, default)
271348

349+
@inline
272350
def clearProperty(key: String): String =
273-
if (SystemProperties.dict ne null) dictGetOrElseAndRemove(SystemProperties.dict, key, null)
274-
else SystemProperties.properties.remove(key).asInstanceOf[String]
275-
276-
def setProperty(key: String, value: String): String = {
277-
if (SystemProperties.dict ne null) {
278-
val oldValue = getProperty(key)
279-
dictSet(SystemProperties.dict, key, value)
280-
oldValue
281-
} else {
282-
SystemProperties.properties.setProperty(key, value).asInstanceOf[String]
283-
}
284-
}
351+
SystemProperties.clearProperty(key)
352+
353+
@inline
354+
def setProperty(key: String, value: String): String =
355+
SystemProperties.setProperty(key, value)
285356

357+
// Environment variables ----------------------------------------------------
358+
359+
@inline
286360
def getenv(): ju.Map[String, String] =
287361
ju.Collections.emptyMap()
288362

363+
@inline
289364
def getenv(name: String): String = {
290365
if (name eq null)
291366
throw new NullPointerException
292367

293368
null
294369
}
295370

371+
// Runtime ------------------------------------------------------------------
372+
296373
//def exit(status: scala.Int): Unit
374+
375+
@inline
297376
def gc(): Unit = Runtime.getRuntime().gc()
298377
}
299378

300-
private[lang] final class JSConsoleBasedPrintStream(isErr: scala.Boolean)
379+
private final class JSConsoleBasedPrintStream(isErr: scala.Boolean)
301380
extends PrintStream(new JSConsoleBasedPrintStream.DummyOutputStream) {
302381

303382
import JSConsoleBasedPrintStream._

0 commit comments

Comments
 (0)