Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ddee368
Tigten IR checking of NewLambda.
sjrd Feb 2, 2026
4cb8c94
Fix #5145: Add {ClassDef,IR}Checker tests for NewLambda.
sjrd Feb 16, 2026
41e6063
Close #5311: Deprecate support for JDK < 17.
sjrd Feb 16, 2026
21a2387
Add LinkingInfo.moduleKind as a link-time property.
sjrd Feb 19, 2026
e133016
Use `LinkingInfo.moduleKind` instead of config-dependent `ExportLoopb…
sjrd Feb 19, 2026
186650b
Bump @tootallnate/once and jsdom
dependabot[bot] Mar 4, 2026
55e86db
Merge pull request #5328 from scala-js/dependabot/npm_and_yarn/multi-…
sjrd Mar 5, 2026
3ff1644
Fix #5331: Transform the body of a void typed closure as a statement.
sjrd Mar 8, 2026
62bcaf1
Merge pull request #5332 from sjrd/fix-void-typed-closure-transform
gzm0 Mar 8, 2026
a52330a
Merge pull request #5322 from sjrd/tighten-ir-check-newlambda
gzm0 Mar 8, 2026
2af249a
Merge pull request #5325 from sjrd/link-time-prop-module-kind
gzm0 Mar 8, 2026
af6919b
Merge pull request #5323 from sjrd/deprecate-jdk-under-17
sjrd Mar 8, 2026
218e649
Fix #5144: More direct hashing of method names for lambda class names.
sjrd Feb 16, 2026
5a94e42
Merge pull request #5324 from sjrd/direct-lambda-make-class-name
sjrd Mar 10, 2026
00b2d3f
Grow linear memory in malloc if required
tanishiking Mar 2, 2026
7a289da
Merge pull request #190 from scala-wasm/grow-memory
tanishiking Mar 11, 2026
9dd12da
Fix the buffer growing logic in InputStream.readNBytes.
sjrd Mar 12, 2026
4fb13b0
Merge remote-tracking branch 'upstream/main' into sync-upstream
tanishiking Mar 12, 2026
6c1d1d1
Merge pull request #194 from scala-wasm/sync-upstream
tanishiking Mar 13, 2026
7c7d6d3
Use dedicated ModuleKind's for Wasm without a JS environment.
sjrd Mar 13, 2026
22d48c0
Fix #5335: Throw a user-friendly exception on inconsistent config.
sjrd Mar 13, 2026
07813a1
Merge pull request #5336 from sjrd/early-error-for-inconcistent-config
sjrd Mar 13, 2026
20f9f18
Bump undici from 7.22.0 to 7.24.1
dependabot[bot] Mar 13, 2026
84f168b
Merge pull request #5337 from scala-js/dependabot/npm_and_yarn/undici…
sjrd Mar 14, 2026
74fb0d1
Merge pull request #196 from scala-js/main
tanishiking Mar 14, 2026
15e1fbc
Merge pull request #195 from sjrd/module-kinds-for-wasm-only-settings
tanishiking Mar 14, 2026
0cce77c
Fix: Return 0 in CharArrayReader(_, _, 0) when past the end.
sjrd Mar 4, 2026
dbf2ef5
In StringBuilder, rely on the underlying string's StringIOOBEs.
sjrd Mar 4, 2026
bfed364
Throw a specified IndexOutOfBoundsException in CharArrayWriter.
sjrd Mar 6, 2026
7b06e2c
Trigger UB for StringIOOBEs in all jl.String methods.
sjrd Mar 6, 2026
23bf595
Throw IOOBE instead of ArrayIOOBE in CharArrayReader.read.
sjrd Mar 4, 2026
edd74b6
Trigger a UB `ArrayStoreException` in `System.arraycopy`.
sjrd Mar 4, 2026
dd7be5a
Replace explicit NegativeArraySizeException's by UB ones.
sjrd Mar 4, 2026
4796da5
Merge pull request #5334 from sjrd/fix-inputstream-readnbytes-grow-bu…
gzm0 Mar 15, 2026
705ac07
Merge pull request #5329 from sjrd/javalib-always-ub-for-ub-exceptions
sjrd Mar 15, 2026
417be19
Merge pull request #197 from scala-js/main
tanishiking Mar 19, 2026
cb6b295
Don't use js.Map in ClassValue when targeting pure Wasm
Mar 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
esVersion: [ES2015, ES2021] # Some javalib features depend on the target ES version
eh-support: [true, false]
env:
SBT_SET_COMMAND: 'set Seq(Global/enableWasmEverywhere := true, ThisBuild/scalaJSLinkerConfig ~= (_.withESFeatures(_.withESVersion(ESVersion.${{ matrix.esVersion }})).withWasmFeatures(_.withTargetPureWasm(true).withExceptionHandling(${{ matrix.eh-support }}))))'
SBT_SET_COMMAND: 'set Seq(Global/enableWasmEverywhere := true, ThisBuild/scalaJSLinkerConfig ~= (_.withESFeatures(_.withESVersion(ESVersion.${{ matrix.esVersion }})).withModuleKind(ModuleKind.MinimalWasmModule).withWasmFeatures(_.withExceptionHandling(${{ matrix.eh-support }}))))'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This is a friendly fork of Scala.js, targeting stand-alone Wasm runtimes such as
### `test-suite`
```sh
sbt:Scala.js> set Global/enableWasmEverywhere := true
sbt:Scala.js> set scalaJSLinkerConfig in testSuite.v2_12 ~= (_.withWasmFeatures(_.withTargetPureWasm(true)))
sbt:Scala.js> set scalaJSLinkerConfig in testSuite.v2_12 ~= (_.withModuleKind(ModuleKind.MinimalWasmModule))
sbt:Scala.js> testSuite2_12/test
```

Expand Down
8 changes: 2 additions & 6 deletions ir/shared/src/main/scala/org/scalajs/ir/Serializers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ import LinkTimeProperty.{
ESVersion,
UseECMAScript2015Semantics,
IsWebAssembly,
LinkerVersion,
TargetPureWasm
LinkerVersion
}
import Types._
import Tags._
Expand Down Expand Up @@ -1590,8 +1589,6 @@ object Serializers {
LinkTimeProperty(LinkerVersion)(StringType)
case StringLiteral("fileLevelThis") =>
JSGlobalRef(JSGlobalRef.FileLevelThis)
case StringLiteral("targetPureWasm") =>
LinkTimeProperty(TargetPureWasm)(BooleanType)
case otherItem =>
JSSelect(jsLinkingInfo, otherItem)
}
Expand Down Expand Up @@ -1628,8 +1625,7 @@ object Serializers {
LinkTimeProperty(UseECMAScript2015Semantics)(BooleanType)),
(StringLiteral("isWebAssembly"), LinkTimeProperty(IsWebAssembly)(BooleanType)),
(StringLiteral("linkerVersion"), LinkTimeProperty(LinkerVersion)(StringType)),
(StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis)),
(StringLiteral("targetPureWasm"), LinkTimeProperty(TargetPureWasm)(BooleanType))
(StringLiteral("fileLevelThis"), JSGlobalRef(JSGlobalRef.FileLevelThis))
))
} else {
throw new IOException(
Expand Down
2 changes: 1 addition & 1 deletion ir/shared/src/main/scala/org/scalajs/ir/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1324,9 +1324,9 @@ object Trees {
final val ProductionMode = "core/productionMode"
final val ESVersion = "core/esVersion"
final val UseECMAScript2015Semantics = "core/useECMAScript2015Semantics"
final val ModuleKind = "core/moduleKind"
final val IsWebAssembly = "core/isWebAssembly"
final val LinkerVersion = "core/linkerVersion"
final val TargetPureWasm = "core/targetPureWasm"
}

// Atomic expressions
Expand Down
8 changes: 5 additions & 3 deletions javalib/src/main/scala/java/io/CharArrayReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,16 @@ class CharArrayReader(protected var buf: Array[Char], offset: Int, length: Int)

override def read(buffer: Array[Char], offset: Int, len: Int): Int = {
if (offset < 0 || offset > buffer.length)
throw new ArrayIndexOutOfBoundsException("Offset out of bounds : " + offset)
throw new IndexOutOfBoundsException("Offset out of bounds : " + offset)

if (len < 0 || len > buffer.length - offset)
throw new ArrayIndexOutOfBoundsException("Length out of bounds : " + len)
throw new IndexOutOfBoundsException("Length out of bounds : " + len)

ensureOpen()

if (this.pos < this.count) {
if (len == 0) {
0
} else if (this.pos < this.count) {
val bytesRead = Math.min(len, this.count - this.pos)
System.arraycopy(this.buf, this.pos, buffer, offset, bytesRead)
this.pos += bytesRead
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/io/CharArrayWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class CharArrayWriter(initialSize: Int) extends Writer {

override def write(str: String, offset: Int, len: Int): Unit = {
if (offset < 0 || offset > str.length || len < 0 || len > str.length - offset)
throw new StringIndexOutOfBoundsException
throw new IndexOutOfBoundsException

ensureCapacity(len)
str.getChars(offset, offset + len, this.buf, this.count)
Expand Down
2 changes: 1 addition & 1 deletion javalib/src/main/scala/java/io/InputStream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ abstract class InputStream extends Closeable {
* - len <= Integer.MAX_VALUE (because of its type)
*/
val newLen =
if (Integer.MAX_VALUE / 2 > buf.length) Integer.MAX_VALUE
if (buf.length > Integer.MAX_VALUE / 2) Integer.MAX_VALUE
else buf.length * 2
buf = Arrays.copyOf(buf, Math.min(len, newLen))
}
Expand Down
9 changes: 5 additions & 4 deletions javalib/src/main/scala/java/lang/Character.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import scala.annotation.{tailrec, switch}

import scala.scalajs.js
import scala.scalajs.LinkingInfo
import scala.scalajs.LinkingInfo.ESVersion
import scala.scalajs.LinkingInfo.{ESVersion, moduleKind}
import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent}

import java.lang.constant.Constable
import java.util.{ArrayList, Arrays, HashMap}
Expand Down Expand Up @@ -132,7 +133,7 @@ object Character {
if (!isValidCodePoint(codePoint))
throw new IllegalArgumentException()

LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) {
LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
if (isBmpCodePoint(codePoint)) {
Character.toString(codePoint.toChar)
} else {
Expand Down Expand Up @@ -723,7 +724,7 @@ object Character {
case _ =>
// In WASI implementation, we cannot use String#toUpperCase
// since it uses Character#toUpperCase.
LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) {
LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
import CaseUtil._
toCase(codePoint, a, z, lowerBeta, lowerRanges, lowerDeltas, lowerSteps)
} {
Expand Down Expand Up @@ -752,7 +753,7 @@ object Character {
case 0x0130 =>
0x0069 // İ => i
case _ =>
LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) {
LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
// in pure Wasm implementation, we cannot use String#toLowerCase
// since it uses Character$toLowerCase
import CaseUtil._
Expand Down
82 changes: 53 additions & 29 deletions javalib/src/main/scala/java/lang/ClassValue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ import java.util.HashMap
import scala.scalajs.js
import scala.scalajs.js.annotation._
import scala.scalajs.LinkingInfo
import scala.scalajs.LinkingInfo.ESVersion
import scala.scalajs.LinkingInfo.{ESVersion, moduleKind}
import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent}

import Utils._

abstract class ClassValue[T] protected () {
private val jsMap: js.Map[Class[_], T] = {
if (LinkingInfo.esVersion >= ESVersion.ES2015 || js.typeOf(js.Dynamic.global.Map) != "undefined")
new js.Map()
else
LinkingInfo.linkTimeIf[js.Map[Class[_], T]](
moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
null
} {
if (LinkingInfo.esVersion >= ESVersion.ES2015 ||
js.typeOf(js.Dynamic.global.Map) != "undefined") {
new js.Map()
} else {
null
}
}
}

@inline
Expand All @@ -35,7 +43,11 @@ abstract class ClassValue[T] protected () {
* emitting ES 2015 code, which allows to dead-code-eliminate the branches
* using `HashMap`s, and therefore `HashMap` itself.
*/
LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null
LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
false
} {
LinkingInfo.esVersion >= ESVersion.ES2015 || jsMap != null
}
}

/* We use a HashMap instead of an IdentityHashMap because the latter is
Expand All @@ -49,35 +61,47 @@ abstract class ClassValue[T] protected () {
protected def computeValue(`type`: Class[_]): T

def get(`type`: Class[_]): T = {
if (useJSMap) {
mapGetOrElseUpdate(jsMap, `type`)(() => computeValue(`type`))
} else {
/* We first perform `get`, and if the result is null, we use
* `containsKey` to disambiguate a present null from an absent key.
* Since the purpose of ClassValue is to be used a cache indexed by Class
* values, the expected use case will have more hits than misses, and so
* this ordering should be faster on average than first performing `has`
* then `get`.
*/
javaMap.get(`type`) match {
case null =>
if (javaMap.containsKey(`type`)) {
null.asInstanceOf[T]
} else {
val newValue = computeValue(`type`)
javaMap.put(`type`, newValue)
newValue
}
case value =>
value
LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
getJavaMap(`type`)
} {
if (useJSMap) {
mapGetOrElseUpdate(jsMap, `type`)(() => computeValue(`type`))
} else {
getJavaMap(`type`)
}
}
}

private def getJavaMap(`type`: Class[_]): T = {
/* We first perform `get`, and if the result is null, we use
* `containsKey` to disambiguate a present null from an absent key.
* Since the purpose of ClassValue is to be used a cache indexed by Class
* values, the expected use case will have more hits than misses, and so
* this ordering should be faster on average than first performing `has`
* then `get`.
*/
javaMap.get(`type`) match {
case null =>
if (javaMap.containsKey(`type`)) {
null.asInstanceOf[T]
} else {
val newValue = computeValue(`type`)
javaMap.put(`type`, newValue)
newValue
}
case value =>
value
}
}

def remove(`type`: Class[_]): Unit = {
if (useJSMap)
jsMap.delete(`type`)
else
LinkingInfo.linkTimeIf[Unit](moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
javaMap.remove(`type`)
} {
if (useJSMap)
jsMap.delete(`type`)
else
javaMap.remove(`type`)
}
}
}
9 changes: 5 additions & 4 deletions javalib/src/main/scala/java/lang/Double.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import java.util.regex.RegExpImpl

import scala.scalajs.js
import scala.scalajs.LinkingInfo
import scala.scalajs.LinkingInfo.linkTimeIf
import scala.scalajs.LinkingInfo.{linkTimeIf, moduleKind}
import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent}

import Utils._

Expand Down Expand Up @@ -127,7 +128,7 @@ object Double {
import RegExpImpl.impl
val groups = impl.exec(doubleStrPat, s)
if (impl.matches(groups)) {
linkTimeIf(LinkingInfo.targetPureWasm) {
linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
parseDoubleWasm(s, groups)
} {
js.Dynamic.global.parseFloat(impl.get(groups, 1)).asInstanceOf[scala.Double]
Expand Down Expand Up @@ -289,7 +290,7 @@ object Double {
@inline def nativeParseInt(s: String, radix: Int): scala.Double =
js.Dynamic.global.parseInt(s, radix).asInstanceOf[scala.Double]

val mantissa = linkTimeIf(LinkingInfo.targetPureWasm) {
val mantissa = linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
val mantissaLong = Long.parseUnsignedLong(truncatedMantissaStr, 16)
// convert unsigned long to double
(mantissaLong >>> 32).toDouble * (1L << 32) + (mantissaLong & 0xffffffffL).toDouble
Expand All @@ -298,7 +299,7 @@ object Double {
}
// Assert: mantissa != 0.0 && mantissa != scala.Double.PositiveInfinity

val binaryExp = linkTimeIf(LinkingInfo.targetPureWasm) {
val binaryExp = linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
if (binaryExpStr.length() > 11) {
if (binaryExpStr.charAt(0) == '-') Int.MinValue
else Int.MaxValue
Expand Down
6 changes: 4 additions & 2 deletions javalib/src/main/scala/java/lang/Float.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import java.lang.constant.{Constable, ConstantDesc}
import java.util.regex.{Matcher, Pattern, RegExpImpl}

import scala.scalajs.js
import scala.scalajs.LinkingInfo._
import scala.scalajs.LinkingInfo
import scala.scalajs.LinkingInfo.{ESVersion, esVersion, linkTimeIf, moduleKind}
import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent}

/* This is a hijacked class. Its instances are primitive numbers.
* Constructors are not emitted.
Expand Down Expand Up @@ -150,7 +152,7 @@ object Float {
private def parseFloatDecimal(fullNumberStr: String,
integralPartStr: String, fractionalPartStr: String,
exponentStr: String): scala.Float = {
linkTimeIf[scala.Float](targetPureWasm) {
linkTimeIf[scala.Float](moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
parseFloatDecimalWasm(fullNumberStr, integralPartStr, fractionalPartStr, exponentStr)
} {
parseFloatDecimalJS(fullNumberStr, integralPartStr, fractionalPartStr, exponentStr)
Expand Down
5 changes: 3 additions & 2 deletions javalib/src/main/scala/java/lang/Integer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import java.util.function._

import scala.scalajs.js
import scala.scalajs.LinkingInfo
import scala.scalajs.LinkingInfo.ESVersion
import scala.scalajs.LinkingInfo.{ESVersion, moduleKind}
import scala.scalajs.LinkingInfo.ModuleKind.{MinimalWasmModule, WasmComponent}

/* This is a hijacked class. Its instances are primitive numbers.
* Constructors are not emitted.
Expand Down Expand Up @@ -339,7 +340,7 @@ object Integer {
@inline def min(a: Int, b: Int): Int = Math.min(a, b)

@inline private[this] def toStringBase(i: scala.Int, base: scala.Int): String = {
LinkingInfo.linkTimeIf(LinkingInfo.targetPureWasm) {
LinkingInfo.linkTimeIf(moduleKind == MinimalWasmModule || moduleKind == WasmComponent) {
toStringWasmGenericImpl(i, base, false)
} {
import js.JSNumberOps.enableJSNumberOps
Expand Down
Loading
Loading