From ca48a687f838b8a7e2ede55ab88ef6bfd895ce4e Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 6 Feb 2017 21:39:21 +0100 Subject: [PATCH] - major work on dynamic array support. Mostly working, some issues still exist with Move and Copy methods and with assignments in stack variables. --- src/dobjtype.cpp | 32 +++- src/dobjtype.h | 4 +- src/namedef.h | 2 + src/scripting/codegeneration/codegen.cpp | 180 +++++++++++++++++------ src/scripting/codegeneration/codegen.h | 1 + src/scripting/zscript/zcc_compile.cpp | 11 +- 6 files changed, 175 insertions(+), 55 deletions(-) diff --git a/src/dobjtype.cpp b/src/dobjtype.cpp index e4d9fc177..958cd4275 100644 --- a/src/dobjtype.cpp +++ b/src/dobjtype.cpp @@ -2012,8 +2012,8 @@ PDynArray::PDynArray() // //========================================================================== -PDynArray::PDynArray(PType *etype) -: ElementType(etype) +PDynArray::PDynArray(PType *etype,PStruct *backing) +: ElementType(etype), BackingType(backing) { mDescriptiveName.Format("DynArray<%s>", etype->DescriptiveName()); Size = sizeof(FArray); @@ -2061,7 +2061,33 @@ PDynArray *NewDynArray(PType *type) PType *atype = TypeTable.FindType(RUNTIME_CLASS(PDynArray), (intptr_t)type, 0, &bucket); if (atype == NULL) { - atype = new PDynArray(type); + FString backingname; + + switch (type->GetRegType()) + { + case REGT_INT: + backingname.Format("DynArray_I%d", type->Size * 8); + break; + + case REGT_FLOAT: + backingname.Format("DynArray_F%d", type->Size * 8); + break; + + case REGT_STRING: + backingname = "DynArray_String"; + break; + + case REGT_POINTER: + backingname = "DynArray_Ptr"; + break; + + default: + I_Error("Unsupported dynamic array requested"); + break; + } + + auto backing = NewNativeStruct(backingname, nullptr); + atype = new PDynArray(type, backing); TypeTable.AddType(atype, RUNTIME_CLASS(PDynArray), (intptr_t)type, 0, bucket); } return (PDynArray *)atype; diff --git a/src/dobjtype.h b/src/dobjtype.h index 8cbe79966..60d410918 100644 --- a/src/dobjtype.h +++ b/src/dobjtype.h @@ -6,6 +6,7 @@ #endif typedef std::pair FTypeAndOffset; +class PStruct; #include "vm.h" @@ -649,9 +650,10 @@ class PDynArray : public PCompoundType DECLARE_CLASS(PDynArray, PCompoundType); HAS_OBJECT_POINTERS; public: - PDynArray(PType *etype); + PDynArray(PType *etype, PStruct *backing); PType *ElementType; + PStruct *BackingType; virtual bool IsMatch(intptr_t id1, intptr_t id2) const; virtual void GetTypeIDs(intptr_t &id1, intptr_t &id2) const; diff --git a/src/namedef.h b/src/namedef.h index 2bdb74ee6..a010fbb8a 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -821,6 +821,8 @@ xx(DamageFunction) xx(Length) xx(Unit) xx(Size) +xx(Copy) +xx(Move) xx(Voidptr) xx(StateLabel) xx(SpriteID) diff --git a/src/scripting/codegeneration/codegen.cpp b/src/scripting/codegeneration/codegen.cpp index a3641269f..fadc17269 100644 --- a/src/scripting/codegeneration/codegen.cpp +++ b/src/scripting/codegeneration/codegen.cpp @@ -6946,20 +6946,33 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx) return nullptr; } - PArray *arraytype = dyn_cast(Array->ValueType); - if (arraytype == nullptr) + PArray *arraytype = nullptr; + PType *elementtype = nullptr; + if (Array->IsDynamicArray()) { - // Check if we got a pointer to an array. Some native data structures (like the line list in sectors) use this. - PPointer *ptype = dyn_cast(Array->ValueType); - if (ptype == nullptr || !ptype->PointedType->IsKindOf(RUNTIME_CLASS(PArray))) - { - ScriptPosition.Message(MSG_ERROR, "'[]' can only be used with arrays."); - delete this; - return nullptr; - } - arraytype = static_cast(ptype->PointedType); + PDynArray *darraytype = static_cast(Array->ValueType); + elementtype = darraytype->ElementType; + Array->ValueType = NewPointer(NewResizableArray(elementtype)); // change type so that this can use the code for resizable arrays unchanged. arrayispointer = true; } + else + { + arraytype = dyn_cast(Array->ValueType); + if (arraytype == nullptr) + { + // Check if we got a pointer to an array. Some native data structures (like the line list in sectors) use this. + PPointer *ptype = dyn_cast(Array->ValueType); + if (ptype == nullptr || !ptype->PointedType->IsKindOf(RUNTIME_CLASS(PArray))) + { + ScriptPosition.Message(MSG_ERROR, "'[]' can only be used with arrays."); + delete this; + return nullptr; + } + arraytype = static_cast(ptype->PointedType); + arrayispointer = true; + } + elementtype = arraytype->ElementType; + } if (Array->IsResizableArray()) { @@ -6967,12 +6980,17 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx) if (Array->ExprType == EFX_ClassMember || Array->ExprType == EFX_StructMember) { auto parentfield = static_cast(Array)->membervar; - SizeAddr = parentfield->Offset + parentfield->Type->Align; + SizeAddr = parentfield->Offset + sizeof(void*); } else if (Array->ExprType == EFX_GlobalVariable) { auto parentfield = static_cast(Array)->membervar; - SizeAddr = parentfield->Offset + parentfield->Type->Align; + SizeAddr = parentfield->Offset + sizeof(void*); + } + else if (Array->ExprType == EFX_StackVariable) + { + auto parentfield = static_cast(Array)->membervar; + SizeAddr = parentfield->Offset + sizeof(void*); } else { @@ -6981,54 +6999,52 @@ FxExpression *FxArrayElement::Resolve(FCompileContext &ctx) return nullptr; } } - else if (index->isConstant()) + // constant indices can only be resolved at compile time for statically sized arrays. + else if (index->isConstant() && arraytype != nullptr && !arrayispointer) { unsigned indexval = static_cast(index)->GetValue().GetInt(); - if (indexval >= arraytype->ElementCount && !Array->IsResizableArray()) + if (indexval >= arraytype->ElementCount) { ScriptPosition.Message(MSG_ERROR, "Array index out of bounds"); delete this; return nullptr; } - if (!arrayispointer) + // if this is an array within a class or another struct we can simplify the expression by creating a new PField with a cumulative offset. + if (Array->ExprType == EFX_ClassMember || Array->ExprType == EFX_StructMember) { - // if this is an array within a class or another struct we can simplify the expression by creating a new PField with a cumulative offset. - if (Array->ExprType == EFX_ClassMember || Array->ExprType == EFX_StructMember) - { - auto parentfield = static_cast(Array)->membervar; - // PFields are garbage collected so this will be automatically taken care of later. - auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); - static_cast(Array)->membervar = newfield; - Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. - auto x = Array->Resolve(ctx); - Array = nullptr; - return x; - } - else if (Array->ExprType == EFX_GlobalVariable) - { - auto parentfield = static_cast(Array)->membervar; - auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); - static_cast(Array)->membervar = newfield; - Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. - auto x = Array->Resolve(ctx); - Array = nullptr; - return x; - } - else if (Array->ExprType == EFX_StackVariable) - { - auto parentfield = static_cast(Array)->membervar; - auto newfield = new PField(NAME_None, arraytype->ElementType, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); - static_cast(Array)->ReplaceField(newfield); - Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. - auto x = Array->Resolve(ctx); - Array = nullptr; - return x; - } + auto parentfield = static_cast(Array)->membervar; + // PFields are garbage collected so this will be automatically taken care of later. + auto newfield = new PField(NAME_None, elementtype, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); + static_cast(Array)->membervar = newfield; + Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. + auto x = Array->Resolve(ctx); + Array = nullptr; + return x; + } + else if (Array->ExprType == EFX_GlobalVariable) + { + auto parentfield = static_cast(Array)->membervar; + auto newfield = new PField(NAME_None, elementtype, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); + static_cast(Array)->membervar = newfield; + Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. + auto x = Array->Resolve(ctx); + Array = nullptr; + return x; + } + else if (Array->ExprType == EFX_StackVariable) + { + auto parentfield = static_cast(Array)->membervar; + auto newfield = new PField(NAME_None, elementtype, parentfield->Flags, indexval * arraytype->ElementSize + parentfield->Offset); + static_cast(Array)->ReplaceField(newfield); + Array->isresolved = false; // re-resolve the parent so it can also check if it can be optimized away. + auto x = Array->Resolve(ctx); + Array = nullptr; + return x; } } - ValueType = arraytype->ElementType; + ValueType = elementtype; if (!Array->RequestAddress(ctx, &AddressWritable)) { ScriptPosition.Message(MSG_ERROR, "Unable to dereference array."); @@ -7804,6 +7820,72 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) // same for String methods. It also uses a hidden struct type to define them. Self->ValueType = TypeStringStruct; } + else if (Self->IsDynamicArray()) + { + if (MethodName == NAME_Size) + { + FxExpression *x = new FxMemberIdentifier(Self, NAME_Size, ScriptPosition); // todo: obfuscate the name to prevent direct access. + Self = nullptr; + delete this; + return x->Resolve(ctx); + } + else + { + auto elementType = static_cast(Self->ValueType)->ElementType; + Self->ValueType = static_cast(Self->ValueType)->BackingType; + // this requires some added type checks for the passed types. + for (auto &a : ArgList) + { + a = a->Resolve(ctx); + if (a == nullptr) + { + delete this; + return nullptr; + } + if (a->IsDynamicArray()) + { + // Copy and Move must turn their parameter into a pointer to the backing struct type. + auto backingtype = static_cast(a->ValueType)->BackingType; + a->ValueType = NewPointer(backingtype); + + // Also change the field's type so the code generator can work with this (actually this requires swapping out the entire field.) + if (Self->ExprType == EFX_StructMember || Self->ExprType == EFX_ClassMember) + { + auto member = static_cast(a); + auto newfield = new PField(NAME_None, backingtype, 0, member->membervar->Offset); + member->membervar = newfield; + Self = nullptr; + delete this; + member->ValueType = TypeUInt32; + return member; + } + else if (Self->ExprType == EFX_StackVariable) + { + auto member = static_cast(Self); + auto newfield = new PField(NAME_None, backingtype, 0, member->membervar->Offset); + member->membervar = newfield; + Self = nullptr; + delete this; + member->ValueType = TypeUInt32; + return member; + } + + } + else if (a->IsPointer() && Self->ValueType->IsKindOf(RUNTIME_CLASS(PPointer))) + { + // the only case which must be checked up front is for pointer arrays receiving a new element. + // Since there is only one native backing class it uses a neutral void pointer as its argument, + // meaning that FxMemberFunctionCall is unable to do a proper check. So we have to do it here. + if (a->ValueType != elementType) + { + ScriptPosition.Message(MSG_ERROR, "Type mismatch in function argument. Got %s, expected %s", a->ValueType->DescriptiveName(), elementType->DescriptiveName()); + delete this; + return nullptr; + } + } + } + } + } else if (Self->IsArray()) { if (MethodName == NAME_Size) diff --git a/src/scripting/codegeneration/codegen.h b/src/scripting/codegeneration/codegen.h index 6438e0868..c3af91729 100644 --- a/src/scripting/codegeneration/codegen.h +++ b/src/scripting/codegeneration/codegen.h @@ -333,6 +333,7 @@ public: bool IsObject() const { return ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && !ValueType->IsKindOf(RUNTIME_CLASS(PClassPointer)) && ValueType != TypeNullPtr && static_cast(ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(PClass)); } bool IsArray() const { return ValueType->IsKindOf(RUNTIME_CLASS(PArray)) || (ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && static_cast(ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(PArray))); } bool IsResizableArray() const { return (ValueType->IsKindOf(RUNTIME_CLASS(PPointer)) && static_cast(ValueType)->PointedType->IsKindOf(RUNTIME_CLASS(PResizableArray))); } // can only exist in pointer form. + bool IsDynamicArray() const { return (ValueType->IsKindOf(RUNTIME_CLASS(PDynArray))); } virtual ExpEmit Emit(VMFunctionBuilder *build); void EmitStatement(VMFunctionBuilder *build); diff --git a/src/scripting/zscript/zcc_compile.cpp b/src/scripting/zscript/zcc_compile.cpp index 610e7554d..44428ab88 100644 --- a/src/scripting/zscript/zcc_compile.cpp +++ b/src/scripting/zscript/zcc_compile.cpp @@ -1358,9 +1358,16 @@ PType *ZCCCompiler::DetermineType(PType *outertype, ZCC_TreeNode *field, FName n case AST_DynArrayType: if (allowarraytypes) { - Error(field, "%s: Dynamic array types not implemented yet", name.GetChars()); auto atype = static_cast(ztype); - retval = NewDynArray(DetermineType(outertype, field, name, atype->ElementType, false, false)); + auto ftype = DetermineType(outertype, field, name, atype->ElementType, false, true); + if (ftype->GetRegType() == REGT_NIL || ftype->GetRegCount() > 1) + { + Error(field, "%s: Base type for dynamic array types nust be integral, but got %s", name.GetChars(), ftype->DescriptiveName()); + } + else + { + retval = NewDynArray(ftype); + } break; } break;