feat(types): add SmallInt / Word 16-bit integer types
Introduces tySmallInt and tyWord first-class types alongside the existing Integer / Int64 / Byte / UInt32 / UInt64 family. Storage is 2 bytes (storeh / loadsh / loaduh) but values are widened to QBE 'w' in registers, matching the Byte pattern. Int16 and UInt16 are accepted as aliases. Implicit widening into Integer, Int64, UInt32 and UInt64 is permitted; all 16-bit values fit losslessly in those wider types. Tests: 11 IR-level + 5 e2e in cp.test.smallint_word.pas. Grammar and language-rationale updated to remove the "deferred" note on SmallInt/Word.
This commit is contained in:
parent
b43a999f80
commit
5d092cafee
|
|
@ -596,7 +596,8 @@ end;
|
|||
function TCodeGenQBE.QbeTypeOf(AType: TTypeDesc): string;
|
||||
begin
|
||||
case AType.Kind of
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum: Result := 'w';
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tySmallInt, tyWord: Result := 'w';
|
||||
tySet: if TSetTypeDesc(AType).BitCount <= 32 then Result := 'w' else Result := 'l';
|
||||
tyInt64, tyUInt64, tyString: Result := 'l';
|
||||
tyRecord: Result := 'l'; { pointer to aggregate }
|
||||
|
|
@ -623,6 +624,8 @@ function TCodeGenQBE.LoadInstrFor(AType: TTypeDesc): string;
|
|||
begin
|
||||
case AType.Kind of
|
||||
tyByte, tyBoolean: Result := 'loadub';
|
||||
tySmallInt: Result := 'loadsh';
|
||||
tyWord: Result := 'loaduh';
|
||||
tyInteger, tyUInt32, tyEnum: Result := 'loadw';
|
||||
tySet: if TSetTypeDesc(AType).BitCount <= 32 then Result := 'loadw' else Result := 'loadl';
|
||||
tyDouble: Result := 'loadd';
|
||||
|
|
@ -638,6 +641,7 @@ function TCodeGenQBE.StoreInstrFor(AType: TTypeDesc): string;
|
|||
begin
|
||||
case AType.Kind of
|
||||
tyByte, tyBoolean: Result := 'storeb';
|
||||
tySmallInt, tyWord: Result := 'storeh';
|
||||
tyInteger, tyUInt32, tyEnum: Result := 'storew';
|
||||
tySet: if TSetTypeDesc(AType).BitCount <= 32 then Result := 'storew' else Result := 'storel';
|
||||
tyDouble: Result := 'stored';
|
||||
|
|
@ -775,6 +779,7 @@ begin
|
|||
Result := AKind in [
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tyInt64, tyUInt64,
|
||||
tySmallInt, tyWord,
|
||||
tyDouble, tySingle,
|
||||
tySet,
|
||||
tyPointer, tyPChar
|
||||
|
|
@ -784,7 +789,8 @@ end;
|
|||
function TCodeGenQBE.PromotedQType(AKind: TTypeKind; AType: TTypeDesc): string;
|
||||
begin
|
||||
case AKind of
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum: Result := 'w';
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tySmallInt, tyWord: Result := 'w';
|
||||
tyInt64, tyUInt64, tyString, tyClass, tyPointer, tyPChar, tyMetaClass: Result := 'l';
|
||||
tyDouble: Result := 'd';
|
||||
tySingle: Result := 's';
|
||||
|
|
@ -1466,7 +1472,8 @@ begin
|
|||
end;
|
||||
|
||||
case Decl.ResolvedType.Kind of
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum:
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tySmallInt, tyWord:
|
||||
begin
|
||||
EmitLine(Format(' %%_var_%s =l alloc4 1', [VarName]));
|
||||
EmitLine(Format(' storew 0, %%_var_%s', [VarName]));
|
||||
|
|
@ -1612,6 +1619,8 @@ begin
|
|||
case Decl.ResolvedType.Kind of
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum:
|
||||
EmitLine(Format('export data $%s = { w 0 }', [VarName]));
|
||||
tySmallInt, tyWord:
|
||||
EmitLine(Format('export data $%s = { h 0 }', [VarName]));
|
||||
tySet:
|
||||
if TSetTypeDesc(Decl.ResolvedType).BitCount <= 32 then
|
||||
EmitLine(Format('export data $%s = { w 0 }', [VarName]))
|
||||
|
|
@ -2388,6 +2397,8 @@ begin
|
|||
ElemSize := SAT.ElementType.RawSize;
|
||||
case SAT.ElementType.Kind of
|
||||
tyByte, tyBoolean: QLoad := 'loadub';
|
||||
tySmallInt: QLoad := 'loadsh';
|
||||
tyWord: QLoad := 'loaduh';
|
||||
tyInteger, tyUInt32, tyEnum: QLoad := 'loadw';
|
||||
else
|
||||
QLoad := 'loadl';
|
||||
|
|
@ -2513,6 +2524,8 @@ begin
|
|||
ElemSize := DAT.ElementType.RawSize;
|
||||
case DAT.ElementType.Kind of
|
||||
tyByte, tyBoolean: QLoad := 'loadub';
|
||||
tySmallInt: QLoad := 'loadsh';
|
||||
tyWord: QLoad := 'loaduh';
|
||||
tyInteger, tyUInt32, tyEnum: QLoad := 'loadw';
|
||||
else
|
||||
QLoad := 'loadl';
|
||||
|
|
@ -4543,7 +4556,8 @@ begin
|
|||
end
|
||||
else
|
||||
case Par.ResolvedType.Kind of
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum:
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tySmallInt, tyWord:
|
||||
begin
|
||||
EmitLine(Format(' %%_var_%s =l alloc4 1', [Par.ParamName]));
|
||||
EmitLine(Format(' storew %%_par_%s, %%_var_%s',
|
||||
|
|
@ -5338,7 +5352,8 @@ begin
|
|||
else
|
||||
begin
|
||||
case Par.ResolvedType.Kind of
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum:
|
||||
tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tySmallInt, tyWord:
|
||||
begin
|
||||
EmitLine(Format(' %%_var_%s =l alloc4 1', [Par.ParamName]));
|
||||
EmitLine(Format(' storew %%_par_%s, %%_var_%s',
|
||||
|
|
@ -6825,13 +6840,15 @@ begin
|
|||
begin
|
||||
ArgTemp := EmitExpr(TASTExpr(TArrayLiteralExpr(FC.Args.Items[1]).Elements.Items[I]));
|
||||
IsIntArg := TASTExpr(TArrayLiteralExpr(FC.Args.Items[1]).Elements.Items[I]).ResolvedType.Kind in
|
||||
[tyInteger, tyBoolean, tyByte, tyUInt32, tyInt64, tyUInt64, tyEnum];
|
||||
[tyInteger, tyBoolean, tyByte, tyUInt32, tyInt64, tyUInt64,
|
||||
tySmallInt, tyWord, tyEnum];
|
||||
end
|
||||
else
|
||||
begin
|
||||
ArgTemp := EmitExpr(TASTExpr(FC.Args.Items[I + 1]));
|
||||
IsIntArg := TASTExpr(FC.Args.Items[I + 1]).ResolvedType.Kind in
|
||||
[tyInteger, tyBoolean, tyByte, tyUInt32, tyInt64, tyUInt64, tyEnum];
|
||||
[tyInteger, tyBoolean, tyByte, tyUInt32, tyInt64, tyUInt64,
|
||||
tySmallInt, tyWord, tyEnum];
|
||||
end;
|
||||
FmtSlotTemp := AllocTemp;
|
||||
EmitLine(Format(' %s =l add %s, %d', [FmtSlotTemp, FmtArrTemp, I * 16]));
|
||||
|
|
@ -7238,6 +7255,10 @@ begin
|
|||
if FC.ResolvedType.Kind = tyByte then
|
||||
{ Byte(X): truncate to 8 bits — mask to [0..255] }
|
||||
EmitLine(Format(' %s =w and %s, 255', [T, ArgTemp]))
|
||||
else if FC.ResolvedType.Kind in [tySmallInt, tyWord] then
|
||||
{ SmallInt(X) / Word(X): truncate to 16 bits. Sign extension on
|
||||
read is the load instruction's job (loadsh vs loaduh). }
|
||||
EmitLine(Format(' %s =w and %s, 65535', [T, ArgTemp]))
|
||||
else if QType = 'w' then
|
||||
EmitLine(Format(' %s =w copy %s', [T, ArgTemp]))
|
||||
else
|
||||
|
|
@ -8061,12 +8082,7 @@ begin
|
|||
EmitLine(Format(' %s =l add $%s, %s', [L, FldAccess.ConstArraySymbol, Ptr]));
|
||||
QType := QbeTypeOf(SAT.ElementType);
|
||||
T := AllocTemp;
|
||||
case SAT.ElementType.Kind of
|
||||
tyByte, tyBoolean: LoadInstr := 'loadub';
|
||||
tyInteger, tyUInt32, tyEnum: LoadInstr := 'loadw';
|
||||
else
|
||||
LoadInstr := 'loadl';
|
||||
end;
|
||||
LoadInstr := LoadInstrFor(SAT.ElementType);
|
||||
EmitLine(Format(' %s =%s %s %s', [T, QType, LoadInstr, L]));
|
||||
Result := T;
|
||||
Exit;
|
||||
|
|
@ -9688,14 +9704,8 @@ begin
|
|||
Result := ElemPtr;
|
||||
Exit;
|
||||
end;
|
||||
case SAT.ElementType.Kind of
|
||||
tyByte, tyBoolean: QLoad := 'loadub';
|
||||
tyInteger, tyUInt32, tyEnum: QLoad := 'loadw';
|
||||
tyInt64, tyUInt64, tyString, tyClass, tyPointer, tyPChar, tyMetaClass: QLoad := 'loadl';
|
||||
else
|
||||
QLoad := 'loadl';
|
||||
end;
|
||||
QType := QbeTypeOf(SAT.ElementType);
|
||||
QLoad := LoadInstrFor(SAT.ElementType);
|
||||
QType := QbeTypeOf(SAT.ElementType);
|
||||
ByteVal := AllocTemp;
|
||||
EmitLine(Format(' %s =%s %s %s', [ByteVal, QType, QLoad, ElemPtr]));
|
||||
Result := ByteVal;
|
||||
|
|
@ -9727,13 +9737,7 @@ begin
|
|||
if AExpr.StrExpr.ResolvedType.Kind = tyDynArray then
|
||||
begin
|
||||
ElemSize := TDynArrayTypeDesc(AExpr.StrExpr.ResolvedType).ElementType.RawSize;
|
||||
case TDynArrayTypeDesc(AExpr.StrExpr.ResolvedType).ElementType.Kind of
|
||||
tyByte, tyBoolean: QLoad := 'loadub';
|
||||
tyInteger, tyUInt32, tyEnum: QLoad := 'loadw';
|
||||
tyInt64, tyUInt64, tyString, tyClass, tyPointer, tyPChar, tyMetaClass: QLoad := 'loadl';
|
||||
else
|
||||
QLoad := 'loadl';
|
||||
end;
|
||||
QLoad := LoadInstrFor(TDynArrayTypeDesc(AExpr.StrExpr.ResolvedType).ElementType);
|
||||
QType := QbeTypeOf(TDynArrayTypeDesc(AExpr.StrExpr.ResolvedType).ElementType);
|
||||
StrPtr := EmitExpr(AExpr.StrExpr);
|
||||
IdxW := EmitExpr(AExpr.IndexExpr);
|
||||
|
|
@ -9966,6 +9970,7 @@ begin
|
|||
EmitLine(Format(' %s =l add %s, %s', [ElemPtr, PCharBase, Offset]));
|
||||
case ElemType.Kind of
|
||||
tyByte, tyBoolean: StoreInstr := 'storeb';
|
||||
tySmallInt, tyWord: StoreInstr := 'storeh';
|
||||
tyInteger, tyUInt32, tyEnum: StoreInstr := 'storew';
|
||||
else
|
||||
begin
|
||||
|
|
@ -10035,6 +10040,7 @@ begin
|
|||
ElemVal := EmitExpr(AStmt.ValueExpr);
|
||||
case ElemType.Kind of
|
||||
tyByte, tyBoolean: StoreInstr := 'storeb';
|
||||
tySmallInt, tyWord: StoreInstr := 'storeh';
|
||||
tyInteger, tyUInt32, tyEnum: StoreInstr := 'storew';
|
||||
tyInt64, tyUInt64, tyString, tyClass, tyPointer, tyPChar, tyMetaClass: StoreInstr := 'storel';
|
||||
else
|
||||
|
|
|
|||
|
|
@ -298,7 +298,8 @@ begin
|
|||
CName := CanonicalName(AType);
|
||||
if HasBeenEmitted(CName) then Exit;
|
||||
case AType.Kind of
|
||||
tyInteger, tyInt64, tyUInt32, tyUInt64, tyByte, tyBoolean:
|
||||
tyInteger, tyInt64, tyUInt32, tyUInt64,
|
||||
tySmallInt, tyWord, tyByte, tyBoolean:
|
||||
EmitPrimitive(AType);
|
||||
tyString:
|
||||
EmitUtf8Str;
|
||||
|
|
@ -334,7 +335,8 @@ begin
|
|||
SzB := AType.RawSize;
|
||||
case AType.Kind of
|
||||
tyBoolean: begin SubKind := SK_BOOLEAN; IsSigned := 0; end;
|
||||
tyInt64, tyInteger:begin SubKind := SK_INTEGER; IsSigned := 1; end;
|
||||
tyInt64, tyInteger, tySmallInt:
|
||||
begin SubKind := SK_INTEGER; IsSigned := 1; end;
|
||||
else
|
||||
SubKind := SK_INTEGER; IsSigned := 0;
|
||||
end;
|
||||
|
|
|
|||
|
|
@ -331,7 +331,8 @@ begin
|
|||
if SameText(AConstraint, 'record') then
|
||||
begin
|
||||
if not (ArgType.Kind in [tyRecord, tyInteger, tyInt64, tyUInt32, tyUInt64,
|
||||
tyByte, tyBoolean, tyString, tyPointer]) then
|
||||
tySmallInt, tyWord, tyByte, tyBoolean, tyString,
|
||||
tyPointer]) then
|
||||
raise ESemanticError.Create(Format(
|
||||
'Type ''%s'' does not satisfy constraint ''%s: record'' in %s',
|
||||
[AArgName, AParamName, AContext]));
|
||||
|
|
@ -3503,7 +3504,8 @@ begin
|
|||
begin
|
||||
K := ADecl.ResolvedReturnType.Kind;
|
||||
if not (K in [tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tyInt64, tyUInt64, tyDouble, tySingle, tyPointer, tyPChar]) then
|
||||
tyInt64, tyUInt64, tySmallInt, tyWord,
|
||||
tyDouble, tySingle, tyPointer, tyPChar]) then
|
||||
Exit;
|
||||
end;
|
||||
|
||||
|
|
@ -3515,7 +3517,8 @@ begin
|
|||
if Par.ResolvedType = nil then Exit;
|
||||
K := Par.ResolvedType.Kind;
|
||||
if not (K in [tyInteger, tyUInt32, tyBoolean, tyByte, tyEnum,
|
||||
tyInt64, tyUInt64, tyDouble, tySingle, tyPointer, tyPChar]) then
|
||||
tyInt64, tyUInt64, tySmallInt, tyWord,
|
||||
tyDouble, tySingle, tyPointer, tyPChar]) then
|
||||
Exit;
|
||||
end;
|
||||
|
||||
|
|
@ -4567,6 +4570,8 @@ begin
|
|||
tyInt64: Base := 'l';
|
||||
tyUInt32: Base := 'u';
|
||||
tyUInt64: Base := 'Q';
|
||||
tySmallInt: Base := 'h';
|
||||
tyWord: Base := 'H';
|
||||
tyByte: Base := 'y';
|
||||
tyBoolean: Base := 'b';
|
||||
tyDouble: Base := 'd';
|
||||
|
|
@ -4770,7 +4775,8 @@ begin
|
|||
when both are candidates. }
|
||||
if (AArgExpr is TIntLiteral) and AParam.IsNumeric then
|
||||
begin
|
||||
if AParam.Kind in [tyInteger, tyInt64, tyUInt32, tyUInt64, tyByte] then
|
||||
if AParam.Kind in [tyInteger, tyInt64, tyUInt32, tyUInt64,
|
||||
tySmallInt, tyWord, tyByte] then
|
||||
Result := 2
|
||||
else
|
||||
Result := 1; { Double, Single — widening }
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ type
|
|||
tyInt64, { Int64 — QBE 'l' }
|
||||
tyUInt32, { UInt32 — QBE 'w' unsigned }
|
||||
tyUInt64, { UInt64 / QWord — QBE 'l' unsigned }
|
||||
tySmallInt, { SmallInt / Int16 — QBE 'h' (signed half-word) }
|
||||
tyWord, { Word / UInt16 — QBE 'h' (unsigned half-word) }
|
||||
tyByte, { Byte — QBE 'b' }
|
||||
tyBoolean, { Boolean — stored as byte, 0/1 only }
|
||||
tyString, { ARC-managed UTF-8 string }
|
||||
|
|
@ -349,11 +351,13 @@ type
|
|||
FAllTypes: TObjectList; { owned TTypeDesc }
|
||||
FGenerics: TStringList; { non-owning: base name → TGenericTypeDef* (void ptr) }
|
||||
|
||||
FTypeInteger: TTypeDesc;
|
||||
FTypeInt64: TTypeDesc;
|
||||
FTypeUInt32: TTypeDesc;
|
||||
FTypeUInt64: TTypeDesc;
|
||||
FTypeByte: TTypeDesc;
|
||||
FTypeInteger: TTypeDesc;
|
||||
FTypeInt64: TTypeDesc;
|
||||
FTypeUInt32: TTypeDesc;
|
||||
FTypeUInt64: TTypeDesc;
|
||||
FTypeSmallInt: TTypeDesc;
|
||||
FTypeWord: TTypeDesc;
|
||||
FTypeByte: TTypeDesc;
|
||||
FTypeBoolean: TTypeDesc;
|
||||
FTypeString: TTypeDesc;
|
||||
FTypeVoid: TTypeDesc;
|
||||
|
|
@ -420,10 +424,12 @@ type
|
|||
function FindGeneric(const AName: string): TObject;
|
||||
|
||||
{ Convenience type accessors }
|
||||
property TypeInteger: TTypeDesc read FTypeInteger;
|
||||
property TypeInt64: TTypeDesc read FTypeInt64;
|
||||
property TypeUInt32: TTypeDesc read FTypeUInt32;
|
||||
property TypeUInt64: TTypeDesc read FTypeUInt64;
|
||||
property TypeInteger: TTypeDesc read FTypeInteger;
|
||||
property TypeInt64: TTypeDesc read FTypeInt64;
|
||||
property TypeUInt32: TTypeDesc read FTypeUInt32;
|
||||
property TypeUInt64: TTypeDesc read FTypeUInt64;
|
||||
property TypeSmallInt: TTypeDesc read FTypeSmallInt;
|
||||
property TypeWord: TTypeDesc read FTypeWord;
|
||||
property TypeByte: TTypeDesc read FTypeByte;
|
||||
property TypeBoolean: TTypeDesc read FTypeBoolean;
|
||||
property TypeString: TTypeDesc read FTypeString;
|
||||
|
|
@ -443,7 +449,7 @@ implementation
|
|||
|
||||
function TTypeDesc.IsNumeric: Boolean;
|
||||
begin
|
||||
Result := Kind in [tyInteger, tyInt64, tyUInt32, tyUInt64, tyByte, tyEnum, tyDouble, tySingle];
|
||||
Result := Kind in [tyInteger, tyInt64, tyUInt32, tyUInt64, tySmallInt, tyWord, tyByte, tyEnum, tyDouble, tySingle];
|
||||
end;
|
||||
|
||||
function TTypeDesc.IsFloat: Boolean;
|
||||
|
|
@ -458,7 +464,7 @@ end;
|
|||
|
||||
function TTypeDesc.IsOrdinal: Boolean;
|
||||
begin
|
||||
Result := Kind in [tyInteger, tyInt64, tyUInt32, tyUInt64, tyByte, tyBoolean, tyEnum];
|
||||
Result := Kind in [tyInteger, tyInt64, tyUInt32, tyUInt64, tySmallInt, tyWord, tyByte, tyBoolean, tyEnum];
|
||||
end;
|
||||
|
||||
function TTypeDesc.IsRecord: Boolean;
|
||||
|
|
@ -470,6 +476,7 @@ function TTypeDesc.RawSize: Integer;
|
|||
begin
|
||||
case Kind of
|
||||
tyByte, tyBoolean: Result := 1;
|
||||
tySmallInt, tyWord: Result := 2;
|
||||
tyInteger, tyUInt32, tyEnum: Result := 4;
|
||||
tyInt64, tyUInt64, tyString, tyClass, tyPointer, tyNil, tyDouble: Result := 8;
|
||||
tySingle: Result := 4;
|
||||
|
|
@ -492,6 +499,7 @@ begin
|
|||
case Kind of
|
||||
tyInteger, tyUInt32, tyEnum: Result := 4;
|
||||
tyInt64, tyUInt64: Result := 8;
|
||||
tySmallInt, tyWord: Result := 2;
|
||||
tyByte, tyBoolean: Result := 1; { true byte; stack slots are alloc4-padded by codegen }
|
||||
tyString: Result := 8; { pointer size on 64-bit }
|
||||
tyRecord: Result := TRecordTypeDesc(Self).TotalSize;
|
||||
|
|
@ -516,6 +524,7 @@ begin
|
|||
case Kind of
|
||||
tyInteger, tyUInt32, tyEnum: Result := 4;
|
||||
tyByte, tyBoolean: Result := 1; { natural alignment }
|
||||
tySmallInt, tyWord: Result := 2;
|
||||
tyInt64, tyUInt64, tyString: Result := 8;
|
||||
tyRecord: Result := TRecordTypeDesc(Self).MaxAlign;
|
||||
tySet: if TSetTypeDesc(Self).BitCount <= 32 then Result := 4 else Result := 8;
|
||||
|
|
@ -1205,11 +1214,13 @@ var
|
|||
TMethodDesc: TRecordTypeDesc;
|
||||
begin
|
||||
{ Primitive types }
|
||||
FTypeInteger := NewType(tyInteger, 'Integer');
|
||||
FTypeInt64 := NewType(tyInt64, 'Int64');
|
||||
FTypeUInt32 := NewType(tyUInt32, 'UInt32');
|
||||
FTypeUInt64 := NewType(tyUInt64, 'UInt64');
|
||||
FTypeByte := NewType(tyByte, 'Byte');
|
||||
FTypeInteger := NewType(tyInteger, 'Integer');
|
||||
FTypeInt64 := NewType(tyInt64, 'Int64');
|
||||
FTypeUInt32 := NewType(tyUInt32, 'UInt32');
|
||||
FTypeUInt64 := NewType(tyUInt64, 'UInt64');
|
||||
FTypeSmallInt := NewType(tySmallInt, 'SmallInt');
|
||||
FTypeWord := NewType(tyWord, 'Word');
|
||||
FTypeByte := NewType(tyByte, 'Byte');
|
||||
FTypeBoolean := NewType(tyBoolean, 'Boolean');
|
||||
FTypeString := NewType(tyString, 'string');
|
||||
FTypeVoid := NewType(tyVoid, 'void');
|
||||
|
|
@ -1227,6 +1238,10 @@ begin
|
|||
Define(TSymbol.Create('UInt64', skType, FTypeUInt64));
|
||||
Define(TSymbol.Create('QWord', skType, FTypeUInt64)); { FPC alias }
|
||||
Define(TSymbol.Create('PtrUInt', skType, FTypeUInt64)); { pointer-sized unsigned on 64-bit }
|
||||
Define(TSymbol.Create('SmallInt', skType, FTypeSmallInt));
|
||||
Define(TSymbol.Create('Int16', skType, FTypeSmallInt)); { Delphi alias }
|
||||
Define(TSymbol.Create('Word', skType, FTypeWord));
|
||||
Define(TSymbol.Create('UInt16', skType, FTypeWord)); { Delphi alias }
|
||||
Define(TSymbol.Create('Byte', skType, FTypeByte));
|
||||
Define(TSymbol.Create('Boolean', skType, FTypeBoolean));
|
||||
Define(TSymbol.Create('string', skType, FTypeString));
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ uses
|
|||
blaise.testing.runner.text,
|
||||
cp.test.runner_filters,
|
||||
cp.test.uint64,
|
||||
cp.test.smallint_word,
|
||||
cp.test.lexer,
|
||||
cp.test.parser,
|
||||
cp.test.codegen,
|
||||
|
|
|
|||
434
compiler/src/test/pascal/cp.test.smallint_word.pas
Normal file
434
compiler/src/test/pascal/cp.test.smallint_word.pas
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
{
|
||||
Blaise - An Object Pascal Compiler
|
||||
Copyright (c) 2026 Graeme Geldenhuys
|
||||
SPDX-License-Identifier: Apache-2.0 WITH Swift-exception
|
||||
Licensed under the Apache License v2.0 with Runtime Library Exception.
|
||||
See LICENSE file in the project root for full license terms.
|
||||
}
|
||||
|
||||
unit cp.test.smallint_word;
|
||||
|
||||
{ Tests for the 16-bit integer types SmallInt (signed) and Word (unsigned),
|
||||
plus their Delphi-style aliases Int16 and UInt16.
|
||||
|
||||
Coverage:
|
||||
- All four names resolve, SmallInt/Int16 and Word/UInt16 share descriptors.
|
||||
- SizeOf is 2 for every name.
|
||||
- Fields are packed at 2-byte stride; mixed records align correctly.
|
||||
- Load/store use loadsh/loaduh/storeh (16-bit half-word ops).
|
||||
- Integer/UInt32 widen implicitly in expressions.
|
||||
- WriteLn and IntToStr handle both signed and unsigned values. }
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
blaise.testing, cp.test.e2e.base,
|
||||
uLexer, uParser, uAST, uSymbolTable, uSemantic, uCodeGenQBE;
|
||||
|
||||
type
|
||||
TSmallIntWordTests = class(TTestCase)
|
||||
private
|
||||
function AnalyseSrc(const ASrc: string): TProgram;
|
||||
function GenIR(const ASrc: string): string;
|
||||
published
|
||||
procedure TestSemantic_SmallInt_TypeRegistered;
|
||||
procedure TestSemantic_Word_TypeRegistered;
|
||||
procedure TestSemantic_Int16_AliasOfSmallInt;
|
||||
procedure TestSemantic_UInt16_AliasOfWord;
|
||||
procedure TestSemantic_SizeOf_SmallInt_Is2;
|
||||
procedure TestSemantic_SizeOf_Word_Is2;
|
||||
procedure TestSemantic_SmallIntField_RecordPacks;
|
||||
procedure TestSemantic_MixedRecord_AlignsTo16;
|
||||
procedure TestCodegen_SmallIntField_UsesLoadsh;
|
||||
procedure TestCodegen_WordField_UsesLoaduh;
|
||||
procedure TestCodegen_SmallIntField_UsesStoreh;
|
||||
end;
|
||||
|
||||
[Threaded]
|
||||
TSmallIntWordE2ETests = class(TE2ETestCase)
|
||||
protected
|
||||
procedure SetUp; override;
|
||||
published
|
||||
procedure TestRun_SmallInt_RoundTrip;
|
||||
procedure TestRun_Word_RoundTrip;
|
||||
procedure TestRun_SmallInt_Negative;
|
||||
procedure TestRun_Word_MaxValue;
|
||||
procedure TestRun_MixedRecord_RoundTrip;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
function TSmallIntWordTests.AnalyseSrc(const ASrc: string): TProgram;
|
||||
var
|
||||
L: TLexer;
|
||||
P: TParser;
|
||||
A: TSemanticAnalyser;
|
||||
begin
|
||||
L := TLexer.Create(ASrc);
|
||||
P := TParser.Create(L);
|
||||
try
|
||||
Result := P.Parse;
|
||||
finally
|
||||
P.Free;
|
||||
L.Free;
|
||||
end;
|
||||
A := TSemanticAnalyser.Create;
|
||||
try
|
||||
A.Analyse(Result);
|
||||
finally
|
||||
A.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TSmallIntWordTests.GenIR(const ASrc: string): string;
|
||||
var
|
||||
Prog: TProgram;
|
||||
CG: TCodeGenQBE;
|
||||
begin
|
||||
Prog := AnalyseSrc(ASrc);
|
||||
try
|
||||
CG := TCodeGenQBE.Create;
|
||||
try
|
||||
CG.Generate(Prog);
|
||||
Result := CG.GetOutput;
|
||||
finally
|
||||
CG.Free;
|
||||
end;
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_SmallInt_TypeRegistered;
|
||||
const
|
||||
Src = '''
|
||||
program P;
|
||||
var X: SmallInt;
|
||||
begin end.
|
||||
''';
|
||||
var
|
||||
Prog: TProgram;
|
||||
T: TTypeDesc;
|
||||
begin
|
||||
Prog := AnalyseSrc(Src);
|
||||
try
|
||||
T := TVarDecl(Prog.Block.Decls.Items[0]).ResolvedType;
|
||||
AssertTrue('resolves', T <> nil);
|
||||
AssertEquals('Kind = tySmallInt', Ord(tySmallInt), Ord(T.Kind));
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_Word_TypeRegistered;
|
||||
const
|
||||
Src = '''
|
||||
program P;
|
||||
var X: Word;
|
||||
begin end.
|
||||
''';
|
||||
var
|
||||
Prog: TProgram;
|
||||
T: TTypeDesc;
|
||||
begin
|
||||
Prog := AnalyseSrc(Src);
|
||||
try
|
||||
T := TVarDecl(Prog.Block.Decls.Items[0]).ResolvedType;
|
||||
AssertTrue('resolves', T <> nil);
|
||||
AssertEquals('Kind = tyWord', Ord(tyWord), Ord(T.Kind));
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_Int16_AliasOfSmallInt;
|
||||
const
|
||||
Src = '''
|
||||
program P;
|
||||
var A: SmallInt;
|
||||
var B: Int16;
|
||||
begin end.
|
||||
''';
|
||||
var
|
||||
Prog: TProgram;
|
||||
begin
|
||||
Prog := AnalyseSrc(Src);
|
||||
try
|
||||
AssertSame('SmallInt and Int16 share descriptor',
|
||||
TVarDecl(Prog.Block.Decls.Items[0]).ResolvedType,
|
||||
TVarDecl(Prog.Block.Decls.Items[1]).ResolvedType);
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_UInt16_AliasOfWord;
|
||||
const
|
||||
Src = '''
|
||||
program P;
|
||||
var A: Word;
|
||||
var B: UInt16;
|
||||
begin end.
|
||||
''';
|
||||
var
|
||||
Prog: TProgram;
|
||||
begin
|
||||
Prog := AnalyseSrc(Src);
|
||||
try
|
||||
AssertSame('Word and UInt16 share descriptor',
|
||||
TVarDecl(Prog.Block.Decls.Items[0]).ResolvedType,
|
||||
TVarDecl(Prog.Block.Decls.Items[1]).ResolvedType);
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_SizeOf_SmallInt_Is2;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
var N: Integer;
|
||||
begin N := SizeOf(SmallInt) end.
|
||||
''');
|
||||
AssertTrue('SizeOf(SmallInt) is 2', Pos('copy 2', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_SizeOf_Word_Is2;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
var N: Integer;
|
||||
begin N := SizeOf(Word) end.
|
||||
''');
|
||||
AssertTrue('SizeOf(Word) is 2', Pos('copy 2', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_SmallIntField_RecordPacks;
|
||||
const
|
||||
Src = '''
|
||||
program P;
|
||||
type
|
||||
TFour = record
|
||||
A: SmallInt;
|
||||
B: SmallInt;
|
||||
C: SmallInt;
|
||||
D: SmallInt;
|
||||
end;
|
||||
var R: TFour;
|
||||
begin end.
|
||||
''';
|
||||
var
|
||||
Prog: TProgram;
|
||||
RT: TRecordTypeDesc;
|
||||
begin
|
||||
Prog := AnalyseSrc(Src);
|
||||
try
|
||||
RT := TRecordTypeDesc(TVarDecl(Prog.Block.Decls.Items[0]).ResolvedType);
|
||||
AssertEquals('A at 0', 0, RT.FindField('A').Offset);
|
||||
AssertEquals('B at 2', 2, RT.FindField('B').Offset);
|
||||
AssertEquals('C at 4', 4, RT.FindField('C').Offset);
|
||||
AssertEquals('D at 6', 6, RT.FindField('D').Offset);
|
||||
AssertEquals('total 8', 8, RT.TotalSize);
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestSemantic_MixedRecord_AlignsTo16;
|
||||
const
|
||||
{ Byte + SmallInt + Integer = 1 + (pad 1) + 2 + 4 = 8 bytes. }
|
||||
Src = '''
|
||||
program P;
|
||||
type
|
||||
TMixed = record
|
||||
Tag: Byte;
|
||||
ID: SmallInt;
|
||||
Val: Integer;
|
||||
end;
|
||||
var R: TMixed;
|
||||
begin end.
|
||||
''';
|
||||
var
|
||||
Prog: TProgram;
|
||||
RT: TRecordTypeDesc;
|
||||
begin
|
||||
Prog := AnalyseSrc(Src);
|
||||
try
|
||||
RT := TRecordTypeDesc(TVarDecl(Prog.Block.Decls.Items[0]).ResolvedType);
|
||||
AssertEquals('Tag at 0', 0, RT.FindField('Tag').Offset);
|
||||
AssertEquals('ID at 2 (after 1-byte pad)', 2, RT.FindField('ID').Offset);
|
||||
AssertEquals('Val at 4', 4, RT.FindField('Val').Offset);
|
||||
AssertEquals('total 8', 8, RT.TotalSize);
|
||||
finally
|
||||
Prog.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestCodegen_SmallIntField_UsesLoadsh;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
type TR = record V: SmallInt; end;
|
||||
var R: TR;
|
||||
var X: Integer;
|
||||
begin X := R.V end.
|
||||
''');
|
||||
AssertTrue('SmallInt field load uses loadsh', Pos('loadsh', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestCodegen_WordField_UsesLoaduh;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
type TR = record V: Word; end;
|
||||
var R: TR;
|
||||
var X: Integer;
|
||||
begin X := R.V end.
|
||||
''');
|
||||
AssertTrue('Word field load uses loaduh', Pos('loaduh', IR) > 0);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordTests.TestCodegen_SmallIntField_UsesStoreh;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
IR := GenIR(
|
||||
'''
|
||||
program P;
|
||||
type TR = record V: SmallInt; end;
|
||||
var R: TR;
|
||||
begin R.V := 42 end.
|
||||
''');
|
||||
AssertTrue('SmallInt field store uses storeh', Pos('storeh', IR) > 0);
|
||||
end;
|
||||
|
||||
{ ---------- e2e ---------- }
|
||||
|
||||
procedure TSmallIntWordE2ETests.SetUp;
|
||||
begin
|
||||
inherited SetUp;
|
||||
SetUpScratch('compiler/target/test-e2e-smallint-word');
|
||||
end;
|
||||
|
||||
const
|
||||
LE = #10;
|
||||
|
||||
SrcSmallIntRoundTrip = '''
|
||||
program P;
|
||||
var X: SmallInt;
|
||||
begin
|
||||
X := 12345;
|
||||
WriteLn(X)
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcWordRoundTrip = '''
|
||||
program P;
|
||||
var X: Word;
|
||||
begin
|
||||
X := 60000;
|
||||
WriteLn(X)
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcSmallIntNegative = '''
|
||||
program P;
|
||||
var X: SmallInt;
|
||||
begin
|
||||
X := -1;
|
||||
WriteLn(X)
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcWordMax = '''
|
||||
program P;
|
||||
var X: Word;
|
||||
begin
|
||||
X := 65535;
|
||||
WriteLn(X)
|
||||
end.
|
||||
''';
|
||||
|
||||
SrcMixedRecord = '''
|
||||
program P;
|
||||
type
|
||||
TMixed = record
|
||||
Tag: Byte;
|
||||
ID: SmallInt;
|
||||
Val: Integer;
|
||||
end;
|
||||
var R: TMixed;
|
||||
begin
|
||||
R.Tag := 9;
|
||||
R.ID := -100;
|
||||
R.Val := 12345;
|
||||
WriteLn(R.Tag);
|
||||
WriteLn(R.ID);
|
||||
WriteLn(R.Val);
|
||||
WriteLn(SizeOf(TMixed))
|
||||
end.
|
||||
''';
|
||||
|
||||
procedure TSmallIntWordE2ETests.TestRun_SmallInt_RoundTrip;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcSmallIntRoundTrip, Output, RCode));
|
||||
AssertEquals('exit 0', 0, RCode);
|
||||
AssertEquals('12345', '12345' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordE2ETests.TestRun_Word_RoundTrip;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcWordRoundTrip, Output, RCode));
|
||||
AssertEquals('exit 0', 0, RCode);
|
||||
AssertEquals('60000', '60000' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordE2ETests.TestRun_SmallInt_Negative;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcSmallIntNegative, Output, RCode));
|
||||
AssertEquals('exit 0', 0, RCode);
|
||||
AssertEquals('-1 sign-extended', '-1' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordE2ETests.TestRun_Word_MaxValue;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcWordMax, Output, RCode));
|
||||
AssertEquals('exit 0', 0, RCode);
|
||||
AssertEquals('65535 zero-extended', '65535' + LE, Output);
|
||||
end;
|
||||
|
||||
procedure TSmallIntWordE2ETests.TestRun_MixedRecord_RoundTrip;
|
||||
var Output: string; RCode: Integer;
|
||||
begin
|
||||
if not ToolchainAvailable then begin Ignore('toolchain unavailable'); Exit; end;
|
||||
AssertTrue('compile+run', CompileAndRun(SrcMixedRecord, Output, RCode));
|
||||
AssertEquals('exit 0', 0, RCode);
|
||||
AssertEquals('Tag, ID, Val, SizeOf',
|
||||
'9' + LE + '-100' + LE + '12345' + LE + '8' + LE, Output);
|
||||
end;
|
||||
|
||||
initialization
|
||||
RegisterTest(TSmallIntWordTests);
|
||||
RegisterTest(TSmallIntWordE2ETests);
|
||||
|
||||
end.
|
||||
|
|
@ -787,8 +787,12 @@ ArrayLiteral
|
|||
* Alias: Cardinal
|
||||
* UInt64 — 64-bit unsigned integer (QBE type: l)
|
||||
* Aliases: QWord, PtrUInt (the pointer-sized unsigned)
|
||||
* SmallInt — 16-bit signed integer (QBE type: w, stored as h)
|
||||
* Alias: Int16
|
||||
* Word — 16-bit unsigned integer (QBE type: w, stored as h)
|
||||
* Alias: UInt16
|
||||
* Boolean — 1-byte boolean (QBE type: w; 0 = false, 1 = true)
|
||||
* Byte — 8-bit unsigned integer (QBE type: w)
|
||||
* Byte — 8-bit unsigned integer (QBE type: w, stored as b)
|
||||
* String — ARC-managed UTF-8 string pointer (QBE type: l)
|
||||
* Pointer — untyped pointer (QBE type: l)
|
||||
* ^T — typed pointer to T; created on demand (QBE type: l)
|
||||
|
|
|
|||
|
|
@ -490,6 +490,8 @@ Blaise defines five integer types with fixed, platform-independent widths:
|
|||
| `Int64` | 64-bit signed | `l` |
|
||||
| `UInt32` | 32-bit unsigned | `w` | `Cardinal`
|
||||
| `UInt64` | 64-bit unsigned | `l` | `QWord`, `PtrUInt`
|
||||
| `SmallInt`| 16-bit signed | `w` (2 bytes in storage) | `Int16`
|
||||
| `Word` | 16-bit unsigned | `w` (2 bytes in storage) | `UInt16`
|
||||
| `Byte` | 8-bit unsigned | `w` (1 byte in storage) |
|
||||
|===
|
||||
|
||||
|
|
@ -521,6 +523,22 @@ negative `Int64` becomes a huge `UInt64` (or vice versa) without
|
|||
warning. All narrower integer types (`Integer`, `UInt32`, `Byte`) widen
|
||||
implicitly into both `Int64` and `UInt64`.
|
||||
|
||||
==== 16-bit integer types: SmallInt / Word
|
||||
|
||||
`SmallInt` (16-bit signed, range −32768..32767) and `Word` (16-bit
|
||||
unsigned, range 0..65535) are provided alongside the established
|
||||
32-/64-bit integer types so that code ported from FPC or Delphi
|
||||
compiles unchanged and so that `record` field layouts can match
|
||||
fixed-width binary formats without packing tricks.
|
||||
|
||||
In registers and temporaries both types are widened to `w` (32-bit
|
||||
QBE word); only field and array element storage uses 2 bytes per
|
||||
element (`storeh` / `loadsh` for `SmallInt`, `loaduh` for `Word`).
|
||||
Implicit widening into `Integer`, `Int64`, `UInt32` and `UInt64` is
|
||||
permitted in the same way `Byte` widens, since 16-bit values fit
|
||||
losslessly in all of those. `Int16` and `UInt16` are accepted as
|
||||
aliases for `SmallInt` and `Word` respectively.
|
||||
|
||||
==== Integer literal typing
|
||||
|
||||
Untyped integer literals are typed by magnitude:
|
||||
|
|
@ -570,11 +588,11 @@ for any realistic string length.
|
|||
interoperating with existing 32-bit FPC code or C functions that expect
|
||||
`int32_t`. Explicit `Int64` covers the cases that genuinely need 64 bits.
|
||||
|
||||
* *`LongInt`, `SmallInt`, `Word` aliases* — deferred. These FPC/Delphi
|
||||
aliases map to fixed widths (32-bit, 16-bit, 16-bit unsigned) and
|
||||
could be added as type aliases in a future release alongside true
|
||||
`SmallInt` / `Word` types. `Cardinal` is provided as an alias for
|
||||
`UInt32` since the unsigned-32 type is already present.
|
||||
* *`LongInt` alias* — deferred. The FPC/Delphi `LongInt` name maps to
|
||||
a 32-bit signed integer (identical to Blaise's `Integer`) and could
|
||||
be added as a plain type alias in a future release. `SmallInt`,
|
||||
`Int16`, `Word` and `UInt16` are now first-class 16-bit types, and
|
||||
`Cardinal` remains an alias for `UInt32`.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue