package main import ( "errors" "fmt" "strconv" "unicode" ) const COMPILE_OPTION_NO_BOUNDS_CHECK = "no_bounds_check" type Compiler struct { Files []*ParsedFile Wasm64 bool CompileOptions map[string]string CurrentBlock *Block CurrentFunction *ParsedFunction } func getPrimitiveWATType(primitive PrimitiveType) string { 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)) } 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 } func getTypeCast(primitive PrimitiveType) Code { switch primitive { case Primitive_I8: return ofInstruction("i32.extend8_s") case Primitive_U8: return ofInstruction("i32.const 255", "i32.and") case Primitive_I16: return ofInstruction("i32.extend16_s") case Primitive_U16: return ofInstruction("i32.const 65535", "i32.and") case Primitive_Bool: return ofInstruction("i32.const 0", "i32.ne") } return emptyCode() } func pushConstantNumberWAT(primitive PrimitiveType, value any) Code { switch primitive { case Primitive_I8, Primitive_I16, Primitive_I32: return ofInstruction("i32.const " + strconv.FormatInt(value.(int64), 10)) case Primitive_U8, Primitive_U16, Primitive_U32: return ofInstruction("i32.const " + strconv.FormatUint(value.(uint64), 10)) case Primitive_I64: return ofInstruction("i64.const " + strconv.FormatInt(value.(int64), 10)) case Primitive_U64: return ofInstruction("i64.const " + strconv.FormatUint(value.(uint64), 10)) case Primitive_F32: return ofInstruction("f32.const " + strconv.FormatFloat(value.(float64), 'f', -1, 32)) case Primitive_F64: return ofInstruction("f64.const " + strconv.FormatFloat(value.(float64), 'f', -1, 64)) } panic(fmt.Sprintf("invalid type passed to pushConstantNumberWAT(): %s", primitive)) } 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: 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)) } 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)) } func castPrimitiveWAT(from PrimitiveType, to PrimitiveType) (Code, error) { if from == to { return emptyCode(), nil } if from == Primitive_Bool || to == Primitive_Bool { return emptyCode(), errors.New("cannot upcast from or to bool") } fromFloat := isFloatingPoint(from) toFloat := isFloatingPoint(to) if fromFloat && toFloat { if to == Primitive_F32 { return ofInstruction("f32.demote_f64"), nil } else { return ofInstruction("f64.promote_f32"), nil } } if toFloat { var suffix string if isUnsignedInt(to) { suffix = "u" } else { suffix = "s" } return ofInstruction(getPrimitiveWATType(to) + ".convert_" + getPrimitiveWATType(from) + "_" + suffix), nil } if fromFloat { var suffix string if isUnsignedInt(to) { suffix = "u" } else { suffix = "s" } return ofInstruction(getPrimitiveWATType(to) + ".trunc_" + getPrimitiveWATType(from) + "_" + suffix), nil } if getBits(from) == getBits(to) { return emptyCode(), nil } if getPrimitiveWATType(from) == getPrimitiveWATType(to) { if getBits(to) < getBits(from) { return getTypeCast(to), nil } return emptyCode(), nil } if getBits(from) < 64 && getBits(to) == 64 { var suffix string if isUnsignedInt(from) { suffix = "u" } else { suffix = "s" } return ofInstruction("i64.extend_i32_" + suffix), nil } code := ofInstruction("i32.wrap_i64") code.addAll(getTypeCast(to)) return code, nil } func (c *Compiler) compileAssignmentExpressionWAT(assignment AssignmentExpression) (Code, error) { lhs := assignment.Lhs exprWAT, err := c.compileExpressionWAT(assignment.Value) if err != nil { return emptyCode(), err } switch lhs.Type { case Expression_VariableReference: ref := lhs.Value.(VariableReferenceExpression) local := strconv.Itoa(getLocal(c.CurrentBlock, ref.Variable).Index) exprWAT.add("local.tee $" + local + " (;" + ref.Variable + ";)") return exprWAT, nil case Expression_ArrayAccess: 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 { return emptyCode(), err } arrayWAT.add("local.set $" + strconv.Itoa(localArray.Index) + " (;array base address;)") indexWAT, err := c.compileExpressionWAT(array.Index) if err != nil { return emptyCode(), err } if !c.Wasm64 { cast, err := castPrimitiveWAT(Primitive_I64, Primitive_I32) if err != nil { return emptyCode(), err } indexWAT.addAll(cast) } indexWAT.add("local.set $" + strconv.Itoa(localIndex.Index) + " (;array index;)") wat := concat(arrayWAT, indexWAT) if _, ok := c.CompileOptions[COMPILE_OPTION_NO_BOUNDS_CHECK]; !ok { // Error if index < 0 wat.add( "block", "local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)", c.getAddressWATType()+".const 0", c.getAddressWATType()+".ge_s", "br_if 0", "call $__builtin_panic", "end", ) // Error if index >= array length wat.add( "block", "local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)", "local.get $"+strconv.Itoa(localArray.Index)+" (;array base address;)", "i32.load", // Load array length c.getAddressWATType()+".lt_s", "br_if 0", "call $__builtin_panic", "end", ) } elementType := array.Array.ValueType.Value.(ArrayType).ElementType wat.add( "local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)", c.getAddressWATType()+".const "+strconv.Itoa(c.getTypeSizeBytes(elementType)), c.getAddressWATType()+".mul", "local.get $"+strconv.Itoa(localArray.Index)+" (;array base address;)", 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), ) return wat, nil 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) if raw.Type.Type != Type_Primitive { panic("TODO") //TODO } addrWAT, err := c.compileExpressionWAT(raw.Address) if err != nil { return emptyCode(), err } // TODO: should leave a copy of the stored value on the stack 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 } panic("assignment expr not implemented") } 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 { return emptyCode(), err } wat := exprWAT.clone() if evaluateToOldValue { wat.add("local.get $" + local + " (;" + ref.Variable + ";)") } wat.addAll(updateWAT) if evaluateToOldValue { wat.add("local.set $" + local + " (;" + ref.Variable + ";)") } else { 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 { return emptyCode(), err } wat := addrWAT // dup address wat.add( "local.tee $"+strconv.Itoa(localAddress.Index), "local.get $"+strconv.Itoa(localAddress.Index), ) wat.add(c.getWATType(raw.Type) + ".load") if evaluateToOldValue { wat.add("local.tee $" + strconv.Itoa(localValue.Index)) } wat.addAll(updateWAT) if !evaluateToOldValue { wat.add("local.tee $" + strconv.Itoa(localValue.Index)) } wat.add( c.getWATType(raw.Type)+".store", "local.get $"+strconv.Itoa(localValue.Index), ) return wat, nil } panic("assignment expr not implemented") } func (c *Compiler) compileExpressionWAT(expr Expression) (Code, error) { var err error switch expr.Type { case Expression_Assignment: ass := expr.Value.(AssignmentExpression) if ass.Operation == Operation_Nop { return c.compileAssignmentExpressionWAT(ass) } watRight, err := c.compileExpressionWAT(ass.Value) if err != nil { return emptyCode(), err } updateOp := c.compileOperationWAT(ass.Operation, ass.Lhs.ValueType.Value.(PrimitiveType)) watRight.addAll(updateOp) return c.compileAssignmentUpdateExpressionWAT(ass.Lhs, watRight, false) 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) { return ofInstruction("i32.const 1"), nil } else { return ofInstruction("i32.const 0"), nil } case Literal_String: panic("not implemented") } case Expression_VariableReference: ref := expr.Value.(VariableReferenceExpression) cast := emptyCode() 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 cast = getTypeCast(expr.ValueType.Value.(PrimitiveType)) } local := strconv.Itoa(getLocal(c.CurrentBlock, ref.Variable).Index) code := emptyCode() code.add("local.get $" + local + " (;" + ref.Variable + ";)") code.addAll(cast) return code, nil case Expression_Binary: binary := expr.Value.(BinaryExpression) // TODO: currently fine, only allowed for primitive types, but should be expanded to allow e.g. strings operandType := binary.Left.ValueType.Value.(PrimitiveType) exprType := expr.ValueType.Value.(PrimitiveType) watLeft, err := c.compileExpressionWAT(binary.Left) if err != nil { return emptyCode(), err } watRight, err := c.compileExpressionWAT(binary.Right) if err != nil { return emptyCode(), err } op := c.compileOperationWAT(binary.Operation, operandType) return concat(watLeft, watRight, op, getTypeCast(exprType)), nil case Expression_Tuple: tuple := expr.Value.(TupleExpression) wat := emptyCode() for _, member := range tuple.Members { memberWAT, err := c.compileExpressionWAT(member) if err != nil { return emptyCode(), err } wat.addAll(memberWAT) } return wat, nil case Expression_FunctionCall: fc := expr.Value.(FunctionCallExpression) wat := emptyCode() if fc.Parameters != nil { wat, err = c.compileExpressionWAT(*fc.Parameters) if err != nil { return emptyCode(), err } } switch fc.Function { case BUILTIN_MEMORY_GROW: if !c.Wasm64 { cast, err := castPrimitiveWAT(Primitive_U64, Primitive_U32) if err != nil { return emptyCode(), err } wat.addAll(cast) } wat.add("memory.grow") if !c.Wasm64 { cast, err := castPrimitiveWAT(Primitive_I32, Primitive_I64) if err != nil { return emptyCode(), err } wat.addAll(cast) } return wat, nil case BUILTIN_MEMORY_SIZE: wat.add("memory.size") if !c.Wasm64 { cast, err := castPrimitiveWAT(Primitive_U32, Primitive_U64) if err != nil { return emptyCode(), err } wat.addAll(cast) } return wat, nil default: wat.add("call $" + fc.Function) return wat, nil } case Expression_Unary: unary := expr.Value.(UnaryExpression) exprType := expr.ValueType.Value.(PrimitiveType) wat, err := c.compileExpressionWAT(unary.Value) if err != nil { return emptyCode(), err } watType := getPrimitiveWATType(exprType) switch unary.Operation { case UnaryOperation_Negate: if isFloatingPoint(exprType) { wat.add(watType + ".neg") return wat, nil } else { code := emptyCode() code.add(watType + ".const 0") code.addAll(wat) code.add(watType + ".sub") code.addAll(getTypeCast(exprType)) return code, nil } case UnaryOperation_Nop: return wat, nil case UnaryOperation_BitwiseNot: if getBits(exprType) == 64 { wat.add(watType + ".const 0xFFFFFFFFFFFFFFFF") } else { wat.add(watType + ".const 0xFFFFFFFF") } wat.add(watType + ".xor") wat.addAll(getTypeCast(exprType)) return wat, nil case UnaryOperation_LogicalNot: wat.add("i32.eqz") return wat, nil case UnaryOperation_PreIncrement, UnaryOperation_PreDecrement, UnaryOperation_PostIncrement, UnaryOperation_PostDecrement: valueType := c.getWATType(*unary.Value.ValueType) updateWAT := ofInstruction(valueType + ".const 1") if unary.Operation == UnaryOperation_PreIncrement || unary.Operation == UnaryOperation_PostIncrement { updateWAT.add(valueType + ".add") } else { 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) wat, err := c.compileExpressionWAT(cast.Value) if err != nil { 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 { return emptyCode(), err } return concat(wat, castWAT), nil case Expression_RawMemoryReference: raw := expr.Value.(RawMemoryReferenceExpression) wat, err := c.compileExpressionWAT(raw.Address) if err != nil { return emptyCode(), err } if raw.Type.Type == Type_Primitive { wat.add(c.getWATType(raw.Type) + ".load") // TODO: use load8/load16(_s/u) for smaller types } 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 { return emptyCode(), err } arrayWAT.add("local.set $" + strconv.Itoa(localArray.Index) + " (;array base address;)") indexWAT, err := c.compileExpressionWAT(array.Index) if err != nil { return emptyCode(), err } if !c.Wasm64 { cast, err := castPrimitiveWAT(Primitive_I64, Primitive_I32) if err != nil { return emptyCode(), err } indexWAT.addAll(cast) } indexWAT.add("local.set $" + strconv.Itoa(localIndex.Index) + " (;array index;)") wat := concat(arrayWAT, indexWAT) if _, ok := c.CompileOptions[COMPILE_OPTION_NO_BOUNDS_CHECK]; !ok { // Error if index < 0 wat.add( "block", "local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)", c.getAddressWATType()+".const 0", c.getAddressWATType()+".ge_s", "br_if 0", "call $__builtin_panic", "end", ) // Error if index >= array length wat.add( "block", "local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)", "local.get $"+strconv.Itoa(localArray.Index)+" (;array base address;)", "i32.load", // Load array length c.getAddressWATType()+".lt_s", "br_if 0", "call $__builtin_panic", "end", ) } elementType := array.Array.ValueType.Value.(ArrayType).ElementType wat.add( "local.get $"+strconv.Itoa(localIndex.Index)+" (;array index;)", c.getAddressWATType()+".const "+strconv.Itoa(c.getTypeSizeBytes(elementType)), c.getAddressWATType()+".mul", "local.get $"+strconv.Itoa(localArray.Index)+"(;array base address;)", 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 return wat, nil } panic("expr not implemented") } func (c *Compiler) compileOperationWAT(operation Operation, operandType PrimitiveType) Code { var op string var suffix string if isUnsignedInt(operandType) { suffix = "u" } else { suffix = "s" } switch operation { case Operation_Add: op = getPrimitiveWATType(operandType) + ".add" case Operation_Sub: op = getPrimitiveWATType(operandType) + ".sub" case Operation_Mul: op = getPrimitiveWATType(operandType) + ".mul" case Operation_Div: op = getPrimitiveWATType(operandType) + ".div_" + suffix case Operation_Mod: op = getPrimitiveWATType(operandType) + ".rem_" + suffix case Operation_Greater: op = getPrimitiveWATType(operandType) + ".gt_" + suffix case Operation_Less: op = getPrimitiveWATType(operandType) + ".lt_" + suffix case Operation_GreaterEquals: op = getPrimitiveWATType(operandType) + ".ge_" + suffix case Operation_LessEquals: op = getPrimitiveWATType(operandType) + ".le_" + suffix case Operation_NotEquals: op = getPrimitiveWATType(operandType) + ".ne" case Operation_Equals: op = getPrimitiveWATType(operandType) + ".eq" default: panic("operation not implemented") } return ofInstruction(op) } func (c *Compiler) compileStatementWAT(stmt Statement, block *Block) (Code, error) { switch stmt.Type { case Statement_Expression: expr := stmt.Value.(ExpressionStatement) wat, err := c.compileExpressionWAT(expr.Expression) if err != nil { return emptyCode(), err } numItems := 0 if expr.Expression.ValueType != nil { numItems = 1 if expr.Expression.ValueType.Type == Type_Tuple { numItems = len(expr.Expression.ValueType.Value.(TupleType).Types) } } for _ = range numItems { wat.add("drop") } return wat, nil case Statement_Block: block := stmt.Value.(BlockStatement) wat, err := c.compileBlockWAT(block.Block) if err != nil { return emptyCode(), err } return wat, nil case Statement_Return: ret := stmt.Value.(ReturnStatement) wat := emptyCode() if ret.Value != nil { valueWAT, err := c.compileExpressionWAT(*ret.Value) if err != nil { return emptyCode(), err } wat.addAll(valueWAT) } wat.add("return") return wat, nil case Statement_DeclareLocalVariable: dlv := stmt.Value.(DeclareLocalVariableStatement) if dlv.Initializer == nil { return emptyCode(), nil } wat, err := c.compileExpressionWAT(*dlv.Initializer) if err != nil { return emptyCode(), err } wat.add("local.set $" + strconv.Itoa(block.Locals[dlv.Variable].Index)) return wat, nil case Statement_If: ifS := stmt.Value.(IfStatement) conditionWAT, err := c.compileExpressionWAT(ifS.Condition) if err != nil { return emptyCode(), err } condBlockWAT, err := c.compileBlockWAT(ifS.ConditionalBlock) if err != nil { return emptyCode(), err } wat := emptyCode() if ifS.ElseBlock != nil { wat.add("block") } // condition wat.add("block") wat.addAll(conditionWAT) wat.add("i32.eqz") // logical not wat.add("br_if 0") // condition is true wat.addAll(condBlockWAT) if ifS.ElseBlock != nil { wat.add("br 1") // jump over else block } wat.add("end") if ifS.ElseBlock != nil { // condition is false elseWAT, err := c.compileBlockWAT(ifS.ElseBlock) if err != nil { return emptyCode(), err } wat.addAll(elseWAT) wat.add("end") } return wat, nil case Statement_WhileLoop: while := stmt.Value.(WhileLoopStatement) conditionWAT, err := c.compileExpressionWAT(while.Condition) if err != nil { return emptyCode(), err } bodyWAT, err := c.compileBlockWAT(while.Body) if err != nil { return emptyCode(), err } wat := emptyCode() wat.add("block") wat.add("loop") wat.addAll(conditionWAT) wat.add("i32.eqz") wat.add("br_if 1") wat.addAll(bodyWAT) wat.add("br 0") wat.add("end") wat.add("end") return wat, nil } panic("stmt not implemented") } func (c *Compiler) compileBlockWAT(block *Block) (Code, error) { blockWAT := emptyCode() for _, stmt := range block.Statements { c.CurrentBlock = block wat, err := c.compileStatementWAT(stmt, block) if err != nil { return emptyCode(), err } blockWAT.addAll(wat) } return blockWAT, nil } 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" for _, local := range function.Locals { if !local.IsParameter { continue } funcWAT += "\t(param $" + strconv.Itoa(local.Index) + " " + c.getWATType(local.Type) + ")\n" } returnTypes := []Type{} if function.ReturnType != nil { returnTypes = []Type{*function.ReturnType} if function.ReturnType.Type == Type_Tuple { returnTypes = function.ReturnType.Value.(TupleType).Types } } for _, t := range returnTypes { funcWAT += "\t(result " + c.getWATType(t) + ")\n" } for _, local := range function.Locals { if local.IsParameter { continue } funcWAT += "\t(local $" + strconv.Itoa(local.Index) + " " + c.getWATType(local.Type) + ")\n" } return funcWAT + blockWat.toString() + ")\n", nil } func (c *Compiler) compile() (string, error) { module := "(module\n" module += "(memory $memory 0) (export \"memory\" (memory $memory))\n" module += "(func $__builtin_panic\n" module += "\tunreachable\n" module += ")\n" for _, file := range c.Files { for i := range file.Functions { wat, err := c.compileFunctionWAT(&file.Functions[i]) if err != nil { return "", err } module += wat } } module += ")" return module, nil }