diff --git a/packages/compiler-pico-c/src/arch/x86/backend/call-conventions/X86StdcallFnCaller.ts b/packages/compiler-pico-c/src/arch/x86/backend/call-conventions/X86StdcallFnCaller.ts index f6b0b561..19910c3d 100644 --- a/packages/compiler-pico-c/src/arch/x86/backend/call-conventions/X86StdcallFnCaller.ts +++ b/packages/compiler-pico-c/src/arch/x86/backend/call-conventions/X86StdcallFnCaller.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import { CFunctionCallConvention } from '#constants'; import { IRVariable, isIRVariable } from 'frontend/ir/variables'; import { CBackendError, CBackendErrorCode } from 'backend/errors/CBackendError'; -import { isStructLikeType } from 'frontend/analyze'; +import { isStructLikeType, isUnionLikeType } from 'frontend/analyze'; import { getBaseTypeIfPtr } from 'frontend/analyze/types/utils'; import { getTypeOffsetByteSize } from 'frontend/ir/utils'; @@ -63,7 +63,7 @@ export class X86StdcallFnCaller implements X86ConventionalFnCaller { const arg = callerInstruction.args[i]; const baseType = getBaseTypeIfPtr(arg.type); - if (isStructLikeType(baseType)) { + if (isStructLikeType(baseType) || isUnionLikeType(baseType)) { if (!isIRVariable(arg)) { throw new CBackendError(CBackendErrorCode.NON_CALLABLE_STRUCT_ARG); } @@ -230,15 +230,14 @@ export class X86StdcallFnCaller implements X86ConventionalFnCaller { }; } - private getReturnReg({ context, declInstruction }: X86FnBasicCompilerAttrs) { - const regs = context.allocator.regs.ownership.getAvailableRegs(); + private getReturnReg({ declInstruction }: X86FnBasicCompilerAttrs) { const { returnType } = declInstruction; if (!returnType || returnType.isVoid()) { return null; } - return regs.general.list[0]; + return returnType.getByteSize() === 1 ? 'al' : 'ax'; } private preserveConventionRegsAsm({ diff --git a/packages/compiler-pico-c/src/arch/x86/backend/compilers/compileStoreInstruction.ts b/packages/compiler-pico-c/src/arch/x86/backend/compilers/compileStoreInstruction.ts index 6e78d931..0ad4d16d 100644 --- a/packages/compiler-pico-c/src/arch/x86/backend/compilers/compileStoreInstruction.ts +++ b/packages/compiler-pico-c/src/arch/x86/backend/compilers/compileStoreInstruction.ts @@ -67,7 +67,7 @@ export function compileStoreInstruction({ } else { // handle normal variable assign // *(a) = 5; - // todo: check if this .isStruct() is needed: + // todo: check if this .isStructOrUnion() is needed: // char b = 'b'; // int k = b; // struct Abc { @@ -88,8 +88,8 @@ export function compileStoreInstruction({ if (isIRVariable(value)) { // check if variable is struct or something bigger than that if ( - getBaseTypeIfPtr(value.type).isStruct() && - getBaseTypeIfPtr(outputVar.type).isStruct() && + getBaseTypeIfPtr(value.type).isStructOrUnion() && + getBaseTypeIfPtr(outputVar.type).isStructOrUnion() && (!value.isTemporary() || isLabelOwnership(regs.ownership.getVarOwnership(value.name))) ) { diff --git a/packages/compiler-pico-c/src/arch/x86/backend/compilers/data/compileArrayInitializerDef.ts b/packages/compiler-pico-c/src/arch/x86/backend/compilers/data/compileArrayInitializerDef.ts index dd7f3697..6cfd75a9 100644 --- a/packages/compiler-pico-c/src/arch/x86/backend/compilers/data/compileArrayInitializerDef.ts +++ b/packages/compiler-pico-c/src/arch/x86/backend/compilers/data/compileArrayInitializerDef.ts @@ -35,14 +35,14 @@ export function compileArrayInitializerDefAsm({ // [1, 2, "as", 3] -> [[1, 2], "as", [3]] const groupedDefs = initializer.fields.reduce( - ({ prevFieldType, groups }, field) => { - const currentFieldType = typeof field; + ({ prevFieldType, groups }, item) => { + const currentFieldType = typeof item?.value; if (currentFieldType !== prevFieldType) { groups.push([]); } - R.last(groups).push(field); + R.last(groups).push(item?.value); return { prevFieldType: currentFieldType, diff --git a/packages/compiler-pico-c/src/arch/x86/backend/reg-allocator/X86BasicRegAllocator.ts b/packages/compiler-pico-c/src/arch/x86/backend/reg-allocator/X86BasicRegAllocator.ts index e6c5eea3..0e8865f2 100644 --- a/packages/compiler-pico-c/src/arch/x86/backend/reg-allocator/X86BasicRegAllocator.ts +++ b/packages/compiler-pico-c/src/arch/x86/backend/reg-allocator/X86BasicRegAllocator.ts @@ -10,7 +10,7 @@ import { isIRVariable, } from 'frontend/ir/variables'; -import { isStructLikeType } from 'frontend/analyze'; +import { isStructLikeType, isUnionLikeType } from 'frontend/analyze'; import { getByteSizeArgPrefixName } from '@ts-c-compiler/x86-assembler'; import { @@ -123,7 +123,9 @@ export class X86BasicRegAllocator { if ( !castToPointerIfArray(arg.type).isScalar() && - (!isStructLikeType(arg.type) || !arg.type.canBeStoredInReg()) + !isUnionLikeType(arg.type) && + !isStructLikeType(arg.type) && + !arg.type.canBeStoredInReg() ) { throw new CBackendError(CBackendErrorCode.REG_ALLOCATOR_ERROR); } diff --git a/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerPrintVisitor.ts b/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerPrintVisitor.ts index 2aa471ec..cd54eafa 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerPrintVisitor.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerPrintVisitor.ts @@ -1,10 +1,12 @@ +import { isNil } from 'ramda'; import { isTreeNode } from '@ts-c-compiler/grammar'; import { CVariableInitializerVisitor } from './CVariableInitializerVisitor'; import { - CVariableInitializerTree, + CVariableInitializePair, CVariableInitializeValue, - isInitializerTreeValue, + CVariableInitializerTree, + isInitializerValuePair, } from '../../scope/variables/CVariableInitializerTree'; /** @@ -17,23 +19,36 @@ export class CVariableInitializerPrintVisitor extends CVariableInitializerVisito return this._reduced; } - override enter(value: CVariableInitializeValue) { + override enter( + maybePair: CVariableInitializePair | CVariableInitializerTree, + ) { if (this._reduced && this._reduced[this._reduced.length - 2] !== '{') { this._reduced += ', '; } - let serializedValue = value; - if (isInitializerTreeValue(value)) { + let serializedValue: CVariableInitializeValue = ''; + + if (isTreeNode(maybePair)) { serializedValue = '{ '; - } else if (isTreeNode(value)) { - serializedValue = ''; + } else if (isInitializerValuePair(maybePair)) { + if (isTreeNode(maybePair.value)) { + serializedValue = ''; + } else { + serializedValue = maybePair.value; + } + } else if (isNil(maybePair)) { + serializedValue = 'null'; } - this._reduced += serializedValue; + if (serializedValue) { + this._reduced += serializedValue; + } } - override leave(value: CVariableInitializeValue) { - if (isInitializerTreeValue(value)) { + override leave( + maybePair: CVariableInitializePair | CVariableInitializerTree, + ) { + if (isTreeNode(maybePair)) { this._reduced += ' }'; } } diff --git a/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerVisitor.ts b/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerVisitor.ts index 306b895a..f93ce929 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerVisitor.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/CVariableInitializerVisitor.ts @@ -1,10 +1,17 @@ import { isCompilerTreeNode } from 'frontend/parser'; import { AbstractTreeVisitor } from '@ts-c-compiler/grammar'; -import { CVariableInitializeValue } from '../../scope/variables'; +import { + CVariableInitializePair, + CVariableInitializerTree, +} from '../../scope/variables'; -export class CVariableInitializerVisitor extends AbstractTreeVisitor { - shouldVisitNode(node: CVariableInitializeValue): boolean { +export class CVariableInitializerVisitor extends AbstractTreeVisitor< + CVariableInitializePair | CVariableInitializerTree +> { + shouldVisitNode( + node: CVariableInitializePair | CVariableInitializerTree, + ): boolean { return !isCompilerTreeNode(node); } } diff --git a/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/builder/CTypeInitializerBuilderVisitor.ts b/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/builder/CTypeInitializerBuilderVisitor.ts index 0bf80269..f02f92e5 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/builder/CTypeInitializerBuilderVisitor.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/ast/initializer-builder/builder/CTypeInitializerBuilderVisitor.ts @@ -24,11 +24,12 @@ import { typeofValueOrNode, CPrimitiveType, isPrimitiveLikeType, + isUnionLikeType, } from '../../../types'; import { CVariableInitializerTree, - CVariableInitializeValue, + CVariableInitializePair, } from '../../../scope/variables'; import { @@ -86,7 +87,7 @@ export class CTypeInitializerBuilderVisitor extends CInnerTypeTreeVisitor { if (!this.tree) { this.tree = new CVariableInitializerTree(baseType, node); - this.maxSize = this.tree.scalarValuesCount; + this.maxSize = this.tree.c89initializerFieldsCount; } const arrayType = isArrayLikeType(baseType); @@ -222,15 +223,20 @@ export class CTypeInitializerBuilderVisitor extends CInnerTypeTreeVisitor { } } - this.appendNextOffsetValue(exprValue, noSizeCheck); - } else if (isCompilerTreeNode(exprValue)) { this.appendNextOffsetValue( - this.parseTreeNodeExpressionValue(node, expectedType, exprValue), + { type: expectedType, value: exprValue }, + noSizeCheck, ); + } else if (isCompilerTreeNode(exprValue)) { + this.appendNextOffsetValue({ + type: expectedType, + value: this.parseTreeNodeExpressionValue(node, expectedType, exprValue), + }); } else { - this.appendNextOffsetValue( - this.parseScalarValue(node, expectedType, exprValue), - ); + this.appendNextOffsetValue({ + type: expectedType, + value: this.parseScalarValue(node, expectedType, exprValue), + }); } } @@ -249,7 +255,7 @@ export class CTypeInitializerBuilderVisitor extends CInnerTypeTreeVisitor { // .x = 1 if (identifier) { - if (!isStructLikeType(baseType)) { + if (!isStructLikeType(baseType) && !isUnionLikeType(baseType)) { throw new CTypeCheckError( CTypeCheckErrorCode.INCORRECT_NAMED_STRUCTURE_INITIALIZER_USAGE, designation.loc.start, @@ -267,8 +273,11 @@ export class CTypeInitializerBuilderVisitor extends CInnerTypeTreeVisitor { ); } - offset += field.index; baseType = field.type; + + if ('index' in field) { + offset += field.index; + } } // [10] = x @@ -307,7 +316,7 @@ export class CTypeInitializerBuilderVisitor extends CInnerTypeTreeVisitor { offset += +constExprResult.right * dimensions.reduce((acc, num, index) => (index ? acc * num : 1), 1) * - type.scalarValuesCount; + type.c89initializerFieldsCount; } } @@ -420,16 +429,16 @@ export class CTypeInitializerBuilderVisitor extends CInnerTypeTreeVisitor { * Appends next value to tree and increments currentKey if number */ private appendNextOffsetValue( - entryValue: CVariableInitializeValue, + entryValue: CVariableInitializePair, noSizeCheck?: boolean, ) { const { tree, maxSize, baseType } = this; // handle struct Vec2 vec[] = { of_vector(), of_vector() }; initializers const delta = isCompilerTreeNode(entryValue) - ? entryValue.type.scalarValuesCount + ? entryValue.type.c89initializerFieldsCount : 1; - if (isStructLikeType(baseType)) { + if (isStructLikeType(baseType) || isUnionLikeType(baseType)) { // increments offets, determine which field is initialized in struct and sets value // used here: struct Vec2 vec = { 1, 2 }; tree.setAndExpand(this.currentOffset, entryValue); diff --git a/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/creators/ASTCPostfixExpressionTypeCreator.ts b/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/creators/ASTCPostfixExpressionTypeCreator.ts index 19d2792d..5d85c7f9 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/creators/ASTCPostfixExpressionTypeCreator.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/creators/ASTCPostfixExpressionTypeCreator.ts @@ -11,7 +11,9 @@ import { isArrayLikeType, isPointerLikeType, isStructLikeType, + isUnionLikeType, } from '../../../types'; + import { checkLeftTypeOverlapping } from '../../../checker'; export class ASTCPostfixExpressionTypeCreator extends ASTCTypeCreator { @@ -94,7 +96,7 @@ export class ASTCPostfixExpressionTypeCreator extends ASTCTypeCreator var = 2; is not supported in GCC if (hasTypedefs) { - if (+hasPrimitives + +hasStructs + +hasEnums > 0) { + if (+hasPrimitives + +hasStructs + +hasUnions + +hasEnums > 0) { throw new CTypeCheckError(CTypeCheckErrorCode.INCORRECT_TYPE_SPECIFIERS); } @@ -61,8 +63,9 @@ export function extractSpecifierType({ } if ( - +hasPrimitives + +hasStructs + +hasEnums > 1 || + +hasPrimitives + +hasStructs + +hasUnions + +hasEnums > 1 || structs.length > 1 || + unions.length > 1 || enums.length > 1 ) { throw new CTypeCheckError(CTypeCheckErrorCode.INCORRECT_TYPE_SPECIFIERS); @@ -79,7 +82,7 @@ export function extractSpecifierType({ ); } - if (hasEnums || hasStructs) { + if (hasEnums || hasStructs || hasUnions) { let typeName: string = null; const extractors = { extractNamedEntryFromDeclaration, @@ -87,8 +90,22 @@ export function extractSpecifierType({ extractSpecifierType, }; - if (hasStructs) { - const structSpecifier = structs[0].structOrUnionSpecifier; + if (hasUnions) { + const unionSpecifier = unions[0].unionSpecifier; + + // declare + definition + if (unionSpecifier.hasDeclarationList()) { + return extractUnionTypeFromNode({ + ...extractors, + unionSpecifier, + context, + }); + } + + // access by name + typeName = unionSpecifier.name.text; + } else if (hasStructs) { + const structSpecifier = structs[0].structSpecifier; // declare + definition if (structSpecifier.hasDeclarationList()) { diff --git a/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/extractor/extractUnionTypeFromNode.ts b/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/extractor/extractUnionTypeFromNode.ts new file mode 100644 index 00000000..1cf015a8 --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/analyze/ast/type-builder/extractor/extractUnionTypeFromNode.ts @@ -0,0 +1,57 @@ +import { pipe } from 'fp-ts/function'; +import { unwrapEitherOrThrow } from '@ts-c-compiler/core'; + +import { ASTCUnionSpecifier } from 'frontend/parser'; +import { + CTypeCheckError, + CTypeCheckErrorCode, +} from '../../../errors/CTypeCheckError'; + +import { CUnionType } from '../../../types'; +import { TypeExtractorAttrs } from '../constants/types'; + +type UnionTypeExtractorAttrs = TypeExtractorAttrs & { + unionSpecifier: ASTCUnionSpecifier; +}; + +/** + * Walks over struct specifier tree and creates struct type + */ +export function extractUnionTypeFromNode({ + context, + unionSpecifier, + extractSpecifierType, + extractNamedEntryFromDeclarator, +}: UnionTypeExtractorAttrs): CUnionType { + let unionType = CUnionType.ofBlank( + context.config.arch, + unionSpecifier.name?.text, + ); + + // handle const int x, y; + unionSpecifier.list?.children.forEach(declaration => { + const type = extractSpecifierType({ + specifier: declaration.specifierList, + context, + }); + + if (!type) { + throw new CTypeCheckError( + CTypeCheckErrorCode.UNABLE_TO_EXTRACT_STRUCT_TYPE, + ); + } + + // define x, y as separate fields and calculate offsets + declaration.declaratorList.children.forEach(structDeclarator => { + const entry = extractNamedEntryFromDeclarator({ + declarator: structDeclarator.declarator, + context, + type, + }); + + unionType = pipe(unionType.ofAppendedField(entry), unwrapEitherOrThrow); + }); + }); + + return unionType; +} diff --git a/packages/compiler-pico-c/src/frontend/analyze/errors/CTypeCheckError.ts b/packages/compiler-pico-c/src/frontend/analyze/errors/CTypeCheckError.ts index 5ed7a226..285649be 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/errors/CTypeCheckError.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/errors/CTypeCheckError.ts @@ -14,6 +14,7 @@ export enum CTypeCheckErrorCode { REDEFINITION_OF_TYPE, REDEFINITION_OF_VARIABLE, REDEFINITION_OF_STRUCT_ENTRY, + REDEFINITION_OF_UNION_ENTRY, REDEFINITION_OF_ENUM_ENTRY, UNABLE_TO_EXTRACT_STRUCT_TYPE, UNKNOWN_DECLARATOR_ENTRY_IDENTIFIER, @@ -76,6 +77,8 @@ export const C_TYPE_CHECK_ERROR_TRANSLATIONS: Record< 'Redefinition of variable %{name}!', [CTypeCheckErrorCode.REDEFINITION_OF_STRUCT_ENTRY]: 'Redefinition of struct entry %{name}!', + [CTypeCheckErrorCode.REDEFINITION_OF_UNION_ENTRY]: + 'Redefinition of union entry %{name}!', [CTypeCheckErrorCode.REDEFINITION_OF_ENUM_ENTRY]: 'Redefinition of enum entry %{name}!', [CTypeCheckErrorCode.INCORRECT_CONSTANT_EXPR]: diff --git a/packages/compiler-pico-c/src/frontend/analyze/scope/CScopeTree.ts b/packages/compiler-pico-c/src/frontend/analyze/scope/CScopeTree.ts index 4a9ed051..2921e36f 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/scope/CScopeTree.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/scope/CScopeTree.ts @@ -20,6 +20,7 @@ import { CTypedef } from './CTypedef'; type TypeFindAttrs = { struct?: boolean; + union?: boolean; primitive?: boolean; enumerator?: boolean; function?: boolean; @@ -235,6 +236,7 @@ export class CScopeTree function: fn, primitive, struct, + union, enumerator, } = attrs; @@ -243,6 +245,7 @@ export class CScopeTree if ( (primitive && !type.isPrimitive()) || (struct && !type.isStruct()) || + (union && !type.isUnion()) || (enumerator && !type.isEnum()) || (fn && !type.isFunction()) ) { diff --git a/packages/compiler-pico-c/src/frontend/analyze/scope/variables/CVariableInitializerTree.ts b/packages/compiler-pico-c/src/frontend/analyze/scope/variables/CVariableInitializerTree.ts index 3871fba2..59046af9 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/scope/variables/CVariableInitializerTree.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/scope/variables/CVariableInitializerTree.ts @@ -6,6 +6,7 @@ import { isArrayLikeType, isPointerLikeType, isStructLikeType, + isUnionLikeType, } from '../../types'; import { ASTCCompilerNode } from '../../../parser/ast/ASTCCompilerNode'; @@ -17,7 +18,12 @@ export type CVariableInitializeValue = | CVariableInitializerTree | ASTCCompilerNode; -export type CVariableInitializerFields = CVariableInitializeValue[]; +export type CVariableInitializePair = { + type: CType; + value: CVariableInitializeValue; +}; + +export type CVariableInitializerFields = CVariableInitializePair[]; export function isConstantVariableInitializer(value: CVariableInitializeValue) { if (!value) { @@ -35,6 +41,12 @@ export function isInitializerTreeValue( return R.is(Object, value) && R.has('_baseType', value); } +export function isInitializerValuePair( + pair: any, +): pair is CVariableInitializePair { + return !!pair && 'type' in pair && 'value' in pair; +} + type CVariableByteInitializerAttrs = { baseType: CType; parentAST?: C; @@ -82,8 +94,8 @@ export class CVariableInitializerTree< return new CVariableInitializerTree(baseType, parentAST, fields); } - fill(value: CVariableInitializeValue) { - this._fields = new Array(this.scalarValuesCount).fill(value); + fill(value: CVariableInitializePair) { + this._fields = new Array(this.c89initializerFieldsCount).fill(value); } walk(visitor: AbstractTreeVisitor): void { @@ -91,7 +103,9 @@ export class CVariableInitializerTree< } hasOnlyConstantExpressions() { - return this._fields.every(isConstantVariableInitializer); + return this._fields.every(item => + isConstantVariableInitializer(item?.value), + ); } getSingleItemByteSize() { @@ -129,7 +143,7 @@ export class CVariableInitializerTree< /** * Sets values on given offset and if offset is bigger than array fills with null */ - setAndExpand(offset: number, value: CVariableInitializeValue) { + setAndExpand(offset: number, value: CVariableInitializePair) { const { fields } = this; this.ensureSize(offset); @@ -139,7 +153,7 @@ export class CVariableInitializerTree< /** * Used to return value for non-array type initializers */ - getFirstValue(): CVariableInitializeValue { + getFirstValue(): CVariableInitializePair { return this._fields[0]; } @@ -152,9 +166,9 @@ export class CVariableInitializerTree< // parse to return non 1 number: // const char str[] = "asdasdasd"; - return fields.reduce((acc, field) => { - if (R.is(String, field)) { - return acc + field.length; + return fields.reduce((acc, item) => { + if (R.is(String, item?.value)) { + return acc + item.value.length; } return acc + 1; @@ -176,7 +190,7 @@ export class CVariableInitializerTree< return baseType.ofSize( Math.ceil( this.getFlattenNonLiteralScalarFieldsCount() / - baseType.baseType.scalarValuesCount, + baseType.baseType.c89initializerFieldsCount, ), ); } @@ -190,8 +204,8 @@ export class CVariableInitializerTree< getIndexExpectedType(offset: number): CType { const { baseType } = this; - if (isStructLikeType(baseType)) { - return baseType.getFieldTypeByIndex( + if (isStructLikeType(baseType) || isUnionLikeType(baseType)) { + return baseType.getFieldTypeByC89InitializerIndex( offset % baseType.getFlattenFieldsCount(), ); } @@ -199,8 +213,8 @@ export class CVariableInitializerTree< if (isArrayLikeType(baseType)) { const baseArrayType = baseType.getSourceType(); - if (isStructLikeType(baseArrayType)) { - return baseArrayType.getFieldTypeByIndex( + if (isStructLikeType(baseArrayType) || isUnionLikeType(baseArrayType)) { + return baseArrayType.getFieldTypeByC89InitializerIndex( offset % baseArrayType.getFlattenFieldsCount(), ); } @@ -234,7 +248,7 @@ export class CVariableInitializerTree< * @example * int abc[3][4] => 12 */ - get scalarValuesCount(): number { - return this.baseType.scalarValuesCount; + get c89initializerFieldsCount(): number { + return this.baseType.c89initializerFieldsCount; } } diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/CArrayType.ts b/packages/compiler-pico-c/src/frontend/analyze/types/CArrayType.ts index 797af98a..6b953aef 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/types/CArrayType.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/types/CArrayType.ts @@ -83,17 +83,17 @@ export class CArrayType extends CType { } get itemScalarValuesCount() { - return this.baseType.scalarValuesCount; + return this.baseType.c89initializerFieldsCount; } - get scalarValuesCount() { - const { scalarValuesCount } = this.baseType; + get c89initializerFieldsCount() { + const { c89initializerFieldsCount } = this.baseType; - if (this.isUnknownSize() || R.isNil(scalarValuesCount)) { + if (this.isUnknownSize() || R.isNil(c89initializerFieldsCount)) { return null; } - return this.size * scalarValuesCount; + return this.size * c89initializerFieldsCount; } /** diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/CType.ts b/packages/compiler-pico-c/src/frontend/analyze/types/CType.ts index ab1a215a..3ebaabd8 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/types/CType.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/types/CType.ts @@ -39,7 +39,7 @@ export abstract class CType return this.value.qualifiers; } - get scalarValuesCount() { + get c89initializerFieldsCount() { return 1; } @@ -116,6 +116,10 @@ export abstract class CType return false; } + isStructOrUnion() { + return this.isUnion() || this.isStruct(); + } + isFunction() { return false; } @@ -141,7 +145,7 @@ export abstract class CType } hasInnerTypeAttributes() { - return this.isEnum() || this.isStruct(); + return this.isEnum() || this.isStruct() || this.isUnion(); } isConst() { diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/index.ts b/packages/compiler-pico-c/src/frontend/analyze/types/index.ts index fed876c8..bc9f5c58 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/types/index.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/types/index.ts @@ -6,5 +6,6 @@ export * from './CArrayType'; export * from './CFlagType'; export * from './CUnknownType'; export * from './struct'; +export * from './union'; export * from './function'; export * from './utils/typeofValueOrNode'; diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/struct/CStructType.ts b/packages/compiler-pico-c/src/frontend/analyze/types/struct/CStructType.ts index b5dd2fce..c0ff72ea 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/types/struct/CStructType.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/types/struct/CStructType.ts @@ -16,6 +16,7 @@ import { import { CStructTypeDescriptor, CStructEntry } from './constants/types'; import { isArrayLikeType } from '../CArrayType'; +import { isUnionLikeType } from '../union'; export function isStructLikeType(type: CType): type is CStructType { return type?.isStruct(); @@ -50,7 +51,7 @@ export class CStructType extends CType { return this.value.align; } - get scalarValuesCount() { + get c89initializerFieldsCount() { return this.getFlattenFieldsTypes().length; } @@ -85,7 +86,7 @@ export class CStructType extends CType { new CStructEntry({ ...entry.unwrap(), index: prevEntry - ? prevEntry.index + prevEntry.type.scalarValuesCount + ? prevEntry.index + prevEntry.type.c89initializerFieldsCount : 0, offset: alignerFn(this, entry.type), bitset, @@ -204,6 +205,12 @@ export class CStructType extends CType { ]); } + if (isUnionLikeType(type)) { + return type + .getFlattenFieldsTypes() + .map(([UnionName, value]) => [`${name}.${UnionName}`, value, offset]); + } + if (isArrayLikeType(type)) { return R.unnest( R.times<[string, CType]>( @@ -246,7 +253,7 @@ export class CStructType extends CType { return null; } - getFieldTypeByIndex(index: number): CType { + getFieldTypeByC89InitializerIndex(index: number): CType { return this.getFlattenFieldsTypes()[index]?.[1]; } } diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/index.ts b/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/index.ts new file mode 100644 index 00000000..fcb073fe --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/types.ts b/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/types.ts index 90abf208..80e91eb0 100644 --- a/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/types.ts +++ b/packages/compiler-pico-c/src/frontend/analyze/types/struct/constants/types.ts @@ -15,6 +15,7 @@ export class CStructEntry extends CNamedTypedEntry { get offset() { return this.value.offset; } + get index() { return this.value.index; } diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/union/CUnionType.ts b/packages/compiler-pico-c/src/frontend/analyze/types/union/CUnionType.ts new file mode 100644 index 00000000..61fb9f56 --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/analyze/types/union/CUnionType.ts @@ -0,0 +1,221 @@ +import * as R from 'ramda'; +import * as E from 'fp-ts/Either'; + +import { dropNewLines, dumpCompilerAttrs } from '@ts-c-compiler/core'; +import { memoizeMethod } from '@ts-c-compiler/core'; + +import { Identity } from '@ts-c-compiler/core'; +import { CCompilerArch } from '#constants'; +import { CType } from '../CType'; +import { CNamedTypedEntry } from '../../scope/variables/CNamedTypedEntry'; +import { + CTypeCheckError, + CTypeCheckErrorCode, +} from '../../errors/CTypeCheckError'; + +import { CUnionTypeDescriptor, CUnionEntry } from './constants/types'; +import { isArrayLikeType } from '../CArrayType'; + +import type { CStructType } from '../struct'; + +export function isUnionLikeType(type: CType): type is CUnionType { + return type?.isUnion(); +} + +/** + * Defines C-like Union + * + * @see {@link https://www.ibm.com/docs/en/zos/2.2.0?topic=initializers-initialization-structures-unions} + */ +export class CUnionType extends CType { + static ofBlank(arch: CCompilerArch, name?: string) { + return new CUnionType({ + arch, + name, + fields: new Map(), + }); + } + + override isUnion() { + return true; + } + + get name() { + return this.value.name; + } + + get fields() { + return this.value.fields; + } + + /** + * According to standard only first field can be initialized using c89 initializer + */ + get c89initializerFieldsCount() { + const [, { type }] = this.getFieldsList()[0]; + + return type.c89initializerFieldsCount; + } + + /** + * Appends new Union field + */ + ofAppendedField( + entry: CNamedTypedEntry, + ): E.Either { + return this.bind(value => { + const { name } = entry; + + if (this.getField(name)) { + return E.left( + new CTypeCheckError( + CTypeCheckErrorCode.REDEFINITION_OF_UNION_ENTRY, + null, + { + name, + }, + ), + ); + } + + const fieldsList = this.getFieldsList(); + const newEntry: [string, CUnionEntry] = [ + name, + new CUnionEntry(entry.unwrap()), + ]; + + return E.right( + new CUnionType({ + ...value, + fields: new Map([...fieldsList, newEntry]), + }), + ); + }); + } + + /** + * Creates new Union with name + */ + ofName(name: string): CUnionType { + return this.map(value => ({ + ...value, + name, + })); + } + + /** + * Compares Union with nested fields + */ + override isEqual(value: Identity): boolean { + if (!(value instanceof CUnionType)) { + return false; + } + + const [left, right] = [this.getFieldsList(), value.getFieldsList()]; + + if (left.length !== right.length) { + return false; + } + + return !left.some(([name, type], index) => { + const [rightName, rightType] = right[index]; + if (!rightType) { + return true; + } + + return name !== rightName || type !== rightType; + }); + } + + override getByteSize(): number { + return this.getFieldsList().reduce( + (acc, [, entry]) => Math.max(acc, entry.type.getByteSize()), + 0, + ); + } + + /** + * Serialize whole Unionure to string + */ + override getDisplayName(): string { + const { name, arch } = this; + let fields = this.getFieldsList() + .map(([fieldName, entry]) => { + const { type } = entry.unwrap(); + + return ` ${dropNewLines(type.getDisplayName())} ${fieldName};`; + }) + .join('\n'); + + if (!R.isEmpty(fields)) { + fields = `\n${fields}\n`; + } + + const UnionAttrs = dumpCompilerAttrs({ + arch, + sizeof: this.getByteSize(), + }); + + return `${UnionAttrs} union ${name || ''} {${fields}}`; + } + + getFieldsList(): [string, CUnionEntry][] { + return [...this.fields.entries()]; + } + + getField(name: string) { + return this.fields.get(name); + } + + @memoizeMethod + getFlattenFieldsTypes(): [string, CType][] { + const mapUnionEntryPair = (pair: [string, CType]) => { + const [name, type] = pair; + + if (isUnionLikeType(type)) { + return type + .getFlattenFieldsTypes() + .map(([UnionName, value]) => [`${name}.${UnionName}`, value]); + } + + if (type.isStruct()) { + return (type as CStructType) + .getFlattenFieldsTypes() + .map(([UnionName, value]) => [`${name}.${UnionName}`, value]); + } + + if (isArrayLikeType(type)) { + return R.unnest( + R.times<[string, CType]>( + index => mapUnionEntryPair([`${name}.${index}`, type.baseType]), + type.size, + ), + ); + } + + return [pair]; + }; + + return this.getFieldsList().flatMap(([name, { type }]) => + mapUnionEntryPair([name, type]), + ); + } + + getFlattenFieldsCount() { + return this.getFlattenFieldsTypes().length; + } + + getFieldTypeByC89InitializerIndex(index: number): CType { + const [, { type }] = this.getFieldsList()[0]; + + if (isUnionLikeType(type) || type.isStruct()) { + return (type as any).getFieldTypeByC89InitializerIndex(index); + } + + if (isArrayLikeType(type)) { + return type.baseType; + } + + return type; + } +} diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/union/constants/index.ts b/packages/compiler-pico-c/src/frontend/analyze/types/union/constants/index.ts new file mode 100644 index 00000000..fcb073fe --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/analyze/types/union/constants/index.ts @@ -0,0 +1 @@ +export * from './types'; diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/union/constants/types.ts b/packages/compiler-pico-c/src/frontend/analyze/types/union/constants/types.ts new file mode 100644 index 00000000..84865a5a --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/analyze/types/union/constants/types.ts @@ -0,0 +1,11 @@ +import { CTypeDescriptor } from '../../CType'; +import { CNamedTypedEntry } from '../../../scope/variables/CNamedTypedEntry'; + +export class CUnionEntry extends CNamedTypedEntry {} + +export type CUnionFieldsMap = Map; + +export type CUnionTypeDescriptor = CTypeDescriptor & { + name?: string; + fields: CUnionFieldsMap; +}; diff --git a/packages/compiler-pico-c/src/frontend/analyze/types/union/index.ts b/packages/compiler-pico-c/src/frontend/analyze/types/union/index.ts new file mode 100644 index 00000000..d4846c4a --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/analyze/types/union/index.ts @@ -0,0 +1,2 @@ +export * from './CUnionType'; +export * from './constants'; diff --git a/packages/compiler-pico-c/src/frontend/ir/errors/IRError.ts b/packages/compiler-pico-c/src/frontend/ir/errors/IRError.ts index a293c0d8..59b27ac8 100644 --- a/packages/compiler-pico-c/src/frontend/ir/errors/IRError.ts +++ b/packages/compiler-pico-c/src/frontend/ir/errors/IRError.ts @@ -20,6 +20,7 @@ export enum IRErrorCode { MISSING_FUNC_DECL_IN_ALLOCATOR = 'MISSING_FUNC_DECL_IN_ALLOCATOR', EXPECTED_CONDITION_FLAG_RESULT = 'EXPECTED_CONDITION_FLAG_RESULT', MISSING_END_FUNCTION_DECLARATION = 'MISSING_END_FUNCTION_DECLARATION', + GLOBAL_INITIALIZER_MUST_HAVE_ONLY_CONSTANT_EXPRESSIONS = 'GLOBAL_INITIALIZER_MUST_HAVE_ONLY_CONSTANT_EXPRESSIONS', } export const C_IR_ERROR_TRANSLATIONS: Record = { @@ -69,6 +70,8 @@ export const C_IR_ERROR_TRANSLATIONS: Record = { [IRErrorCode.MISSING_END_FUNCTION_DECLARATION]: fixme( 'Missing end function declaration!', ), + [IRErrorCode.GLOBAL_INITIALIZER_MUST_HAVE_ONLY_CONSTANT_EXPRESSIONS]: + 'Global initializer must have only constant expressions!', }; /** diff --git a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-expr/emitExpressionIR.ts b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-expr/emitExpressionIR.ts index fbdf6a2a..19c35ef0 100644 --- a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-expr/emitExpressionIR.ts +++ b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-expr/emitExpressionIR.ts @@ -19,6 +19,7 @@ import { isPointerArithmeticType, isPointerLikeType, isStructLikeType, + isUnionLikeType, } from 'frontend/analyze'; import { @@ -345,7 +346,7 @@ export function emitExpressionIR({ if ( !srcGlobalVar.virtualArrayPtr && - !getBaseTypeIfPtr(srcGlobalVar.type).isStruct() + !getBaseTypeIfPtr(srcGlobalVar.type).isStructOrUnion() ) { const tmpDestVar = allocNextVariable( getBaseTypeIfPtr(srcGlobalVar.type), @@ -375,7 +376,8 @@ export function emitExpressionIR({ instructions.push(new IRLeaInstruction(srcVar, tmpVar)); } else if ( - isStructLikeType(srcVar.type.baseType) && + (isStructLikeType(srcVar.type.baseType) || + isUnionLikeType(srcVar.type.baseType)) && !srcVar.type.baseType.canBeStoredInReg() ) { // handle `a = vec` assign where `vec` is structure that diff --git a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/emitVariableLoadInitializerIR.ts b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/emitVariableLoadInitializerIR.ts index 9cb451cb..8ca713e1 100644 --- a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/emitVariableLoadInitializerIR.ts +++ b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/emitVariableLoadInitializerIR.ts @@ -2,6 +2,7 @@ import * as R from 'ramda'; import { isCompilerTreeNode } from 'frontend/parser'; import { + CPointerType, CVariableInitializerTree, isInitializerTreeValue, } from 'frontend/analyze'; @@ -25,6 +26,7 @@ import { } from './literal'; import { shouldEmitStringPtrInitializer } from './literal/shouldEmitStringPtrInitializer'; +import { getBaseTypeIfPtr } from 'frontend/analyze/types/utils'; type LoadInitializerIREmitAttrs = IREmitterContextAttrs & { initializerTree: CVariableInitializerTree; @@ -41,14 +43,19 @@ export function emitVariableLoadInitializerIR({ context, }: LoadInitializerIREmitAttrs): IREmitterStmtResult { const result = createBlankStmtResult(); + const isDestUnion = getBaseTypeIfPtr(destVar.type).isUnion(); + let offset: number = 0; - initializerTree.fields.forEach((initializer, index) => { + initializerTree.fields.forEach((pair, index) => { + const initializer = pair?.value; + if (isInitializerTreeValue(initializer)) { throw new IRError(IRErrorCode.INCORRECT_INITIALIZER_BLOCK); } - const itemOffsetType = initializerTree.getIndexExpectedType(index); + const itemOffsetType = + pair?.type ?? initializerTree.getIndexExpectedType(index); if (R.is(String, initializer)) { const attrs: StringPtrInitializerLocalIREmitAttrs = { @@ -104,7 +111,9 @@ export function emitVariableLoadInitializerIR({ result.instructions.push( new IRStoreInstruction( IRConstant.ofConstant(itemOffsetType, initializer), - destVar, + isDestUnion + ? destVar.ofType(CPointerType.ofType(itemOffsetType)) + : destVar, offset, ), ); diff --git a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/literal/emitStringLiteralPtrInitializerIR.ts b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/literal/emitStringLiteralPtrInitializerIR.ts index fe2de10d..073318bc 100644 --- a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/literal/emitStringLiteralPtrInitializerIR.ts +++ b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emit-initializer/literal/emitStringLiteralPtrInitializerIR.ts @@ -56,14 +56,13 @@ export function emitStringLiteralPtrInitializerIR({ const constArrayVar = allocator.allocDataVariable(dataType); const dataLabel = IRLabel.ofName(constArrayVar.name); + const literalType = CArrayType.ofStringLiteral(config.arch, literal.length); result.data.push( new IRDefDataInstruction( - new CVariableInitializerTree( - CArrayType.ofStringLiteral(config.arch, literal.length), - null, - [literal], - ), + new CVariableInitializerTree(literalType, null, [ + { type: literalType, value: literal }, + ]), constArrayVar, ), ); diff --git a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitGlobalDeclarationsIR.ts b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitGlobalDeclarationsIR.ts index 83622aa5..f4093643 100644 --- a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitGlobalDeclarationsIR.ts +++ b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitGlobalDeclarationsIR.ts @@ -8,6 +8,7 @@ import { IRDefDataInstruction, type IRInstruction } from '../../instructions'; import { checkIfVirtualGlobalArrayPtr } from '../../utils'; import type { IREmitterContextAttrs } from './types'; +import { IRError, IRErrorCode } from 'frontend/ir/errors/IRError'; type GlobalDeclarationIREmitAttrs = Omit & { globalScope: CScopeTree; @@ -32,6 +33,12 @@ export function emitGlobalDeclarationsIR({ length: variable.type.getByteSize(), }); + if (!initializer.hasOnlyConstantExpressions()) { + throw new IRError( + IRErrorCode.GLOBAL_INITIALIZER_MUST_HAVE_ONLY_CONSTANT_EXPRESSIONS, + ); + } + if ( checkIfVirtualGlobalArrayPtr({ type: variable.type, diff --git a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitIdentifierGetterIR.ts b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitIdentifierGetterIR.ts index 56a1b041..67c0b31f 100644 --- a/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitIdentifierGetterIR.ts +++ b/packages/compiler-pico-c/src/frontend/ir/generator/emitters/emitIdentifierGetterIR.ts @@ -9,6 +9,7 @@ import { isArrayLikeType, isPointerLikeType, isStructLikeType, + isUnionLikeType, } from 'frontend/analyze'; import { CUnaryCastOperator } from '#constants'; @@ -180,7 +181,8 @@ export function emitIdentifierGetterIR({ const parentType = getParentType(); if ( !isPointerLikeType(parentType) || - !isStructLikeType(parentType.baseType) + (!isStructLikeType(parentType.baseType) && + !isUnionLikeType(parentType.baseType)) ) { throw new IRError(IRErrorCode.ACCESS_STRUCT_ATTR_IN_NON_STRUCT); } @@ -194,12 +196,24 @@ export function emitIdentifierGetterIR({ ), ); - const offsetConstant = IRConstant.ofConstant( - CPrimitiveType.int(config.arch), - parentType.baseType.getField(expr.name.text).offset, - ); + if (isUnionLikeType(parentType.baseType)) { + lastIRVar = lastIRVar.ofType( + CPointerType.ofType( + parentType.baseType.getField(expr.name.text).type, + ), + ); + + return false; + } + + const offset = parentType.baseType.getField(expr.name.text).offset; + + if (offset) { + const offsetConstant = IRConstant.ofConstant( + CPrimitiveType.int(config.arch), + offset, + ); - if (offsetConstant.constant) { instructions.push( new IRMathInstruction( TokenType.PLUS, @@ -227,13 +241,14 @@ export function emitIdentifierGetterIR({ } const parentType = getParentType(); - if (!isStructLikeType(parentType)) { + if (!isStructLikeType(parentType) && !isUnionLikeType(parentType)) { throw new IRError(IRErrorCode.ACCESS_STRUCT_ATTR_IN_NON_STRUCT); } if ( isPointerLikeType(lastIRVar.type) && - isStructLikeType(lastIRVar.type.baseType) && + (isStructLikeType(lastIRVar.type.baseType) || + isUnionLikeType(lastIRVar.type.baseType)) && !lastIRVar.isTemporary() ) { instructions.push( @@ -244,12 +259,22 @@ export function emitIdentifierGetterIR({ ); } - const offsetConstant = IRConstant.ofConstant( - CPrimitiveType.int(config.arch), - parentType.getField(expr.name.text).offset, - ); + if (isUnionLikeType(parentType)) { + lastIRVar = lastIRVar.ofType( + CPointerType.ofType(parentType.getField(expr.name.text).type), + ); + + return false; + } + + const offset = parentType.getField(expr.name.text).offset; + + if (offset) { + const offsetConstant = IRConstant.ofConstant( + CPrimitiveType.int(config.arch), + offset, + ); - if (offsetConstant.constant) { instructions.push( new IRMathInstruction( TokenType.PLUS, diff --git a/packages/compiler-pico-c/src/frontend/ir/instructions/IRLeaInstruction.ts b/packages/compiler-pico-c/src/frontend/ir/instructions/IRLeaInstruction.ts index a2147ce2..7c2626e2 100644 --- a/packages/compiler-pico-c/src/frontend/ir/instructions/IRLeaInstruction.ts +++ b/packages/compiler-pico-c/src/frontend/ir/instructions/IRLeaInstruction.ts @@ -18,7 +18,7 @@ export class IRLeaInstruction extends IRInstruction implements IsOutputInstruction { - constructor(readonly inputVar: IRVariable, readonly outputVar: IRVariable) { + constructor(public inputVar: IRVariable, public outputVar: IRVariable) { super(IROpcode.LEA); } diff --git a/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/dropRedundantAddressInstructions.ts b/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/dropRedundantAddressInstructions.ts index 193528bf..b3d94a21 100644 --- a/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/dropRedundantAddressInstructions.ts +++ b/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/dropRedundantAddressInstructions.ts @@ -68,7 +68,15 @@ export function dropRedundantAddressInstructions( ); if (optimizedInstruction) { - newInstructions[i] = optimizedInstruction; + const optimizedArgs = optimizedInstruction.getArgs(); + + // force preserve output type due to issues with unions + newInstructions[i] = optimizedInstruction.ofArgs({ + input: optimizedArgs.input, + output: optimizedArgs.output.ofType( + instruction.getArgs().output.type, + ), + }); } } diff --git a/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/foldAddressOffsetsInstructions.ts b/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/foldAddressOffsetsInstructions.ts index 7e472680..b9a47c2b 100644 --- a/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/foldAddressOffsetsInstructions.ts +++ b/packages/compiler-pico-c/src/frontend/ir/optimizer/block/phases/foldAddressOffsetsInstructions.ts @@ -16,6 +16,7 @@ import { dropConstantInstructionArgs, tryEvalConstArgsBinaryInstruction, } from '../utils'; +import { getBaseTypeIfPtr } from 'frontend/analyze/types/utils'; /** * Reduces: @@ -59,7 +60,9 @@ export function foldAddressOffsetsInstructions(instructions: IRInstruction[]) { ) { newInstructions[i] = instruction.ofArgs({ ...instruction.getArgs(), - output: prevInstruction.inputVar, + output: getBaseTypeIfPtr(prevInstruction.inputVar.type).isUnion() + ? prevInstruction.inputVar.ofType(instruction.outputVar.type) + : prevInstruction.inputVar, }); break; @@ -105,11 +108,17 @@ export function foldAddressOffsetsInstructions(instructions: IRInstruction[]) { break; } + // edge case for unions, it is impossible to deduce type of assigned + // value based on offset so force use last operator type + const isUnion = getBaseTypeIfPtr(leaInstruction.inputVar.type).isUnion(); + newInstructions[i] = instruction .ofOffset(instruction.offset + evalResult) .ofArgs({ ...instruction.getArgs(), - output: leaInstruction.inputVar, + output: isUnion + ? leaInstruction.inputVar.ofType(mathInstruction.outputVar.type) + : leaInstruction.inputVar, }); const replacedOutputs = { diff --git a/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifier.ts b/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifier.ts index 93fe8892..c157a437 100644 --- a/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifier.ts +++ b/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifier.ts @@ -7,10 +7,17 @@ import { CTypeSpecifier } from '../../../constants'; import { ASTCCompilerKind, ASTCCompilerNode } from './ASTCCompilerNode'; import { ASTCEnumSpecifier } from './ASTCEnumSpecifier'; import { ASTCStructSpecifier } from './ASTCStructSpecifier'; +import { ASTCUnionSpecifier } from './ASTCUnionSpecifier'; import { CGrammarTypedefEntry } from '../grammar/matchers'; @walkOverFields({ - fields: ['specifier', 'typeName', 'enumSpecifier', 'structOrUnionSpecifier'], + fields: [ + 'specifier', + 'typeName', + 'enumSpecifier', + 'structSpecifier', + 'unionSpecifier', + ], }) export class ASTCTypeSpecifier extends ASTCCompilerNode { constructor( @@ -18,7 +25,8 @@ export class ASTCTypeSpecifier extends ASTCCompilerNode { readonly specifier?: CTypeSpecifier, readonly typeName?: Token, readonly enumSpecifier?: ASTCEnumSpecifier, - readonly structOrUnionSpecifier?: ASTCStructSpecifier, + readonly structSpecifier?: ASTCStructSpecifier, + readonly unionSpecifier?: ASTCUnionSpecifier, readonly typedefEntry?: CGrammarTypedefEntry, ) { super(ASTCCompilerKind.TypeSpecifier, loc); diff --git a/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifiersList.ts b/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifiersList.ts index e0fdb44c..3e038fdd 100644 --- a/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifiersList.ts +++ b/packages/compiler-pico-c/src/frontend/parser/ast/ASTCTypeSpecifiersList.ts @@ -8,6 +8,14 @@ import { NodeLocation } from '@ts-c-compiler/grammar'; import { ASTCCompilerKind, ASTCCompilerNode } from './ASTCCompilerNode'; import { ASTCTypeSpecifier } from './ASTCTypeSpecifier'; +type GroupedSpecifiersResult = { + primitives: ASTCTypeSpecifier[]; + structs: ASTCTypeSpecifier[]; + unions: ASTCTypeSpecifier[]; + enums: ASTCTypeSpecifier[]; + typedefs: ASTCTypeSpecifier[]; +}; + @walkOverFields({ fields: ['items'], }) @@ -20,19 +28,14 @@ export class ASTCTypeSpecifiersList } getGroupedSpecifiers() { - type Result = { - primitives: ASTCTypeSpecifier[]; - structs: ASTCTypeSpecifier[]; - enums: ASTCTypeSpecifier[]; - typedefs: ASTCTypeSpecifier[]; - }; - - return this.items.reduce( + return this.items.reduce( (acc, item) => { if (item.specifier) { acc.primitives.push(item); - } else if (item.structOrUnionSpecifier) { + } else if (item.structSpecifier) { acc.structs.push(item); + } else if (item.unionSpecifier) { + acc.unions.push(item); } else if (item.enumSpecifier) { acc.enums.push(item); } else if (item.typedefEntry) { @@ -45,6 +48,7 @@ export class ASTCTypeSpecifiersList primitives: [], structs: [], enums: [], + unions: [], typedefs: [], }, ); diff --git a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/index.ts b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/index.ts index cc20998b..0d34f313 100644 --- a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/index.ts +++ b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/index.ts @@ -3,7 +3,7 @@ export * from './declarationSpecifiers'; export * from './functionSpecifier'; export * from './qualifiersSpecifiers'; export * from './storageClassSpecifier'; -export * from './structOrUnionSpecifier'; +export * from './structSpecifier'; export * from './typeQualifier'; export * from './typeQualifiers'; export * from './typeSpecifier'; diff --git a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/structOrUnionSpecifier.ts b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/structOrUnionSpecifier.ts deleted file mode 100644 index 057be9a9..00000000 --- a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/structOrUnionSpecifier.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { NodeLocation } from '@ts-c-compiler/grammar'; - -import { - CCOMPILER_STRUCT_LIKE_SPECIFIERS, - CStructLikeSpecifiers, -} from '#constants'; - -import { - ASTCStructDeclarationList, - ASTCStructSpecifier, - ASTCUnionSpecifier, -} from '../../../ast'; - -import { CGrammar } from '../shared'; -import { structDeclarationList } from '../declarations/structDeclarationList'; - -function structOrUnionConstructor({ g }: CGrammar) { - const typeToken = g.identifier(CCOMPILER_STRUCT_LIKE_SPECIFIERS); - - return { - typeToken, - constructor: - typeToken.value === CStructLikeSpecifiers.STRUCT - ? ASTCStructSpecifier - : ASTCUnionSpecifier, - }; -} - -/** - * struct_or_union_specifier - * : struct_or_union '{' struct_declaration_list '}' - * | struct_or_union IDENTIFIER '{' struct_declaration_list '}' - * | struct_or_union IDENTIFIER - * ; - */ -export function structOrUnionSpecifier(grammar: CGrammar): ASTCStructSpecifier { - const { g } = grammar; - const { typeToken, constructor: StructLikeConstructor } = - structOrUnionConstructor(grammar); - - const name = g.try(() => g.nonIdentifierKeyword()); - let list: ASTCStructDeclarationList = null; - - if (g.currentToken.text === '{') { - g.terminal('{'); - list = structDeclarationList(grammar); - g.terminal('}'); - } - - return new StructLikeConstructor( - NodeLocation.fromTokenLoc(typeToken.loc), - list, - name, - ); -} diff --git a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/structSpecifier.ts b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/structSpecifier.ts new file mode 100644 index 00000000..970bcb20 --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/structSpecifier.ts @@ -0,0 +1,34 @@ +import { NodeLocation } from '@ts-c-compiler/grammar'; + +import { CCompilerKeyword } from '#constants'; +import { ASTCStructDeclarationList, ASTCStructSpecifier } from '../../../ast'; + +import { CGrammar } from '../shared'; +import { structDeclarationList } from '../declarations/structDeclarationList'; + +/** + * struct_specifier + * : struct '{' struct_declaration_list '}' + * | struct IDENTIFIER '{' struct_declaration_list '}' + * | struct IDENTIFIER + * ; + */ +export function structSpecifier(grammar: CGrammar): ASTCStructSpecifier { + const { g } = grammar; + const startToken = g.identifier(CCompilerKeyword.STRUCT); + + const name = g.try(() => g.nonIdentifierKeyword()); + let list: ASTCStructDeclarationList = null; + + if (g.currentToken.text === '{') { + g.terminal('{'); + list = structDeclarationList(grammar); + g.terminal('}'); + } + + return new ASTCStructSpecifier( + NodeLocation.fromTokenLoc(startToken.loc), + list, + name, + ); +} diff --git a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/typeSpecifier.ts b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/typeSpecifier.ts index b1ee1e4c..2b5b78e2 100644 --- a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/typeSpecifier.ts +++ b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/typeSpecifier.ts @@ -2,12 +2,14 @@ import { CCOMPILER_TYPE_SPECIFIERS, CTypeSpecifier } from '#constants'; import { Token, TokenType } from '@ts-c-compiler/lexer'; import { NodeLocation } from '@ts-c-compiler/grammar'; +import { SyntaxError } from '@ts-c-compiler/grammar'; + import { ASTCTypeSpecifier } from 'frontend/parser/ast'; import { CGrammar } from '../shared'; import { enumDeclarator } from '../declarations/enumDeclator'; -import { structOrUnionSpecifier } from './structOrUnionSpecifier'; -import { SyntaxError } from '@ts-c-compiler/grammar'; +import { structSpecifier } from './structSpecifier'; +import { unionSpecifier } from './unionSpecifier'; /** * type_specifier @@ -39,16 +41,15 @@ export function typeSpecifier(grammar: CGrammar): ASTCTypeSpecifier { identifierToken.text as CTypeSpecifier, ); }, + union() { + const union = unionSpecifier(grammar); + + return new ASTCTypeSpecifier(union.loc, null, null, null, null, union); + }, struct() { - const structOrUnion = structOrUnionSpecifier(grammar); + const struct = structSpecifier(grammar); - return new ASTCTypeSpecifier( - structOrUnion.loc, - null, - null, - null, - structOrUnion, - ); + return new ASTCTypeSpecifier(struct.loc, null, null, null, struct); }, enum() { const enumSpecifier = enumDeclarator(grammar); @@ -76,6 +77,7 @@ export function typeSpecifier(grammar: CGrammar): ASTCTypeSpecifier { null, null, null, + null, typedefEntry, ); }, diff --git a/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/unionSpecifier.ts b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/unionSpecifier.ts new file mode 100644 index 00000000..b7de2243 --- /dev/null +++ b/packages/compiler-pico-c/src/frontend/parser/grammar/matchers/specifiers/unionSpecifier.ts @@ -0,0 +1,38 @@ +import { NodeLocation } from '@ts-c-compiler/grammar'; + +import { CCompilerKeyword } from '#constants'; +import { + ASTCStructDeclarationList, + ASTCStructSpecifier, + ASTCUnionSpecifier, +} from '../../../ast'; + +import { CGrammar } from '../shared'; +import { structDeclarationList } from '../declarations/structDeclarationList'; + +/** + * union_specifier + * : union '{' struct_declaration_list '}' + * | union IDENTIFIER '{' struct_declaration_list '}' + * | union IDENTIFIER + * ; + */ +export function unionSpecifier(grammar: CGrammar): ASTCStructSpecifier { + const { g } = grammar; + const startToken = g.identifier(CCompilerKeyword.UNION); + + const name = g.try(() => g.nonIdentifierKeyword()); + let list: ASTCStructDeclarationList = null; + + if (g.currentToken.text === '{') { + g.terminal('{'); + list = structDeclarationList(grammar); + g.terminal('}'); + } + + return new ASTCUnionSpecifier( + NodeLocation.fromTokenLoc(startToken.loc), + list, + name, + ); +} diff --git a/packages/compiler-pico-c/tests/analyze/access.test.ts b/packages/compiler-pico-c/tests/analyze/access.test.ts index a425329f..aedf29f5 100644 --- a/packages/compiler-pico-c/tests/analyze/access.test.ts +++ b/packages/compiler-pico-c/tests/analyze/access.test.ts @@ -14,19 +14,23 @@ describe('Variable access', () => { test('access nested array struct item to int', () => { expect(/* cpp */ ` - struct Vec2 { - int x, y; - struct Rect { int z; } nested[2]; - } abc = { .x = 5 }; + struct Vec2 { + int x, y; + struct Rect { int z; } nested[2]; + } abc = { .x = 5 }; + void main() { int acc = abc.nested[0].z + 4; + } `).not.toHaveCompilerError(); }); test('array like access to array variables', () => { expect(/* cpp */ ` + void main() { int numbers[] = { 1, 2, 3 }; int item = numbers[1]; + } `).not.toHaveCompilerError(); }); @@ -53,10 +57,19 @@ describe('Variable access', () => { `).toHaveCompilerError(CTypeCheckErrorCode.WRONG_NON_STRUCT_FIELD_ACCESS); }); - test('array like access to pointer variables', () => { + test('array like access to pointer local variables', () => { expect(/* cpp */ ` + void main() { const char* str = "Hello world"; char item = str[1]; + } `).not.toHaveCompilerError(); }); + + test('array like access to pointer global variables', () => { + expect(/* cpp */ ` + const char* str = "Hello world"; + char item = str[1]; + `).toHaveCompilerError(); + }); }); diff --git a/packages/compiler-pico-c/tests/analyze/initializer.test.ts b/packages/compiler-pico-c/tests/analyze/initializer.test.ts index 93f844c1..07d1e6d9 100644 --- a/packages/compiler-pico-c/tests/analyze/initializer.test.ts +++ b/packages/compiler-pico-c/tests/analyze/initializer.test.ts @@ -280,10 +280,12 @@ describe('Initializer typecheck', () => { `).not.toHaveCompilerError(); }); - test('dynamic variable initializer', () => { + test('dynamic variable initializer in local variables', () => { expect(/* cpp */ ` - int d = 5; - int acc = d + 4; + void main() { + int d = 5; + int acc = d + 4; + } `).not.toHaveCompilerError(); }); diff --git a/packages/compiler-pico-c/tests/analyze/utils/analyzeMatcher.ts b/packages/compiler-pico-c/tests/analyze/utils/analyzeMatcher.ts index 0c47b96c..8900dd18 100644 --- a/packages/compiler-pico-c/tests/analyze/utils/analyzeMatcher.ts +++ b/packages/compiler-pico-c/tests/analyze/utils/analyzeMatcher.ts @@ -13,7 +13,7 @@ declare global { namespace jest { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Matchers { - toHaveCompilerError(errorCode?: number): MatcherResult; + toHaveCompilerError(errorCode?: number | string): MatcherResult; } } } diff --git a/packages/compiler-pico-c/tests/codegen/declarations/unions.test.ts b/packages/compiler-pico-c/tests/codegen/declarations/unions.test.ts new file mode 100644 index 00000000..d146837f --- /dev/null +++ b/packages/compiler-pico-c/tests/codegen/declarations/unions.test.ts @@ -0,0 +1,265 @@ +import '../utils'; + +describe('Unions', () => { + test('proper assign to int array in union', () => { + expect(/* cpp */ ` + union Data { + int ch[2]; + char k; + }; + + void main() { + union Data data; + + data.ch[1] = 1; + data.ch[0] = 1; + data.k = 5; + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 4 + mov word [bp - 2], 1 ; *(data{0}: int[2]*2B + %2) = store %1: char1B + mov word [bp - 4], 1 ; *(data{0}: int[2]*2B) = store %1: char1B + mov byte [bp - 4], 5 ; *(data{0}: char*2B) = store %5: char1B + mov sp, bp + pop bp + ret + `); + }); + + test('assign to struct inside union', () => { + expect(/* cpp */ ` + union Data { + char i, j; + + struct Vec2 { + int x, y; + char dupa[10]; + } vecs[2]; + + char ch[2]; + }; + + void main() { + union Data data; + + data.vecs[1].x = 2; + data.i = 5; + data.ch[2] = 1; + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 28 + mov word [bp - 14], 2 ; *(data{0}: struct Vec2[2]*2B + %14) = store %2: char1B + mov byte [bp - 28], 5 ; *(data{0}: char*2B) = store %5: char1B + mov byte [bp - 26], 1 ; *(data{0}: char[2]*2B + %2) = store %1: char1B + mov sp, bp + pop bp + ret + `); + }); + + test('assign to array using dynamic index inside union', () => { + expect(/* cpp */ ` + union Data { + int ch[2]; + char k; + }; + + void main() { + int x = 1; + union Data data; + + data.ch[x] = 1; + data.ch[0] = 1; + data.k = 5; + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 6 + mov word [bp - 2], 1 ; *(x{0}: int*2B) = store %1: int2B + lea bx, [bp - 6] ; %t{0}: union Data**2B = lea data{0}: union Data*2B + mov ax, [bp - 2] + shl ax, 1 ; %t{2}: int[2]*2B = %t{1}: int2B mul %2: int2B + add bx, ax ; %t{3}: int[2]*2B = %t{0}: int[2]*2B plus %t{2}: int[2]*2B + mov word [bx], 1 ; *(%t{3}: int[2]*2B) = store %1: char1B + mov word [bp - 6], 1 ; *(data{0}: int[2]*2B) = store %1: char1B + mov byte [bp - 6], 5 ; *(data{0}: char*2B) = store %5: char1B + mov sp, bp + pop bp + ret + `); + }); + + test('union basic initializer', () => { + expect(/* cpp */ ` + union Data { + int ch[2]; + char k; + }; + + void main() { + union Data data = { .k = 5 }; + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 4 + mov byte [bp - 4], 5 ; *(data{0}: char*2B) = store %5: char1B + mov sp, bp + pop bp + ret + `); + }); + + test('union nested array initializer', () => { + expect(/* cpp */ ` + union Data { + int ch[2]; + char k; + }; + + void main() { + union Data data = { .ch = { 1, 2 } }; + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 4 + mov word [bp - 4], 1 ; *(data{0}: int*2B) = store %1: int2B + mov word [bp - 2], 2 ; *(data{0}: int*2B + %2) = store %2: int2B + mov sp, bp + pop bp + ret + `); + }); + + test('union that fits reg is returned from function', () => { + expect(/* cpp */ ` + union Data { + char k; + }; + + union Data fun() { + union Data tmp = { + .k = 2 + }; + + return tmp; + }; + + void main() { + union Data data = fun(); + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def fun(): [ret: union Data1B] + @@_fn_fun: + push bp + mov bp, sp + sub sp, 1 + mov byte [bp - 1], 2 ; *(tmp{0}: char*2B) = store %2: char1B + mov al, [bp - 1] + mov sp, bp + pop bp + ret + + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 1 + call @@_fn_fun + mov byte [bp - 1], al ; *(data{0}: union Data*2B) = store %t{2}: union Data1B + mov sp, bp + pop bp + ret + `); + }); + + test('reading data from union member works', () => { + expect(/* cpp */ ` + typedef union Data { + char k; + int a[4]; + } abc; + + void main() { + abc data = { + .a = { 1, 2, 3, 4 } + }; + + int k = data.a[2]; + asm("xchg dx, dx"); + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 10 + mov word [bp - 8], 1 ; *(data{0}: int*2B) = store %1: int2B + mov word [bp - 6], 2 ; *(data{0}: int*2B + %2) = store %2: int2B + mov word [bp - 4], 3 ; *(data{0}: int*2B + %4) = store %3: int2B + mov word [bp - 2], 4 ; *(data{0}: int*2B + %6) = store %4: int2B + lea bx, [bp - 8] ; %t{0}: union Data**2B = lea data{0}: union Data*2B + add bx, 4 ; %t{1}: int[4]*2B = %t{0}: int[4]*2B plus %4: int2B + mov ax, [bx] ; %t{2}: int2B = load %t{1}: int[4]*2B + mov word [bp - 10], ax ; *(k{0}: int*2B) = store %t{2}: int2B + xchg dx, dx + mov sp, bp + pop bp + ret + `); + }); + + test('c89 initializer for single char value', () => { + expect(/* cpp */ ` + typedef union Data { + char k; + int a[4]; + } abc; + + void main() { + abc data = { 1 }; + + int k = data.a[0]; + asm("xchg dx, dx"); + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 10 + mov byte [bp - 8], 1 ; *(data{0}: char*2B) = store %1: char1B + lea bx, [bp - 8] ; %t{0}: union Data**2B = lea data{0}: union Data*2B + mov ax, [bx] ; %t{2}: int2B = load %t{1}: int[4]*2B + mov word [bp - 10], ax ; *(k{0}: int*2B) = store %t{2}: int2B + xchg dx, dx + mov sp, bp + pop bp + ret + `); + }); +}); diff --git a/packages/compiler-pico-c/tests/codegen/statements/fn.test.ts b/packages/compiler-pico-c/tests/codegen/statements/fn.test.ts index f50d5a85..f157549b 100644 --- a/packages/compiler-pico-c/tests/codegen/statements/fn.test.ts +++ b/packages/compiler-pico-c/tests/codegen/statements/fn.test.ts @@ -36,4 +36,80 @@ describe('Fn statement', () => { ret `); }); + + test('small variables using registers directly to initializer', () => { + expect(/* cpp */ ` + char fn() { + return 2; + } + + int main() { + char a = fn(); + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def fn(): [ret: char1B] + @@_fn_fn: + push bp + mov bp, sp + mov al, byte 2 + mov sp, bp + pop bp + ret + + ; def main(): [ret: int2B] + @@_fn_main: + push bp + mov bp, sp + sub sp, 1 + call @@_fn_fn + mov byte [bp - 1], al ; *(a{0}: char*2B) = store %t{1}: char1B + mov sp, bp + pop bp + ret + `); + }); + + test('small structures using registers directly to initializer', () => { + expect(/* cpp */ ` + struct Data { + char k; + }; + + struct Data fun() { + struct Data tmp = { + .k = 2 + }; + + return tmp; + }; + + void main() { + struct Data data = fun(); + } + `).toCompiledAsmBeEqual(` + cpu 386 + ; def fun(): [ret: struct Data1B] + @@_fn_fun: + push bp + mov bp, sp + sub sp, 1 + mov byte [bp - 1], 2 ; *(tmp{0}: struct Data*2B) = store %2: char1B + mov al, [bp - 1] + mov sp, bp + pop bp + ret + + ; def main(): + @@_fn_main: + push bp + mov bp, sp + sub sp, 1 + call @@_fn_fun + mov byte [bp - 1], al ; *(data{0}: struct Data*2B) = store %t{2}: struct Data1B + mov sp, bp + pop bp + ret + `); + }); }); diff --git a/packages/compiler-pico-c/tests/ir/declarations/array.test.ts b/packages/compiler-pico-c/tests/ir/declarations/array.test.ts index 9dbaff76..d23f12f2 100644 --- a/packages/compiler-pico-c/tests/ir/declarations/array.test.ts +++ b/packages/compiler-pico-c/tests/ir/declarations/array.test.ts @@ -147,19 +147,21 @@ describe('Arrays declarations IR', () => { struct Vec2 vec[] = { of_vector(), of_vector() }; } `).toCompiledIRBeEqual(/* ruby */ ` - # --- Block of_vector --- - def of_vector(rvo: %out{0}: struct Vec2*2B): + # --- Block of_vector --- + def of_vector(rvo: %out{0}: struct Vec2*2B): vec{0}: struct Vec2*2B = alloca struct Vec28B *(vec{0}: struct Vec2*2B) = store %0: int2B *(vec{0}: struct Vec2*2B + %6) = store %0: int2B ret vec{0}: struct Vec2*2B end-def - # --- Block main --- - def main(): - vec{1}: struct Vec2[2]*2B = alloca struct Vec2[2]16B - %t{1}: struct Vec2[2]**2B = lea vec{1}: struct Vec2[2]*2B - call label-offset of_vector :: (%t{1}: struct Vec2[2]**2B) - %t{3}: int*2B = lea vec{1}: struct Vec2[2]*2B + + + # --- Block main --- + def main(): + vec{1}: struct Vec2[1]*2B = alloca struct Vec2[1]8B + %t{1}: struct Vec2[1]**2B = lea vec{1}: struct Vec2[1]*2B + call label-offset of_vector :: (%t{1}: struct Vec2[1]**2B) + %t{3}: int*2B = lea vec{1}: struct Vec2[1]*2B %t{4}: int*2B = %t{3}: int*2B plus %8: int2B call label-offset of_vector :: (%t{4}: int*2B) ret diff --git a/packages/compiler-pico-c/tests/ir/declarations/scope.test.ts b/packages/compiler-pico-c/tests/ir/declarations/scope.test.ts index a1f017d3..b06c15aa 100644 --- a/packages/compiler-pico-c/tests/ir/declarations/scope.test.ts +++ b/packages/compiler-pico-c/tests/ir/declarations/scope.test.ts @@ -1,6 +1,16 @@ +import { IRErrorCode } from '../../../src/frontend/ir/errors/IRError'; import '../utils'; describe('Declaration scope', () => { + test('dynamic variable initializer', () => { + expect(/* cpp */ ` + int d = 5; + int acc = d + 4; + `).toHaveIRError( + IRErrorCode.GLOBAL_INITIALIZER_MUST_HAVE_ONLY_CONSTANT_EXPRESSIONS, + ); + }); + test('should be possible to shadow variable name', () => { expect(/* cpp */ ` void main() { diff --git a/packages/compiler-pico-c/tests/ir/utils/irMatcher.ts b/packages/compiler-pico-c/tests/ir/utils/irMatcher.ts index 50f63b14..1b5056f4 100644 --- a/packages/compiler-pico-c/tests/ir/utils/irMatcher.ts +++ b/packages/compiler-pico-c/tests/ir/utils/irMatcher.ts @@ -1,5 +1,6 @@ import stripAnsi from 'strip-ansi'; import * as E from 'fp-ts/Either'; +import * as R from 'ramda'; import { stripNonPrintableCharacters, trimLines } from '@ts-c-compiler/core'; @@ -16,6 +17,7 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Matchers { toCompiledIRBeEqual(ir: string): MatcherResult; + toHaveIRError(errorCode?: number | string): MatcherResult; } } } @@ -61,6 +63,55 @@ function toCompiledIRBeEqual( }; } +function toHaveIRError(received: string, code?: string): MatcherResult { + const parseResult = cIRCompiler()(received as string); + + if (E.isRight(parseResult)) { + return { + pass: false, + message: () => + `expected err code to be equal ${ + this.utils.printExpected(code) || '' + } but result is ok!`, + }; + } + + const err = parseResult.left; + const pass = (() => { + if (R.isNil(code)) { + return true; + } + + return this.equals( + err, + expect.arrayContaining([ + expect.objectContaining({ + code, + }), + ]), + ); + })(); + + if (pass) { + return { + pass, + message: () => + `expected err code ${this.utils.printReceived( + err[0].code, + )} to be equal ${this.utils.printExpected(code)}`, + }; + } + + return { + pass, + message: () => + `expected err code ${this.utils.printReceived( + err[0].code, + )} to not be equal ${this.utils.printExpected(code)}`, + }; +} + expect.extend({ toCompiledIRBeEqual, + toHaveIRError, });