diff --git a/README.md b/README.md index 3c23142..2d9d1db 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The Elysium programming language. - [ ] Modules - [ ] Arrays - [ ] Structs +- [ ] Strings - [X] Raw memory access - [ ] Memory allocation, Heap allocator - [ ] Garbage collector diff --git a/backend_wat.go b/backend_wat.go index 15d9063..e6acab8 100644 --- a/backend_wat.go +++ b/backend_wat.go @@ -112,6 +112,34 @@ func (c *Compiler) getWATType(t Type) string { 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) (string, error) { if from == to { return "", nil @@ -492,6 +520,71 @@ func (c *Compiler) compileExpressionWAT(expr Expression) (string, error) { wat += c.getWATType(raw.Type) + ".load\n" // 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 "", err + } + + arrayWAT += "local.set $" + strconv.Itoa(localArray.Index) + "\n" + + indexWAT, err := c.compileExpressionWAT(array.Index) + if err != nil { + return "", err + } + + if !c.Wasm64 { + cast, err := castPrimitiveWAT(Primitive_I64, Primitive_I32) + if err != nil { + return "", err + } + + indexWAT += cast + } + + indexWAT += "local.set $" + strconv.Itoa(localIndex.Index) + "\n" + + wat := arrayWAT + indexWAT + + // Error if index <= 0 + wat += "block\n" + wat += "local.get $" + strconv.Itoa(localIndex.Index) + "\n" + wat += c.getAddressWATType() + ".const 0\n" + wat += c.getAddressWATType() + ".gt_s\n" + wat += "br_if 0\n" + wat += "call $__builtin_panic\n" + wat += "end\n" + + // Error if index >= array length + wat += "block\n" + wat += "local.get $" + strconv.Itoa(localIndex.Index) + "\n" + wat += "local.get $" + strconv.Itoa(localArray.Index) + "\n" + wat += "i32.load\n" // Load array length + wat += c.getAddressWATType() + ".lt_s\n" + wat += "br_if 0\n" + wat += "call $__builtin_panic\n" + wat += "end\n" + + elementType := array.Array.ValueType.Value.(ArrayType).ElementType + wat += "local.get $" + strconv.Itoa(localIndex.Index) + "\n" + wat += c.getAddressWATType() + ".const " + strconv.Itoa(c.getTypeSizeBytes(elementType)) + "\n" + wat += c.getAddressWATType() + ".mul\n" + wat += "local.get $" + strconv.Itoa(localArray.Index) + "\n" + wat += c.getAddressWATType() + ".add\n" + wat += c.getAddressWATType() + ".const 4\n" // first 4 bytes = length + wat += c.getAddressWATType() + ".add\n" + + wat += c.getWATType(elementType) + ".load\n" // TODO: use load8/load16(_s/u) for smaller types + return wat, nil } @@ -727,6 +820,13 @@ func (c *Compiler) compileFunctionWAT(function *ParsedFunction) (string, error) func (c *Compiler) compile() (string, error) { module := "(module (memory 0)\n" + module += "(func $__builtin_panic\n" + module += "i32.const 0\n" + module += "i32.const 0\n" + module += "i32.div_u\n" + module += "drop\n" + module += ")\n" + for _, file := range c.Files { for i := range file.Functions { wat, err := c.compileFunctionWAT(&file.Functions[i]) diff --git a/example/test.ely b/example/test.ely index 4cb5340..2591e34 100644 --- a/example/test.ely +++ b/example/test.ely @@ -38,3 +38,7 @@ u64 assign(u64 a) { a += 1u64; return raw(u64, a) += 2u64; } + +u64 test() { + return raw(u64[], 0x0u8)[0]; +} diff --git a/validator.go b/validator.go index d501030..b0bb577 100644 --- a/validator.go +++ b/validator.go @@ -127,6 +127,10 @@ func isPrimitiveTypeExpandableTo(from PrimitiveType, to PrimitiveType) bool { return true } + if isSignedInt(from) && !isSignedInt(to) { + return false + } + switch from { case Primitive_I8, Primitive_U8: if to == Primitive_I16 || to == Primitive_U16 { @@ -396,11 +400,14 @@ func (v *Validator) validatePotentiallyVoidExpression(expr *Expression) []error return errors } - if raw.Address.ValueType.Type != Type_Primitive || raw.Address.ValueType.Value.(PrimitiveType) != Primitive_U64 { - errors = append(errors, v.createError("address must evaluate to a u64 value", expr.Position)) + typeU64 := Type{Type: Type_Primitive, Value: Primitive_U64, Position: unknownPosition()} + if !isTypeExpandableTo(*raw.Address.ValueType, typeU64) { + errors = append(errors, v.createError("address must be expandable to a u64 value", expr.Position)) return errors } + expandExpressionToType(&raw.Address, typeU64) + if !v.Wasm64 { castTo := Type{Type: Type_Primitive, Value: Primitive_U32} raw.Address = Expression{Type: Expression_Cast, Value: CastExpression{Type: castTo, Value: raw.Address}, ValueType: &castTo, Position: raw.Address.Position} @@ -408,6 +415,43 @@ func (v *Validator) validatePotentiallyVoidExpression(expr *Expression) []error expr.ValueType = &raw.Type expr.Value = raw + case Expression_ArrayAccess: + array := expr.Value.(ArrayAccessExpression) + + arrayErrors := v.validateExpression(&array.Array) + if len(arrayErrors) != 0 { + errors = append(errors, arrayErrors...) + } + + indexErrors := v.validateExpression(&array.Index) + if len(indexErrors) != 0 { + errors = append(errors, indexErrors...) + return errors + } + + if len(errors) != 0 { + return errors + } + + if array.Array.ValueType.Type != Type_Array { + errors = append(errors, v.createError("trying to access non-array type as an array", array.Array.Position)) + } + + typeI64 := Type{Type: Type_Primitive, Value: Primitive_I64, Position: unknownPosition()} + if !isTypeExpandableTo(*array.Index.ValueType, typeI64) { + errors = append(errors, v.createError("array index must be expandable to an i64 value", array.Index.Position)) + return errors + } + + expandExpressionToType(&array.Index, typeI64) + + if len(errors) != 0 { + return errors + } + + elementType := array.Array.ValueType.Value.(ArrayType).ElementType + expr.ValueType = &elementType + expr.Value = array default: panic("expr not implemented") }