elysium/backend_wat.go

968 lines
25 KiB
Go
Raw Normal View History

2024-03-18 21:14:28 +01:00
package main
import (
"errors"
"fmt"
2024-03-18 21:14:28 +01:00
"strconv"
2024-03-21 21:00:31 +01:00
"unicode"
2024-03-18 21:14:28 +01:00
)
2024-10-31 20:50:09 +01:00
const COMPILE_OPTION_NO_BOUNDS_CHECK = "no_bounds_check"
type Compiler struct {
2024-10-31 20:50:09 +01:00
Files []*ParsedFile
Wasm64 bool
CompileOptions map[string]string
2024-04-02 19:43:05 +02:00
CurrentBlock *Block
CurrentFunction *ParsedFunction
}
2024-03-19 08:00:49 +01:00
func getPrimitiveWATType(primitive PrimitiveType) string {
2024-03-18 21:14:28 +01:00
switch primitive {
case Primitive_I8, Primitive_I16, Primitive_I32, Primitive_U8, Primitive_U16, Primitive_U32:
return "i32"
case Primitive_I64, Primitive_U64:
return "i64"
case Primitive_F32:
return "f32"
case Primitive_F64:
return "f64"
case Primitive_Bool:
return "i32"
}
panic(fmt.Sprintf("unhandled type in getPrimitiveWATType(): %s", primitive))
2024-03-18 21:14:28 +01:00
}
2024-03-21 21:00:31 +01:00
func safeASCIIIdentifier(identifier string) string {
ascii := ""
for _, rune := range identifier {
if rune < unicode.MaxASCII && (unicode.IsLetter(rune) || unicode.IsDigit(rune)) {
ascii += string(rune)
continue
}
ascii += "$" + strconv.Itoa(int(rune))
}
return ascii
}
2024-11-16 22:41:26 +01:00
func getTypeCast(primitive PrimitiveType) Code {
2024-03-18 21:14:28 +01:00
switch primitive {
case Primitive_I8:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.extend8_s")
2024-03-18 21:14:28 +01:00
case Primitive_U8:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const 255", "i32.and")
2024-03-18 21:14:28 +01:00
case Primitive_I16:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.extend16_s")
2024-03-18 21:14:28 +01:00
case Primitive_U16:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const 65535", "i32.and")
2024-03-18 21:14:28 +01:00
case Primitive_Bool:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const 0", "i32.ne")
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
return emptyCode()
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
func pushConstantNumberWAT(primitive PrimitiveType, value any) Code {
2024-03-18 21:14:28 +01:00
switch primitive {
case Primitive_I8, Primitive_I16, Primitive_I32:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const " + strconv.FormatInt(value.(int64), 10))
2024-03-18 21:14:28 +01:00
case Primitive_U8, Primitive_U16, Primitive_U32:
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const " + strconv.FormatUint(value.(uint64), 10))
2024-03-18 21:14:28 +01:00
case Primitive_I64:
2024-11-16 22:41:26 +01:00
return ofInstruction("i64.const " + strconv.FormatInt(value.(int64), 10))
2024-03-18 21:14:28 +01:00
case Primitive_U64:
2024-11-16 22:41:26 +01:00
return ofInstruction("i64.const " + strconv.FormatUint(value.(uint64), 10))
2024-03-18 21:14:28 +01:00
case Primitive_F32:
2024-11-16 22:41:26 +01:00
return ofInstruction("f32.const " + strconv.FormatFloat(value.(float64), 'f', -1, 32))
2024-03-18 21:14:28 +01:00
case Primitive_F64:
2024-11-16 22:41:26 +01:00
return ofInstruction("f64.const " + strconv.FormatFloat(value.(float64), 'f', -1, 64))
2024-03-18 21:14:28 +01:00
}
panic(fmt.Sprintf("invalid type passed to pushConstantNumberWAT(): %s", primitive))
2024-03-18 21:14:28 +01:00
}
2024-04-02 19:43:05 +02:00
func (c *Compiler) getAddressWATType() string {
if c.Wasm64 {
return "i64"
} else {
return "i32"
}
}
func (c *Compiler) getEffectiveAddressType() PrimitiveType {
if c.Wasm64 {
return Primitive_U64
} else {
return Primitive_U32
}
}
func (c *Compiler) getWATType(t Type) string {
switch t.Type {
case Type_Primitive:
return getPrimitiveWATType(t.Value.(PrimitiveType))
case Type_Named, Type_Array:
2024-04-02 19:43:05 +02:00
return c.getAddressWATType()
case Type_Tuple:
panic(fmt.Sprintf("tuple type passed to getWATType(): %s", t))
}
panic(fmt.Sprintf("type not implemented in getWATType(): %s", t))
}
2024-10-27 16:21:46 +01:00
func (c *Compiler) getPrimitiveTypeSizeBytes(primitive PrimitiveType) int {
switch primitive {
case Primitive_I8, Primitive_U8, Primitive_Bool:
return 1
case Primitive_I16, Primitive_U16:
return 2
case Primitive_I32, Primitive_U32, Primitive_F32:
return 4
case Primitive_I64, Primitive_U64, Primitive_F64:
return 8
}
panic(fmt.Sprintf("unhandled type in getPrimitiveTypeSizeBytes(): %s", primitive))
}
func (c *Compiler) getTypeSizeBytes(t Type) int {
switch t.Type {
case Type_Primitive:
return c.getPrimitiveTypeSizeBytes(t.Value.(PrimitiveType))
case Type_Named, Type_Array:
return c.getPrimitiveTypeSizeBytes(Primitive_U64)
case Type_Tuple:
panic(fmt.Sprintf("tuple type passed to getTypeSizeBytes(): %s", t))
}
panic(fmt.Sprintf("unhandled type in getTypeSizeBytes(): %s", t))
}
2024-11-16 22:41:26 +01:00
func castPrimitiveWAT(from PrimitiveType, to PrimitiveType) (Code, error) {
2024-03-18 21:14:28 +01:00
if from == to {
2024-11-16 22:41:26 +01:00
return emptyCode(), nil
2024-03-18 21:14:28 +01:00
}
if from == Primitive_Bool || to == Primitive_Bool {
2024-11-16 22:41:26 +01:00
return emptyCode(), errors.New("cannot upcast from or to bool")
2024-03-18 21:14:28 +01:00
}
2024-03-19 08:00:49 +01:00
fromFloat := isFloatingPoint(from)
toFloat := isFloatingPoint(to)
if fromFloat && toFloat {
if to == Primitive_F32 {
2024-11-16 22:41:26 +01:00
return ofInstruction("f32.demote_f64"), nil
2024-03-19 08:00:49 +01:00
} else {
2024-11-16 22:41:26 +01:00
return ofInstruction("f64.promote_f32"), nil
2024-03-19 08:00:49 +01:00
}
2024-03-18 21:14:28 +01:00
}
2024-03-19 08:00:49 +01:00
if toFloat {
2024-11-16 22:41:26 +01:00
var suffix string
2024-03-19 08:00:49 +01:00
if isUnsignedInt(to) {
suffix = "u"
} else {
suffix = "s"
}
2024-11-16 22:41:26 +01:00
return ofInstruction(getPrimitiveWATType(to) + ".convert_" + getPrimitiveWATType(from) + "_" + suffix), nil
2024-03-18 21:14:28 +01:00
}
2024-03-19 08:00:49 +01:00
if fromFloat {
2024-11-16 22:41:26 +01:00
var suffix string
2024-03-19 08:00:49 +01:00
if isUnsignedInt(to) {
suffix = "u"
} else {
suffix = "s"
}
2024-11-16 22:41:26 +01:00
return ofInstruction(getPrimitiveWATType(to) + ".trunc_" + getPrimitiveWATType(from) + "_" + suffix), nil
2024-03-18 21:14:28 +01:00
}
2024-03-19 08:00:49 +01:00
if getBits(from) == getBits(to) {
2024-11-16 22:41:26 +01:00
return emptyCode(), nil
2024-03-19 08:00:49 +01:00
}
2024-03-18 21:14:28 +01:00
2024-03-19 08:00:49 +01:00
if getPrimitiveWATType(from) == getPrimitiveWATType(to) {
2024-03-19 10:54:21 +01:00
if getBits(to) < getBits(from) {
return getTypeCast(to), nil
}
2024-03-25 16:58:23 +01:00
2024-11-16 22:41:26 +01:00
return emptyCode(), nil
2024-03-18 21:14:28 +01:00
}
if getBits(from) < 64 && getBits(to) == 64 {
2024-11-16 22:41:26 +01:00
var suffix string
2024-03-19 08:00:49 +01:00
if isUnsignedInt(from) {
suffix = "u"
2024-03-18 21:14:28 +01:00
} else {
2024-03-19 08:00:49 +01:00
suffix = "s"
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
return ofInstruction("i64.extend_i32_" + suffix), nil
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
code := ofInstruction("i32.wrap_i64")
code.addAll(getTypeCast(to))
return code, nil
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
func (c *Compiler) compileAssignmentExpressionWAT(assignment AssignmentExpression) (Code, error) {
2024-04-02 19:43:05 +02:00
lhs := assignment.Lhs
2024-04-02 19:43:05 +02:00
exprWAT, err := c.compileExpressionWAT(assignment.Value)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-04-02 19:43:05 +02:00
}
switch lhs.Type {
case Expression_VariableReference:
ref := lhs.Value.(VariableReferenceExpression)
local := strconv.Itoa(getLocal(c.CurrentBlock, ref.Variable).Index)
2024-11-16 22:41:26 +01:00
2024-11-23 16:38:07 +01:00
exprWAT.add("local.tee $" + local + " (;" + ref.Variable + ";)")
2024-11-16 22:41:26 +01:00
return exprWAT, nil
2024-04-02 19:43:05 +02:00
case Expression_ArrayAccess:
2024-10-31 20:50:09 +01:00
array := lhs.Value.(ArrayAccessExpression)
localArray := Local{Name: "", Type: Type{Type: Type_Primitive, Value: c.getEffectiveAddressType(), Position: unknownPosition()}, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localArray)
localIndex := Local{Name: "", Type: Type{Type: Type_Primitive, Value: c.getEffectiveAddressType(), Position: unknownPosition()}, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localIndex)
localElement := Local{Name: "", Type: *assignment.Value.ValueType, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localElement)
arrayWAT, err := c.compileExpressionWAT(array.Array)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-10-31 20:50:09 +01:00
}
2024-11-23 16:38:07 +01:00
arrayWAT.add("local.set $" + strconv.Itoa(localArray.Index) + " (;array base address;)")
2024-10-31 20:50:09 +01:00
indexWAT, err := c.compileExpressionWAT(array.Index)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-10-31 20:50:09 +01:00
}
if !c.Wasm64 {
cast, err := castPrimitiveWAT(Primitive_I64, Primitive_I32)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-10-31 20:50:09 +01:00
}
2024-11-16 22:41:26 +01:00
indexWAT.addAll(cast)
2024-10-31 20:50:09 +01:00
}
2024-11-23 16:38:07 +01:00
indexWAT.add("local.set $" + strconv.Itoa(localIndex.Index) + " (;array index;)")
2024-10-31 20:50:09 +01:00
2024-11-16 22:41:26 +01:00
wat := concat(arrayWAT, indexWAT)
2024-10-31 20:50:09 +01:00
if _, ok := c.CompileOptions[COMPILE_OPTION_NO_BOUNDS_CHECK]; !ok {
// Error if index < 0
2024-11-16 22:41:26 +01:00
wat.add(
"block",
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)",
2024-11-16 22:41:26 +01:00
c.getAddressWATType()+".const 0",
c.getAddressWATType()+".ge_s",
"br_if 0",
"call $__builtin_panic",
"end",
)
2024-10-31 20:50:09 +01:00
// Error if index >= array length
2024-11-16 22:41:26 +01:00
wat.add(
"block",
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)",
"local.get $"+strconv.Itoa(localArray.Index)+" (;array base address;)",
2024-11-16 22:41:26 +01:00
"i32.load", // Load array length
c.getAddressWATType()+".lt_s",
"br_if 0",
"call $__builtin_panic",
"end",
)
2024-10-31 20:50:09 +01:00
}
elementType := array.Array.ValueType.Value.(ArrayType).ElementType
2024-11-16 22:41:26 +01:00
wat.add(
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)",
2024-11-16 22:41:26 +01:00
c.getAddressWATType()+".const "+strconv.Itoa(c.getTypeSizeBytes(elementType)),
c.getAddressWATType()+".mul",
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localArray.Index)+" (;array base address;)",
2024-11-16 22:41:26 +01:00
c.getAddressWATType()+".add",
c.getAddressWATType()+".const 4", // first 4 bytes = length
c.getAddressWATType()+".add",
)
wat.addAll(exprWAT)
wat.add(
"local.tee $"+strconv.Itoa(localElement.Index),
c.getWATType(elementType)+".store", // TODO: use load8/load16(_s/u) for smaller types
"local.get $"+strconv.Itoa(localElement.Index),
)
2024-10-31 20:50:09 +01:00
return wat, nil
2024-04-02 19:43:05 +02:00
case Expression_RawMemoryReference:
raw := lhs.Value.(RawMemoryReferenceExpression)
local := Local{Name: "", Type: *lhs.ValueType, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, local)
2024-03-24 21:36:34 +01:00
2024-04-02 19:43:05 +02:00
if raw.Type.Type != Type_Primitive {
panic("TODO") //TODO
}
addrWAT, err := c.compileExpressionWAT(raw.Address)
2024-03-24 21:36:34 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-24 21:36:34 +01:00
}
2024-04-02 19:43:05 +02:00
// TODO: should leave a copy of the stored value on the stack
2024-11-16 22:41:26 +01:00
code := addrWAT.clone()
code.addAll(exprWAT)
code.add(
"local.tee $"+strconv.Itoa(local.Index),
c.getWATType(raw.Type)+".store",
"local.get $"+strconv.Itoa(local.Index),
)
return code, nil
2024-04-02 19:43:05 +02:00
}
panic("assignment expr not implemented")
}
2024-03-24 21:36:34 +01:00
2024-11-16 22:41:26 +01:00
func (c *Compiler) compileAssignmentUpdateExpressionWAT(lhs Expression, updateWAT Code, evaluateToOldValue bool) (Code, error) {
switch lhs.Type {
case Expression_VariableReference:
ref := lhs.Value.(VariableReferenceExpression)
local := strconv.Itoa(getLocal(c.CurrentBlock, ref.Variable).Index)
exprWAT, err := c.compileExpressionWAT(lhs)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
wat := exprWAT.clone()
if evaluateToOldValue {
2024-11-23 16:38:07 +01:00
wat.add("local.get $" + local + " (;" + ref.Variable + ";)")
}
2024-11-16 22:41:26 +01:00
wat.addAll(updateWAT)
if evaluateToOldValue {
2024-11-23 16:38:07 +01:00
wat.add("local.set $" + local + " (;" + ref.Variable + ";)")
} else {
2024-11-23 16:38:07 +01:00
wat.add("local.tee $" + local + " (;" + ref.Variable + ";)")
}
return wat, nil
case Expression_ArrayAccess:
panic("TODO") // TODO
case Expression_RawMemoryReference:
raw := lhs.Value.(RawMemoryReferenceExpression)
localAddress := Local{Name: "", Type: Type{Type: Type_Primitive, Value: c.getEffectiveAddressType()}, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localAddress)
localValue := Local{Name: "", Type: *lhs.ValueType, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localValue)
if raw.Type.Type != Type_Primitive {
panic("TODO") //TODO
}
addrWAT, err := c.compileExpressionWAT(raw.Address)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
wat := addrWAT
// dup address
2024-11-16 22:41:26 +01:00
wat.add(
"local.tee $"+strconv.Itoa(localAddress.Index),
"local.get $"+strconv.Itoa(localAddress.Index),
)
2024-11-16 22:41:26 +01:00
wat.add(c.getWATType(raw.Type) + ".load")
if evaluateToOldValue {
2024-11-16 22:41:26 +01:00
wat.add("local.tee $" + strconv.Itoa(localValue.Index))
}
2024-11-16 22:41:26 +01:00
wat.addAll(updateWAT)
if !evaluateToOldValue {
2024-11-16 22:41:26 +01:00
wat.add("local.tee $" + strconv.Itoa(localValue.Index))
}
2024-11-16 22:41:26 +01:00
wat.add(
c.getWATType(raw.Type)+".store",
"local.get $"+strconv.Itoa(localValue.Index),
)
return wat, nil
}
panic("assignment expr not implemented")
}
2024-11-16 22:41:26 +01:00
func (c *Compiler) compileExpressionWAT(expr Expression) (Code, error) {
2024-04-02 19:43:05 +02:00
var err error
switch expr.Type {
case Expression_Assignment:
ass := expr.Value.(AssignmentExpression)
2024-04-20 13:56:41 +02:00
if ass.Operation == Operation_Nop {
return c.compileAssignmentExpressionWAT(ass)
}
watRight, err := c.compileExpressionWAT(ass.Value)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-04-20 13:56:41 +02:00
}
updateOp := c.compileOperationWAT(ass.Operation, ass.Lhs.ValueType.Value.(PrimitiveType))
2024-11-16 22:41:26 +01:00
watRight.addAll(updateOp)
2024-04-20 13:56:41 +02:00
2024-11-16 22:41:26 +01:00
return c.compileAssignmentUpdateExpressionWAT(ass.Lhs, watRight, false)
2024-03-18 21:14:28 +01:00
case Expression_Literal:
lit := expr.Value.(LiteralExpression)
switch lit.Literal.Type {
case Literal_Number:
return pushConstantNumberWAT(lit.Literal.Primitive, lit.Literal.Value), nil
case Literal_Boolean:
if lit.Literal.Value.(bool) {
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const 1"), nil
2024-03-18 21:14:28 +01:00
} else {
2024-11-16 22:41:26 +01:00
return ofInstruction("i32.const 0"), nil
2024-03-18 21:14:28 +01:00
}
case Literal_String:
panic("not implemented")
}
case Expression_VariableReference:
ref := expr.Value.(VariableReferenceExpression)
2024-03-19 12:19:19 +01:00
2024-11-16 22:41:26 +01:00
cast := emptyCode()
2024-03-19 12:19:19 +01:00
if expr.ValueType.Type == Type_Primitive {
// TODO: technically only needed for function parameters because functions can be called from outside WASM so they might not be fully type checked
2024-03-19 12:19:19 +01:00
cast = getTypeCast(expr.ValueType.Value.(PrimitiveType))
}
2024-11-23 16:38:07 +01:00
local := strconv.Itoa(getLocal(c.CurrentBlock, ref.Variable).Index)
2024-11-16 22:41:26 +01:00
code := emptyCode()
2024-11-23 16:38:07 +01:00
code.add("local.get $" + local + " (;" + ref.Variable + ";)")
2024-11-16 22:41:26 +01:00
code.addAll(cast)
return code, nil
2024-03-23 14:03:20 +01:00
case Expression_Binary:
2024-03-24 15:19:45 +01:00
binary := expr.Value.(BinaryExpression)
2024-03-19 10:54:21 +01:00
// TODO: currently fine, only allowed for primitive types, but should be expanded to allow e.g. strings
operandType := binary.Left.ValueType.Value.(PrimitiveType)
2024-03-19 10:54:21 +01:00
exprType := expr.ValueType.Value.(PrimitiveType)
2024-04-02 19:43:05 +02:00
watLeft, err := c.compileExpressionWAT(binary.Left)
2024-03-19 10:54:21 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-19 10:54:21 +01:00
}
2024-04-02 19:43:05 +02:00
watRight, err := c.compileExpressionWAT(binary.Right)
2024-03-19 10:54:21 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-19 10:54:21 +01:00
}
2024-04-20 13:56:41 +02:00
op := c.compileOperationWAT(binary.Operation, operandType)
2024-03-19 10:54:21 +01:00
2024-11-16 22:41:26 +01:00
return concat(watLeft, watRight, op, getTypeCast(exprType)), nil
2024-03-18 21:14:28 +01:00
case Expression_Tuple:
tuple := expr.Value.(TupleExpression)
2024-11-16 22:41:26 +01:00
wat := emptyCode()
for _, member := range tuple.Members {
2024-04-02 19:43:05 +02:00
memberWAT, err := c.compileExpressionWAT(member)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
wat.addAll(memberWAT)
}
return wat, nil
case Expression_FunctionCall:
fc := expr.Value.(FunctionCallExpression)
2024-11-16 22:41:26 +01:00
wat := emptyCode()
if fc.Parameters != nil {
2024-04-02 19:43:05 +02:00
wat, err = c.compileExpressionWAT(*fc.Parameters)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
}
switch fc.Function {
case BUILTIN_MEMORY_GROW:
if !c.Wasm64 {
cast, err := castPrimitiveWAT(Primitive_U64, Primitive_U32)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
wat.addAll(cast)
}
2024-11-16 22:41:26 +01:00
wat.add("memory.grow")
if !c.Wasm64 {
cast, err := castPrimitiveWAT(Primitive_I32, Primitive_I64)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
wat.addAll(cast)
}
return wat, nil
case BUILTIN_MEMORY_SIZE:
2024-11-16 22:41:26 +01:00
wat.add("memory.size")
if !c.Wasm64 {
cast, err := castPrimitiveWAT(Primitive_U32, Primitive_U64)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
wat.addAll(cast)
}
return wat, nil
default:
2024-11-16 22:41:26 +01:00
wat.add("call $" + fc.Function)
return wat, nil
}
2024-04-09 13:57:17 +02:00
case Expression_Unary:
unary := expr.Value.(UnaryExpression)
exprType := expr.ValueType.Value.(PrimitiveType)
2024-04-09 13:57:17 +02:00
wat, err := c.compileExpressionWAT(unary.Value)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
watType := getPrimitiveWATType(exprType)
2024-04-09 13:57:17 +02:00
switch unary.Operation {
case UnaryOperation_Negate:
if isFloatingPoint(exprType) {
2024-11-16 22:41:26 +01:00
wat.add(watType + ".neg")
return wat, nil
2024-04-09 13:57:17 +02:00
} else {
2024-11-16 22:41:26 +01:00
code := emptyCode()
code.add(watType + ".const 0")
code.addAll(wat)
code.add(watType + ".sub")
code.addAll(getTypeCast(exprType))
return code, nil
2024-04-09 13:57:17 +02:00
}
case UnaryOperation_Nop:
return wat, nil
case UnaryOperation_BitwiseNot:
if getBits(exprType) == 64 {
2024-11-16 22:41:26 +01:00
wat.add(watType + ".const 0xFFFFFFFFFFFFFFFF")
2024-04-09 13:57:17 +02:00
} else {
2024-11-16 22:41:26 +01:00
wat.add(watType + ".const 0xFFFFFFFF")
2024-04-09 13:57:17 +02:00
}
2024-11-16 22:41:26 +01:00
wat.add(watType + ".xor")
wat.addAll(getTypeCast(exprType))
return wat, nil
2024-04-09 13:57:17 +02:00
case UnaryOperation_LogicalNot:
2024-11-16 22:41:26 +01:00
wat.add("i32.eqz")
return wat, nil
2024-04-11 13:40:04 +02:00
case UnaryOperation_PreIncrement, UnaryOperation_PreDecrement, UnaryOperation_PostIncrement, UnaryOperation_PostDecrement:
valueType := c.getWATType(*unary.Value.ValueType)
2024-11-16 22:41:26 +01:00
updateWAT := ofInstruction(valueType + ".const 1")
if unary.Operation == UnaryOperation_PreIncrement || unary.Operation == UnaryOperation_PostIncrement {
2024-11-16 22:41:26 +01:00
updateWAT.add(valueType + ".add")
} else {
2024-11-16 22:41:26 +01:00
updateWAT.add(valueType + ".sub")
}
return c.compileAssignmentUpdateExpressionWAT(unary.Value, updateWAT, unary.Operation == UnaryOperation_PostIncrement || unary.Operation == UnaryOperation_PostDecrement)
}
case Expression_Cast:
cast := expr.Value.(CastExpression)
2024-04-02 19:43:05 +02:00
wat, err := c.compileExpressionWAT(cast.Value)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
// TODO: fine, as it is currently only allowed for primitive types
fromType := cast.Value.ValueType.Value.(PrimitiveType)
toType := cast.Type.Value.(PrimitiveType)
castWAT, err := castPrimitiveWAT(fromType, toType)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
return concat(wat, castWAT), nil
2024-04-02 19:43:05 +02:00
case Expression_RawMemoryReference:
raw := expr.Value.(RawMemoryReferenceExpression)
wat, err := c.compileExpressionWAT(raw.Address)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-04-02 19:43:05 +02:00
}
if raw.Type.Type == Type_Primitive {
2024-11-16 22:41:26 +01:00
wat.add(c.getWATType(raw.Type) + ".load") // TODO: use load8/load16(_s/u) for smaller types
2024-04-02 19:43:05 +02:00
}
2024-10-27 16:21:46 +01:00
return wat, nil
case Expression_ArrayAccess:
array := expr.Value.(ArrayAccessExpression)
localArray := Local{Name: "", Type: Type{Type: Type_Primitive, Value: c.getEffectiveAddressType(), Position: unknownPosition()}, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localArray)
localIndex := Local{Name: "", Type: Type{Type: Type_Primitive, Value: c.getEffectiveAddressType(), Position: unknownPosition()}, IsParameter: false, Index: len(c.CurrentFunction.Locals)}
c.CurrentFunction.Locals = append(c.CurrentFunction.Locals, localIndex)
arrayWAT, err := c.compileExpressionWAT(array.Array)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-10-27 16:21:46 +01:00
}
2024-11-23 16:38:07 +01:00
arrayWAT.add("local.set $" + strconv.Itoa(localArray.Index) + " (;array base address;)")
2024-10-27 16:21:46 +01:00
indexWAT, err := c.compileExpressionWAT(array.Index)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-10-27 16:21:46 +01:00
}
if !c.Wasm64 {
cast, err := castPrimitiveWAT(Primitive_I64, Primitive_I32)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-10-27 16:21:46 +01:00
}
2024-11-16 22:41:26 +01:00
indexWAT.addAll(cast)
2024-10-27 16:21:46 +01:00
}
2024-11-23 16:38:07 +01:00
indexWAT.add("local.set $" + strconv.Itoa(localIndex.Index) + " (;array index;)")
2024-10-27 16:21:46 +01:00
2024-11-16 22:41:26 +01:00
wat := concat(arrayWAT, indexWAT)
2024-10-27 16:21:46 +01:00
2024-10-31 20:50:09 +01:00
if _, ok := c.CompileOptions[COMPILE_OPTION_NO_BOUNDS_CHECK]; !ok {
// Error if index < 0
2024-11-16 22:41:26 +01:00
wat.add(
"block",
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)",
2024-11-16 22:41:26 +01:00
c.getAddressWATType()+".const 0",
c.getAddressWATType()+".ge_s",
"br_if 0",
"call $__builtin_panic",
"end",
)
2024-10-31 20:50:09 +01:00
// Error if index >= array length
2024-11-16 22:41:26 +01:00
wat.add(
"block",
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)",
"local.get $"+strconv.Itoa(localArray.Index)+" (;array base address;)",
2024-11-16 22:41:26 +01:00
"i32.load", // Load array length
c.getAddressWATType()+".lt_s",
"br_if 0",
"call $__builtin_panic",
"end",
)
2024-10-31 20:50:09 +01:00
}
2024-10-27 16:21:46 +01:00
elementType := array.Array.ValueType.Value.(ArrayType).ElementType
2024-11-16 22:41:26 +01:00
wat.add(
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)",
2024-11-16 22:41:26 +01:00
c.getAddressWATType()+".const "+strconv.Itoa(c.getTypeSizeBytes(elementType)),
c.getAddressWATType()+".mul",
2024-11-23 16:38:07 +01:00
"local.get $"+strconv.Itoa(localArray.Index)+"(;array base address;)",
2024-11-16 22:41:26 +01:00
c.getAddressWATType()+".add",
c.getAddressWATType()+".const 4", // first 4 bytes = length
c.getAddressWATType()+".add",
)
wat.add(c.getWATType(elementType) + ".load") // TODO: use load8/load16(_s/u) for smaller types
2024-10-27 16:21:46 +01:00
2024-04-02 19:43:05 +02:00
return wat, nil
2024-03-18 21:14:28 +01:00
}
panic("expr not implemented")
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
func (c *Compiler) compileOperationWAT(operation Operation, operandType PrimitiveType) Code {
var op string
2024-04-20 13:56:41 +02:00
2024-11-16 22:41:26 +01:00
var suffix string
2024-04-20 13:56:41 +02:00
if isUnsignedInt(operandType) {
suffix = "u"
} else {
suffix = "s"
}
switch operation {
case Operation_Add:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".add"
2024-04-20 13:56:41 +02:00
case Operation_Sub:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".sub"
2024-04-20 13:56:41 +02:00
case Operation_Mul:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".mul"
2024-04-20 13:56:41 +02:00
case Operation_Div:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".div_" + suffix
2024-04-20 13:56:41 +02:00
case Operation_Mod:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".rem_" + suffix
2024-04-20 13:56:41 +02:00
case Operation_Greater:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".gt_" + suffix
2024-04-20 13:56:41 +02:00
case Operation_Less:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".lt_" + suffix
2024-04-20 13:56:41 +02:00
case Operation_GreaterEquals:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".ge_" + suffix
2024-04-20 13:56:41 +02:00
case Operation_LessEquals:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".le_" + suffix
2024-04-20 13:56:41 +02:00
case Operation_NotEquals:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".ne"
2024-04-20 13:56:41 +02:00
case Operation_Equals:
2024-11-16 22:41:26 +01:00
op = getPrimitiveWATType(operandType) + ".eq"
2024-04-20 13:56:41 +02:00
default:
panic("operation not implemented")
}
2024-11-16 22:41:26 +01:00
return ofInstruction(op)
2024-04-20 13:56:41 +02:00
}
2024-11-16 22:41:26 +01:00
func (c *Compiler) compileStatementWAT(stmt Statement, block *Block) (Code, error) {
2024-03-18 21:14:28 +01:00
switch stmt.Type {
case Statement_Expression:
expr := stmt.Value.(ExpressionStatement)
2024-04-02 19:43:05 +02:00
wat, err := c.compileExpressionWAT(expr.Expression)
2024-03-18 21:14:28 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-18 21:14:28 +01:00
}
numItems := 0
if expr.Expression.ValueType != nil {
numItems = 1
if expr.Expression.ValueType.Type == Type_Tuple {
numItems = len(expr.Expression.ValueType.Value.(TupleType).Types)
}
}
2024-11-16 22:41:26 +01:00
for _ = range numItems {
wat.add("drop")
}
return wat, nil
2024-03-18 21:14:28 +01:00
case Statement_Block:
block := stmt.Value.(BlockStatement)
wat, err := c.compileBlockWAT(block.Block)
2024-03-18 21:14:28 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-18 21:14:28 +01:00
}
return wat, nil
case Statement_Return:
ret := stmt.Value.(ReturnStatement)
2024-04-20 14:50:20 +02:00
2024-11-16 22:41:26 +01:00
wat := emptyCode()
2024-04-20 14:50:20 +02:00
if ret.Value != nil {
valueWAT, err := c.compileExpressionWAT(*ret.Value)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-04-20 14:50:20 +02:00
}
2024-11-16 22:41:26 +01:00
wat.addAll(valueWAT)
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
wat.add("return")
return wat, nil
2024-03-18 21:14:28 +01:00
case Statement_DeclareLocalVariable:
dlv := stmt.Value.(DeclareLocalVariableStatement)
if dlv.Initializer == nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), nil
2024-03-18 21:14:28 +01:00
}
2024-04-02 19:43:05 +02:00
wat, err := c.compileExpressionWAT(*dlv.Initializer)
2024-03-18 21:14:28 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
wat.add("local.set $" + strconv.Itoa(block.Locals[dlv.Variable].Index))
return wat, nil
2024-03-24 15:19:45 +01:00
case Statement_If:
ifS := stmt.Value.(IfStatement)
2024-04-02 19:43:05 +02:00
conditionWAT, err := c.compileExpressionWAT(ifS.Condition)
2024-03-24 15:19:45 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-24 15:19:45 +01:00
}
condBlockWAT, err := c.compileBlockWAT(ifS.ConditionalBlock)
2024-03-24 15:19:45 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-24 15:19:45 +01:00
}
2024-11-16 22:41:26 +01:00
wat := emptyCode()
2024-03-24 15:19:45 +01:00
if ifS.ElseBlock != nil {
2024-11-16 22:41:26 +01:00
wat.add("block")
2024-03-24 15:19:45 +01:00
}
// condition
2024-11-16 22:41:26 +01:00
wat.add("block")
2024-03-24 15:19:45 +01:00
2024-11-16 22:41:26 +01:00
wat.addAll(conditionWAT)
wat.add("i32.eqz") // logical not
wat.add("br_if 0")
2024-03-24 15:19:45 +01:00
// condition is true
2024-11-16 22:41:26 +01:00
wat.addAll(condBlockWAT)
2024-03-24 15:19:45 +01:00
if ifS.ElseBlock != nil {
2024-11-16 22:41:26 +01:00
wat.add("br 1") // jump over else block
2024-03-24 15:19:45 +01:00
}
2024-11-16 22:41:26 +01:00
wat.add("end")
2024-03-24 15:19:45 +01:00
if ifS.ElseBlock != nil {
// condition is false
elseWAT, err := c.compileBlockWAT(ifS.ElseBlock)
2024-03-24 15:19:45 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-24 15:19:45 +01:00
}
2024-11-16 22:41:26 +01:00
wat.addAll(elseWAT)
wat.add("end")
2024-03-24 15:19:45 +01:00
}
return wat, nil
case Statement_WhileLoop:
while := stmt.Value.(WhileLoopStatement)
conditionWAT, err := c.compileExpressionWAT(while.Condition)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
bodyWAT, err := c.compileBlockWAT(while.Body)
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
}
2024-11-16 22:41:26 +01:00
wat := emptyCode()
wat.add("block")
wat.add("loop")
2024-11-16 22:41:26 +01:00
wat.addAll(conditionWAT)
wat.add("i32.eqz")
wat.add("br_if 1")
2024-11-16 22:41:26 +01:00
wat.addAll(bodyWAT)
wat.add("br 0")
2024-11-16 22:41:26 +01:00
wat.add("end")
wat.add("end")
2024-03-24 15:19:45 +01:00
return wat, nil
2024-03-18 21:14:28 +01:00
}
2024-03-23 14:03:20 +01:00
panic("stmt not implemented")
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
func (c *Compiler) compileBlockWAT(block *Block) (Code, error) {
blockWAT := emptyCode()
2024-03-18 21:14:28 +01:00
for _, stmt := range block.Statements {
2024-04-02 19:43:05 +02:00
c.CurrentBlock = block
wat, err := c.compileStatementWAT(stmt, block)
2024-03-18 21:14:28 +01:00
if err != nil {
2024-11-16 22:41:26 +01:00
return emptyCode(), err
2024-03-18 21:14:28 +01:00
}
2024-11-16 22:41:26 +01:00
blockWAT.addAll(wat)
2024-03-18 21:14:28 +01:00
}
return blockWAT, nil
}
2024-04-02 19:43:05 +02:00
func (c *Compiler) compileFunctionWAT(function *ParsedFunction) (string, error) {
c.CurrentFunction = function
blockWat, err := c.compileBlockWAT(function.Body)
if err != nil {
return "", err
}
funcWAT := "(func $" + safeASCIIIdentifier(function.FullName) + " (export \"" + function.FullName + "\")\n"
2024-03-18 21:14:28 +01:00
for _, local := range function.Locals {
2024-03-19 12:48:06 +01:00
if !local.IsParameter {
continue
2024-03-18 21:14:28 +01:00
}
funcWAT += "\t(param $" + strconv.Itoa(local.Index) + " " + c.getWATType(local.Type) + ")\n"
2024-03-18 21:14:28 +01:00
}
2024-03-21 19:55:05 +01:00
returnTypes := []Type{}
if function.ReturnType != nil {
returnTypes = []Type{*function.ReturnType}
if function.ReturnType.Type == Type_Tuple {
returnTypes = function.ReturnType.Value.(TupleType).Types
}
2024-03-21 13:52:05 +01:00
}
for _, t := range returnTypes {
funcWAT += "\t(result " + c.getWATType(t) + ")\n"
}
2024-03-19 10:54:21 +01:00
2024-03-19 12:48:06 +01:00
for _, local := range function.Locals {
if local.IsParameter {
continue
}
funcWAT += "\t(local $" + strconv.Itoa(local.Index) + " " + c.getWATType(local.Type) + ")\n"
2024-03-19 12:48:06 +01:00
}
2024-11-16 22:41:26 +01:00
return funcWAT + blockWat.toString() + ")\n", nil
2024-03-18 21:14:28 +01:00
}
func (c *Compiler) compile() (string, error) {
2024-11-16 22:41:26 +01:00
module := "(module\n"
module += "(memory $memory 0) (export \"memory\" (memory $memory))\n"
2024-03-18 21:14:28 +01:00
2024-10-27 16:21:46 +01:00
module += "(func $__builtin_panic\n"
2024-11-16 22:41:26 +01:00
module += "\tunreachable\n"
2024-10-27 16:21:46 +01:00
module += ")\n"
for _, file := range c.Files {
2024-04-02 19:43:05 +02:00
for i := range file.Functions {
wat, err := c.compileFunctionWAT(&file.Functions[i])
if err != nil {
return "", err
}
2024-03-18 21:14:28 +01:00
module += wat
}
2024-03-18 21:14:28 +01:00
}
module += ")"
return module, nil
}