Skip to content
This repository was archived by the owner on Jul 12, 2024. It is now read-only.

Commit 03b1755

Browse files
authored
Merge pull request #21 from sjrd/static-methods
Add support for static methods.
2 parents 2a3a7e0 + 4a318ee commit 03b1755

File tree

10 files changed

+127
-97
lines changed

10 files changed

+127
-97
lines changed

cli/src/main/scala/TestSuites.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ object TestSuites {
1414
TestSuite("testsuite.core.HijackedClassesDispatchTest"),
1515
TestSuite("testsuite.core.HijackedClassesMonoTest"),
1616
TestSuite("testsuite.core.HijackedClassesUpcastTest"),
17+
TestSuite("testsuite.core.StaticMethodTest"),
1718
TestSuite("testsuite.core.ToStringTest")
1819
)
1920
}

run.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { load } from "./loader.mjs";
22

33
const { test } = await load("./target/output.wasm");
4-
const o = test();
4+
const o = test(7);
55
console.log(o);

sample/src/main/scala/Sample.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@ import scala.scalajs.js.annotation._
1212
//
1313
object Main {
1414
@JSExportTopLevel("test")
15-
def test() = {
16-
val i = 4
15+
def test(i: Int): Boolean = {
1716
val loopFib = fib(new LoopFib {}, i)
1817
val recFib = fib(new RecFib {}, i)
1918
val tailrecFib = fib(new TailRecFib {}, i)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package testsuite.core
2+
3+
import testsuite.Assert.ok
4+
5+
object StaticMethodTest {
6+
def main(): Unit = {
7+
ok(java.lang.Integer.sum(5, 65) == 70)
8+
ok(java.lang.Integer.reverseBytes(0x01020304) == 0x04030201)
9+
}
10+
}

wasm/src/main/scala/ir2wasm/HelperFunctions.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,11 @@ object HelperFunctions {
327327
// TODO: "isInstance", "isAssignableFrom", "checkCast", "newArrayOfThisClass"
328328

329329
// Call java.lang.Class::<init>(dataObject)
330-
instrs += CALL(FuncIdx(WasmFunctionName(IRNames.ClassClass, SpecialNames.ClassCtor)))
330+
instrs += CALL(FuncIdx(WasmFunctionName(
331+
IRTrees.MemberNamespace.Constructor,
332+
IRNames.ClassClass,
333+
SpecialNames.ClassCtor
334+
)))
331335

332336
// typeData.classOf := classInstance
333337
instrs += LOCAL_GET(typeDataParam)

wasm/src/main/scala/ir2wasm/Preprocessor.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ object Preprocessor {
2222
}
2323

2424
private def preprocess(clazz: LinkedClass)(implicit ctx: WasmContext): Unit = {
25-
val infos = clazz.methods.filterNot(_.flags.namespace.isConstructor).map { method =>
26-
makeWasmFunctionInfo(clazz, method)
27-
}
25+
val infos = clazz.methods
26+
.filter(_.flags.namespace == IRTrees.MemberNamespace.Public)
27+
.map(method => makeWasmFunctionInfo(clazz, method))
2828

2929
ctx.putClassInfo(
3030
clazz.name.name,
@@ -47,7 +47,7 @@ object Preprocessor {
4747
method: IRTrees.MethodDef
4848
): WasmFunctionInfo = {
4949
WasmFunctionInfo(
50-
Names.WasmFunctionName(clazz.name.name, method.name.name),
50+
Names.WasmFunctionName(method.flags.namespace, clazz.name.name, method.name.name),
5151
method.args.map(_.ptpe),
5252
method.resultType,
5353
isAbstract = method.body.isEmpty

wasm/src/main/scala/ir2wasm/WasmBuilder.scala

Lines changed: 30 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,13 @@ class WasmBuilder {
230230
.getOrElse(throw new Error(s"Module class should have a constructor, ${clazz.name}"))
231231
val typeName = WasmTypeName.WasmStructTypeName(clazz.name.name)
232232
val globalInstanceName = WasmGlobalName.WasmModuleInstanceName.fromIR(clazz.name.name)
233-
val ctorName = WasmFunctionName(clazz.name.name, ctor.name.name)
233+
234+
val ctorName = WasmFunctionName(
235+
ctor.flags.namespace,
236+
clazz.name.name,
237+
ctor.name.name
238+
)
239+
234240
val body = List(
235241
// global.get $module_name
236242
// ref.if_null
@@ -355,7 +361,7 @@ class WasmBuilder {
355361
Names.WasmTypeName.WasmITableTypeName(className),
356362
classInfo.methods.map { m =>
357363
WasmStructField(
358-
Names.WasmFieldName(m.name.methodName),
364+
Names.WasmFieldName(m.name.simpleName),
359365
WasmRefNullType(WasmHeapType.Func(m.toWasmFunctionType().name)),
360366
isMutable = false
361367
)
@@ -397,97 +403,58 @@ class WasmBuilder {
397403
exportDef: IRTrees.TopLevelMethodExportDef
398404
)(implicit ctx: WasmContext): Unit = {
399405
val method = exportDef.methodDef
400-
val methodName = method.name match {
401-
case lit: IRTrees.StringLiteral => lit
402-
case _ => ???
403-
}
404-
405-
// hack
406-
// export top[moduleID="main"] static def "foo"(arg: any): any = {
407-
// val prep0: int = arg.asInstanceOf[int];
408-
// mod:sample.Main$.foo;I;I(prep0)
409-
// }
410-
// ->
411-
// export top[moduleID="main"] static def "foo"(arg: int): int = {
412-
// val prep0: int = arg;
413-
// mod:sample.Main$.foo;I;I(arg)
414-
// }
415-
val paramTypeMap = mutable.Map[IRTrees.LocalIdent, IRTypes.Type]()
416-
val nameMap = mutable.Map[IRTrees.LocalIdent, IRTrees.LocalIdent]()
417-
val resultType: IRTypes.Type = method.body.tpe
418-
def collectMapping(t: IRTrees.Tree): Unit = {
419-
t match {
420-
case IRTrees.Block(stats) => stats.foreach(collectMapping)
421-
case IRTrees.VarDef(lhs, _, _, _, IRTrees.AsInstanceOf(IRTrees.VarRef(ident), tpe)) =>
422-
paramTypeMap.update(ident, tpe) // arg -> int
423-
nameMap.update(lhs, ident) // prep0 -> arg
424-
case _ =>
425-
}
426-
}
427-
def mutateTree(t: IRTrees.Tree): IRTrees.Tree = {
428-
t match {
429-
case b: IRTrees.Block => IRTrees.Block(b.stats.map(mutateTree))(b.pos)
430-
case vdef @ IRTrees.VarDef(_, _, _, _, IRTrees.AsInstanceOf(vref, tpe)) =>
431-
vdef.copy(rhs = vref)(vdef.pos)
432-
case app: IRTrees.Apply =>
433-
app.copy(args = app.args.map(a => mutateTree(a)))(app.tpe)(app.pos)
434-
case vref: IRTrees.VarRef =>
435-
val newName = nameMap.getOrElse(vref.ident, throw new Error("Invalid name"))
436-
vref.copy(ident = newName)(vref.tpe)(vref.pos)
437-
case t => t
438-
}
439-
}
406+
val exportedName = exportDef.topLevelExportName
440407

441-
collectMapping(method.body)
442-
val newBody = mutateTree(method.body)
443-
val newParams = method.args.map { arg =>
444-
paramTypeMap.get(arg.name) match {
445-
case None => arg
446-
case Some(newTpe) => arg.copy(ptpe = newTpe)(arg.pos)
447-
}
408+
if (method.restParam.isDefined) {
409+
throw new UnsupportedOperationException(
410+
s"Top-level export with ...rest param is unsupported at ${method.pos}: $method"
411+
)
448412
}
449413

450414
implicit val fctx = WasmFunctionContext(
451415
enclosingClassName = None,
452-
Names.WasmFunctionName(methodName),
416+
Names.WasmFunctionName.forExport(exportedName),
453417
receiverTyp = None,
454-
newParams,
455-
resultType
418+
method.args,
419+
IRTypes.AnyType
456420
)
457421

458-
WasmExpressionBuilder.generateIRBody(newBody, resultType)
422+
WasmExpressionBuilder.generateIRBody(method.body, IRTypes.AnyType)
459423

460424
val func = fctx.buildAndAddToContext()
461425

462-
val exprt = new WasmExport.Function(
463-
methodName.value,
464-
func
465-
)
426+
val exprt = new WasmExport.Function(exportedName, func)
466427
ctx.addExport(exprt)
467428
}
468429

469430
private def genFunction(
470431
clazz: LinkedClass,
471432
method: IRTrees.MethodDef
472433
)(implicit ctx: WasmContext): WasmFunction = {
473-
val functionName = Names.WasmFunctionName(clazz.name.name, method.name.name)
434+
val functionName = Names.WasmFunctionName(
435+
method.flags.namespace,
436+
clazz.name.name,
437+
method.name.name
438+
)
474439

475440
// Receiver type for non-constructor methods needs to be `(ref any)` because params are invariant
476441
// Otherwise, vtable can't be a subtype of the supertype's subtype
477442
// Constructor can use the exact type because it won't be registered to vtables.
478443
val receiverTyp =
479-
if (clazz.kind == ClassKind.HijackedClass)
480-
transformType(IRTypes.BoxedClassToPrimType(clazz.name.name))
444+
if (method.flags.namespace.isStatic)
445+
None
446+
else if (clazz.kind == ClassKind.HijackedClass)
447+
Some(transformType(IRTypes.BoxedClassToPrimType(clazz.name.name)))
481448
else if (method.flags.namespace.isConstructor)
482-
WasmRefNullType(WasmHeapType.Type(WasmTypeName.WasmStructTypeName(clazz.name.name)))
449+
Some(WasmRefNullType(WasmHeapType.Type(WasmTypeName.WasmStructTypeName(clazz.name.name))))
483450
else
484-
WasmRefType.any
451+
Some(WasmRefType.any)
485452

486453
// Prepare for function context, set receiver and parameters
487454
implicit val fctx = WasmFunctionContext(
488455
Some(clazz.className),
489456
functionName,
490-
Some(receiverTyp),
457+
receiverTyp,
491458
method.args,
492459
method.resultType
493460
)

wasm/src/main/scala/ir2wasm/WasmExpressionBuilder.scala

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ private class WasmExpressionBuilder private (
108108
case t: IRTrees.This => genThis(t)
109109
case t: IRTrees.ApplyStatically => genApplyStatically(t)
110110
case t: IRTrees.Apply => genApply(t)
111+
case t: IRTrees.ApplyStatic => genApplyStatic(t)
111112
case t: IRTrees.IsInstanceOf => genIsInstanceOf(t)
112113
case t: IRTrees.AsInstanceOf => genAsInstanceOf(t)
113114
case t: IRTrees.GetClass => genGetClass(t)
@@ -337,7 +338,11 @@ private class WasmExpressionBuilder private (
337338
* After this code gen, the stack contains the result.
338339
*/
339340
def genHijackedClassCall(hijackedClass: IRNames.ClassName): Unit = {
340-
val funcName = Names.WasmFunctionName(hijackedClass, t.method.name)
341+
val funcName = Names.WasmFunctionName(
342+
IRTrees.MemberNamespace.Public,
343+
hijackedClass,
344+
t.method.name
345+
)
341346
instrs += CALL(FuncIdx(funcName))
342347
}
343348

@@ -522,7 +527,11 @@ private class WasmExpressionBuilder private (
522527

523528
val (methodIdx, info) = ctx
524529
.calculateVtableType(receiverClassName)
525-
.resolveWithIdx(WasmFunctionName(receiverClassName, methodName))
530+
.resolveWithIdx(WasmFunctionName(
531+
IRTrees.MemberNamespace.Public,
532+
receiverClassName,
533+
methodName
534+
))
526535

527536
// // push args to the stacks
528537
// local.get $this ;; for accessing funcref
@@ -582,14 +591,25 @@ private class WasmExpressionBuilder private (
582591
}
583592

584593
genArgs(t.args, t.method.name)
585-
val funcName = Names.WasmFunctionName(t.className, t.method.name)
594+
val namespace = IRTrees.MemberNamespace.forNonStaticCall(t.flags)
595+
val funcName = Names.WasmFunctionName(namespace, t.className, t.method.name)
586596
instrs += CALL(FuncIdx(funcName))
587597
if (t.tpe == IRTypes.NothingType)
588598
instrs += UNREACHABLE
589599
t.tpe
590600
}
591601
}
592602

603+
private def genApplyStatic(tree: IRTrees.ApplyStatic): IRTypes.Type = {
604+
genArgs(tree.args, tree.method.name)
605+
val namespace = IRTrees.MemberNamespace.forStaticCall(tree.flags)
606+
val funcName = Names.WasmFunctionName(namespace, tree.className, tree.method.name)
607+
instrs += CALL(FuncIdx(funcName))
608+
if (tree.tpe == IRTypes.NothingType)
609+
instrs += UNREACHABLE
610+
tree.tpe
611+
}
612+
593613
private def genArgs(args: List[IRTrees.Tree], methodName: IRNames.MethodName): Unit = {
594614
for ((arg, paramTypeRef) <- args.lazyZip(methodName.paramTypeRefs)) {
595615
val paramType = ctx.inferTypeFromTypeRef(paramTypeRef)
@@ -1328,7 +1348,11 @@ private class WasmExpressionBuilder private (
13281348
instrs += CALL(FuncIdx(WasmFunctionName.newDefault(n.className)))
13291349
instrs += LOCAL_TEE(LocalIdx(localInstance.name))
13301350
genArgs(n.args, n.ctor.name)
1331-
instrs += CALL(FuncIdx(WasmFunctionName(n.className, n.ctor.name)))
1351+
instrs += CALL(FuncIdx(WasmFunctionName(
1352+
IRTrees.MemberNamespace.Constructor,
1353+
n.className,
1354+
n.ctor.name
1355+
)))
13321356
instrs += LOCAL_GET(LocalIdx(localInstance.name))
13331357
n.tpe
13341358
}

wasm/src/main/scala/wasm4s/Names.scala

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,21 +97,46 @@ object Names {
9797
// }
9898

9999
case class WasmFunctionName private (
100-
val className: String,
101-
val methodName: String
102-
) extends WasmName(s"$className#$methodName")
100+
val namespace: String,
101+
val simpleName: String
102+
) extends WasmName(namespace + "#" + simpleName)
103+
103104
object WasmFunctionName {
104-
def apply(clazz: IRNames.ClassName, method: IRNames.MethodName): WasmFunctionName =
105-
new WasmFunctionName(clazz.nameString, method.nameString)
106-
def apply(lit: IRTrees.StringLiteral): WasmFunctionName = new WasmFunctionName(lit.value, "")
105+
def apply(
106+
namespace: IRTrees.MemberNamespace,
107+
clazz: IRNames.ClassName,
108+
method: IRNames.MethodName
109+
): WasmFunctionName = {
110+
new WasmFunctionName(
111+
namespaceString(namespace) + "#" + clazz.nameString,
112+
method.nameString
113+
)
114+
}
115+
116+
private def namespaceString(namespace: IRTrees.MemberNamespace): String = {
117+
import IRTrees.MemberNamespace._
118+
119+
// These strings are the same ones that the JS back-end uses
120+
namespace match {
121+
case Public => "f"
122+
case Private => "p"
123+
case PublicStatic => "s"
124+
case PrivateStatic => "ps"
125+
case Constructor => "ct"
126+
case StaticConstructor => "sct"
127+
}
128+
}
129+
130+
def forExport(exportedName: String): WasmFunctionName =
131+
new WasmFunctionName("export", exportedName)
107132

108133
// Adding prefix __ to avoid name clashes with user code.
109134
// It should be safe not to add prefix to the method name
110135
// since loadModule is a static method and it's not registered in the vtable.
111136
def loadModule(clazz: IRNames.ClassName): WasmFunctionName =
112-
new WasmFunctionName(s"__${clazz.nameString}", "loadModule")
137+
new WasmFunctionName("loadModule", clazz.nameString)
113138
def newDefault(clazz: IRNames.ClassName): WasmFunctionName =
114-
new WasmFunctionName(s"__${clazz.nameString}", "newDefault")
139+
new WasmFunctionName("new", clazz.nameString)
115140

116141
val start = new WasmFunctionName("start", "start")
117142

0 commit comments

Comments
 (0)