Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,30 @@ object Instructions:
stackargs = Seq(arrayRef),
resultType = S(I32Type)
)

/** Creates an `array.new_fixed` instruction. */
def new_fixed(arrayType: TypeIdx, items: Seq[Expr]): FoldedInstr = FoldedInstr(
mnemonic = "array.new_fixed",
instrargs = Seq(arrayType.toWat, doc"${items.length}"),
stackargs = items,
resultType = S(RefType(arrayType, nullable = false))
)

/** Creates an `array.get` instruction. */
def get(arrayType: TypeIdx, arrayRef: Expr, index: Expr, elemType: Type): FoldedInstr = FoldedInstr(
mnemonic = "array.get",
instrargs = Seq(arrayType.toWat),
stackargs = Seq(arrayRef, index),
resultType = S(elemType)
)

/** Creates an `array.set` instruction. */
def set(arrayType: TypeIdx, arrayRef: Expr, index: Expr, value: Expr): FoldedInstr = FoldedInstr(
mnemonic = "array.set",
instrargs = Seq(arrayType.toWat),
stackargs = Seq(arrayRef, index, value),
resultType = N
)
end array

object ref:
Expand Down Expand Up @@ -336,6 +360,14 @@ object Instructions:
resultType = S(RefType(ty, nullable = false))
)

/** Creates a `struct.new` instruction. */
def new_(ty: TypeIdx, values: Seq[Expr]): FoldedInstr = FoldedInstr(
mnemonic = "struct.new",
instrargs = Seq(ty.toWat),
stackargs = values,
resultType = S(RefType(ty, nullable = false))
)

/** Creates a `struct.set` instruction. */
def set(index: FieldIdx, ref: Expr, value: FoldedInstr): FoldedInstr = FoldedInstr(
mnemonic = "struct.set",
Expand Down
15 changes: 14 additions & 1 deletion hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/Wasm.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,21 @@ case class StructType(
def toWat: Document =
doc"(struct${fieldSeq.map(_.toWat).mkDocument(doc" ").surroundUnlessEmpty(doc" ")})"

/** A type representing an array type. */
case class ArrayType(
elemType: Type,
mutable: Bool,
parents: Seq[TypeIdx] = Seq.empty,
isSubtype: Bool = false
) extends ToWat:
private def elemDoc: Document =
if mutable then doc"(mut ${elemType.toWat})" else elemType.toWat

def toWat: Document =
doc"(array ${elemDoc})"

/** A composite type. */
type CompType = StructType | FunctionType
type CompType = StructType | FunctionType | ArrayType

type AbsHeapType =
HeapType.Func.type
Expand Down
244 changes: 227 additions & 17 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/wasm/text/WatBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class WatBuilder(using TraceLogger, State) extends CodeBuilder:

private val baseObjectSym: BlockMemberSymbol = BlockMemberSymbol("Object", Nil)
private val tagFieldSym: TermSymbol = TermSymbol(syntax.MutVal, owner = N, Ident("$tag"))
private case class TupleArrayInfo(arrayType: TypeIdx, elemType: Type, mutable: Bool)
private var mutTupleArrayInfo: Opt[TupleArrayInfo] = N
private var tupleArrayInfo: Opt[TupleArrayInfo] = N
private case class ActiveLabel(sym: Local, breakLabel: Str, continueLabel: Opt[Str])
private var activeLabels: List[ActiveLabel] = Nil

private def baseObjectTypeIdx(using Ctx): TypeIdx =
ctx.getType_!(baseObjectSym)
Expand All @@ -49,6 +54,83 @@ class WatBuilder(using TraceLogger, State) extends CodeBuilder:
private def baseObjectRefType(nullable: Bool)(using Ctx): RefType =
RefType(baseObjectTypeIdx, nullable = nullable)

private def tupleArray(mut: Bool)(using Ctx): TupleArrayInfo =
val cached = if mut then mutTupleArrayInfo else tupleArrayInfo
cached match
case S(info) => info
case N =>
val suffix = if mut then "Mut" else ""
val sym = BlockMemberSymbol(s"TupleArray$suffix", Nil)
val arrayType = ctx.addType(
sym = S(sym),
TypeInfo(
sym = sym,
compType = ArrayType(
elemType = RefType.anyref,
mutable = mut
)
)
)
val info = TupleArrayInfo(arrayType, RefType.anyref, mutable = mut)
if mut then mutTupleArrayInfo = S(info) else tupleArrayInfo = S(info)
info

private def tupleArrayGet(
tupleExpr: Expr,
idxBuilder: Expr => Expr
)(using Ctx, Raise, Scope): Expr =
val mutInfo = tupleArray(true)
val immInfo = tupleArray(false)
val tupleIsMutable = ref.test(tupleExpr, RefType(mutInfo.arrayType, nullable = true))
val mutableBranch =
val tupleRef = ref.cast(tupleExpr, RefType(mutInfo.arrayType, nullable = false))
array.get(mutInfo.arrayType, tupleRef, idxBuilder(tupleRef), mutInfo.elemType)
val immutableBranch =
val tupleRef = ref.cast(tupleExpr, RefType(immInfo.arrayType, nullable = false))
array.get(immInfo.arrayType, tupleRef, idxBuilder(tupleRef), immInfo.elemType)
Instructions.`if`(
condition = tupleIsMutable,
ifTrue = mutableBranch,
ifFalse = S(immutableBranch),
resultTypes = Seq(Result(mutInfo.elemType.asValType_!))
)

private def tupleIndexBuilder(
fld: Path,
loc: Opt[Loc],
errCtx: Str,
extra: => Str
)(using Ctx, Raise, Scope): Expr => Expr =
fld match
case Value.Lit(IntLit(value)) if value.isValidInt =>
val idx = value.toInt
tupleRef =>
if idx >= 0 then i32.const(idx)
else i32.add(array.len(tupleRef), i32.const(idx))
case _ =>
val rawIdx = result(fld)
val idxI32 = rawIdx.resultType match
case S(I32Type) => rawIdx
case S(RefType(HeapType.I31, _)) => i31.get(rawIdx, signed = true)
case S(RefType(HeapType.Any, _)) =>
val casted = ref.cast(rawIdx, RefType.i31ref)
i31.get(casted, signed = true)
case ty =>
val err = errExpr(
Ls(
msg"$errCtx expects an integer index but found ${ty.fold("(none)")(_.toWat.mkString())}" -> loc
),
extraInfo = S(extra)
)
return (_: Expr) => err
tupleRef =>
Instructions.`if`(
condition = i32.lt_s(idxI32, i32.const(0)),
ifTrue = i32.add(idxI32, array.len(tupleRef)),
ifFalse = S(idxI32),
resultTypes = Seq(Result(I32Type))
)

/**
* Raises a [[WarningReport]] with the given `warnMsgs` and `extraInfo`, and emits an
* `unreachable` instruction.
Expand Down Expand Up @@ -237,25 +319,54 @@ class WatBuilder(using TraceLogger, State) extends CodeBuilder:

case sel @ Select(qual, id) =>
val qualRes = result(qual)
val selSym = sel.symbol getOrElse:
lastWords(s"Symbol for Select(...) expression must be resolved")
val selTrmSym = selSym match
case termSym: TermSymbol => termSym
case sym => lastWords(
s"Expected resolved Select(...) expression to be a TermSymbol, but got $sym (${sym.getClass.getName})"
sel.symbol match
case S(selSym: TermSymbol) =>
val selOwner = selSym.owner getOrElse:
lastWords(s"Expected resolved Select(...) expression `$selSym` to have an owner")
val selCls = selOwner.asBlkMember getOrElse:
lastWords(
s"Expected resolved class for Select(...) expression to be a BlockMemberSymbol, but got $selOwner (${selOwner.getClass.getName})"
)
val fieldidx = fieldSelect(selCls, selSym)
struct.get(
fieldidx,
ref = ref.cast(qualRes, RefType(ctx.getType_!(selCls), nullable = false)),
ty = RefType.anyref
)
val selOwner = selTrmSym.owner getOrElse:
lastWords(s"Expected resolved Select(...) expression `$selTrmSym` to have an owner")
val selCls = selOwner.asBlkMember getOrElse:
lastWords(
s"Expected resolved class for Select(...) expression to be a BlockMemberSymbol, but got $selOwner (${selOwner.getClass.getName})"
case S(otherSym) =>
lastWords(
s"Expected resolved Select(...) expression to be a TermSymbol, but got $otherSym (${otherSym.getClass.getName})"
)
case N =>
id.name.toIntOption.filter(_ >= 0)
.map: idx =>
tupleArrayGet(
tupleExpr = qualRes,
idxBuilder = _ => i32.const(idx)
)
.getOrElse:
errExpr(
Ls(
msg"WatBuilder::result for field selection without a resolved symbol is not implemented (field `${id.name}`)" -> sel.toLoc
),
extraInfo = S(sel.toString)
)

case dyn @ DynSelect(qual, fld, arrayIdx) =>
val qualRes = result(qual)
if arrayIdx then
val idxBuilder = tupleIndexBuilder(
fld = fld,
loc = fld.toLoc,
errCtx = "WatBuilder::result for array-style dynamic selections",
extra = dyn.toString
)
tupleArrayGet(qualRes, idxBuilder)
else
errExpr(
Ls(msg"WatBuilder::result for dynamic field selections is not implemented yet" -> dyn.toLoc),
extraInfo = S(dyn.toString)
)
val fieldidx = fieldSelect(selCls, selSym)
struct.get(
fieldidx,
ref = ref.cast(qualRes, RefType(ctx.getType_!(selCls), nullable = false)),
ty = RefType.anyref
)

case Instantiate(_, cls, as) =>
val ctorClsSymOpt = cls match
Expand Down Expand Up @@ -287,6 +398,11 @@ class WatBuilder(using TraceLogger, State) extends CodeBuilder:
val objType = ctx.getFuncInfo_!(ctorFuncIdx).body.resultType_!
call(funcidx = ctorFuncIdx, as.map(argument), Seq(Result(objType.asValType_!)))

case Tuple(mut, elems) =>
val tupleInfo = tupleArray(mut)
val tupleValues = elems.map(argument)
array.new_fixed(tupleInfo.arrayType, tupleValues)

case r =>
errExpr(
Ls(msg"WatBackend::result for expression not implemented yet" -> r.toLoc),
Expand Down Expand Up @@ -439,6 +555,74 @@ class WatBuilder(using TraceLogger, State) extends CodeBuilder:
Result(if ty is UnreachableType then RefType.anyref else ty.asValType_!)
)

case assign @ AssignField(lhs, nme, rhs, rst) =>
val lhsExpr = result(lhs)
val rhsExpr = result(rhs)
val assignInstr = assign.symbol match
case S(selSym: TermSymbol) =>
val selOwner = selSym.owner getOrElse
lastWords(s"Expected resolved AssignField(...) expression `$selSym` to have an owner")
val selCls = selOwner.asBlkMember getOrElse
lastWords(
s"Expected resolved class for AssignField(...) expression to be a BlockMemberSymbol, but got $selOwner (${selOwner.getClass.getName})"
)
val fieldidx = fieldSelect(selCls, selSym)
val objRef = ref.cast(lhsExpr, RefType(ctx.getType_!(selCls), nullable = false))
struct.set(fieldidx, objRef, rhsExpr)
case S(otherSym) =>
lastWords(
s"Expected resolved AssignField(...) expression to be a TermSymbol, but got $otherSym (${otherSym.getClass.getName})"
)
case N =>
nme.name.toIntOption.filter(_ >= 0).map: idx =>
val tupleInfo = tupleArray(mut = true)
val tupleRef = ref.cast(lhsExpr, RefType(tupleInfo.arrayType, nullable = false))
array.set(tupleInfo.arrayType, tupleRef, i32.const(idx), rhsExpr)
.getOrElse:
errExpr(
Ls(
msg"WatBuilder::returningTerm for AssignField(...) without a resolved symbol is not implemented (field `${nme.name}`)" -> nme.toLoc
),
extraInfo = S(assign.toString)
)

val rstBlk = returningTerm(rst)
Instructions.block(
label = N,
children = Seq(assignInstr, rstBlk),
resultTypes = rstBlk.resultTypes.map: ty =>
Result(if ty is UnreachableType then RefType.anyref else ty.asValType_!)
)

case assign @ AssignDynField(lhs, fld, arrayIdx, rhs, rst) =>
val lhsExpr = result(lhs)
val rhsExpr = result(rhs)
val assignInstr =
if arrayIdx then
val tupleInfo = tupleArray(mut = true)
val tupleRef = ref.cast(lhsExpr, RefType(tupleInfo.arrayType, nullable = false))
val idxBuilder = tupleIndexBuilder(
fld = fld,
loc = fld.toLoc,
errCtx = "WatBuilder::returningTerm for AssignDynField(...)",
extra = assign.toString
)
val idxExpr = idxBuilder(tupleRef)
array.set(tupleInfo.arrayType, tupleRef, idxExpr, rhsExpr)
else
errExpr(
Ls(msg"WatBuilder::returningTerm for AssignDynField(...) where `arrayIdx = false` is not implemented yet" -> lhs.toLoc),
extraInfo = S(assign.toString)
)

val rstBlk = returningTerm(rst)
Instructions.block(
label = N,
children = Seq(assignInstr, rstBlk),
resultTypes = rstBlk.resultTypes.map: ty =>
Result(if ty is UnreachableType then RefType.anyref else ty.asValType_!)
)

case Define(defn, rst) =>
def mkThis(sym: InnerSymbol): Expr = result(Value.This(sym))
defn match
Expand Down Expand Up @@ -787,6 +971,32 @@ class WatBuilder(using TraceLogger, State) extends CodeBuilder:
ifFalse = N,
resultTypes = Seq.empty
))
case Case.Tup(len, inf) =>
val arrayRefType = RefType(HeapType.Array, nullable = true)
val isArrayTest = ref.test(getScrutExpr, arrayRefType)

// Length check
val scrutArray = ref.cast(getScrutExpr, arrayRefType)
val arrayLength = array.len(scrutArray)
val lengthTest = if inf then
i32.ge_u(arrayLength, i32.const(len))
else
i32.eq(arrayLength, i32.const(len))

val testExpr = i32.and(isArrayTest, lengthTest)
val bodyExpr = returningTerm(body)
val armLabelSym = TempSymbol(N, "arm")
val armLabel = scope.allocateName(armLabelSym)
S(Instructions.`if`(
condition = testExpr,
ifTrue = Instructions.block(
label = S(armLabel),
children = Seq(bodyExpr, br(matchLabel)),
resultTypes = Seq.empty
),
ifFalse = N,
resultTypes = Seq.empty
))
case _ =>
break(errExpr(
Ls(
Expand Down
Loading
Loading