diff --git a/Cesium.CodeGen.Tests/CodeGenArrayTests.cs b/Cesium.CodeGen.Tests/CodeGenArrayTests.cs index 958a20a1..4d0a7d2a 100644 --- a/Cesium.CodeGen.Tests/CodeGenArrayTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenArrayTests.cs @@ -119,4 +119,21 @@ public Task ArrayInitializationWithoutSize() => DoTest(@"int main() { [Fact] public Task ArrayParameterPassing() => DoTest(@" int foo(int ints[]) { return ints[0]; }"); + + [Fact] + public Task ArrayOverPointer() => DoTest(@"int main(int argc, char** argv) { + char c = argv[0][0]; + return argv[1]; + }"); + + [Fact] + public Task PointerArrayIndexing() => DoTest(@" +int f(char*** t) { + char* c = t[2][3]; + return c[1]; +} + +int main() { + return 42; + }"); } diff --git a/Cesium.CodeGen.Tests/CodeGenMethodTests.cs b/Cesium.CodeGen.Tests/CodeGenMethodTests.cs index 84a61672..a0e239c3 100644 --- a/Cesium.CodeGen.Tests/CodeGenMethodTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenMethodTests.cs @@ -191,6 +191,12 @@ public Task CanHaveTwoFunctionDeclarations() => DoTest(@" int console_read(void) { return 0; }"); + [Fact] + public Task CanHaveTwoFunctionDeclarationsWithDifferentParameterNames() => DoTest(@" +int console_read(int argc); + +int console_read(int __argc) { return 0; }"); + [Fact] public void DoubleDefinition() => DoesNotCompile(@"int console_read() { return 1; } int console_read() { return 2; }", "Double definition of function console_read."); diff --git a/Cesium.CodeGen.Tests/CodeGenPointersTests.cs b/Cesium.CodeGen.Tests/CodeGenPointersTests.cs index d70d815f..a367199c 100644 --- a/Cesium.CodeGen.Tests/CodeGenPointersTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenPointersTests.cs @@ -54,10 +54,10 @@ public void CannotAddPointerTypes() => DoesNotCompile( "Operator Add is not supported for pointer/pointer operands"); [Fact] - public void CanSubstractPointerTypes() => DoTest("void foo (int *x, int *y) { int d = x - y; }"); + public Task CanSubtractPointerTypes() => DoTest("void foo (int *x, int *y) { int d = x - y; }"); [Fact] - public void CanSubstractPointerWithConstTypes() => DoTest("void foo (int *x, const int *y) { int d = x - y; }"); + public Task CanSubtractPointerWithConstTypes() => DoTest("void foo (int *x, const int *y) { int d = x - y; }"); [Fact] public Task CanUseBuiltinOffsetOfOnDeclaredStruct() => DoTest( diff --git a/Cesium.CodeGen.Tests/CodeGenSizeofTests.cs b/Cesium.CodeGen.Tests/CodeGenSizeofTests.cs index 3e070ec0..da5b00bc 100644 --- a/Cesium.CodeGen.Tests/CodeGenSizeofTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenSizeofTests.cs @@ -40,6 +40,13 @@ int main() { return sizeof(x); }"); + [Fact] + public Task ArraySizeofLong() => DoTest(@" +int main() { + long x[] = { 1,2,3,4,5 }; + return sizeof(x); +}"); + [Fact] public Task EnumSizeof() => DoTest(@" int main() { diff --git a/Cesium.CodeGen.Tests/CodeGenTypeTests.cs b/Cesium.CodeGen.Tests/CodeGenTypeTests.cs index ce9fc63b..a8dfb843 100644 --- a/Cesium.CodeGen.Tests/CodeGenTypeTests.cs +++ b/Cesium.CodeGen.Tests/CodeGenTypeTests.cs @@ -242,4 +242,11 @@ typedef struct { [Fact] public Task StaticFileScopedVariable() => DoTest(@"static int x = 123;"); + + [Fact] + public Task StaticStruct() => DoTest(@"struct _foo { + int x[4]; +}; + +static struct _foo foo;"); } diff --git a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitialization.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitialization.verified.txt index cfe12e6f..b7f4b584 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitialization.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitialization.verified.txt @@ -1,4 +1,4 @@ -System.Int32 ::main() +System.Int32 ::main() Locals: System.Int32* V_0 IL_0000: ldc.i4.s 16 diff --git a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitializationWithoutSize.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitializationWithoutSize.verified.txt index c61932bd..4f32ae0a 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitializationWithoutSize.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayInitializationWithoutSize.verified.txt @@ -1,4 +1,4 @@ -System.Int32 ::main() +System.Int32 ::main() Locals: System.Int32* V_0 System.Int32* V_1 diff --git a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayOverPointer.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayOverPointer.verified.txt new file mode 100644 index 00000000..f456164d --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.ArrayOverPointer.verified.txt @@ -0,0 +1,63 @@ +System.Int32 ::main(System.Int32 argc, System.Byte** argv) + Locals: + System.Byte V_0 + IL_0000: ldarg.1 + IL_0001: ldc.i4.0 + IL_0002: conv.i + IL_0003: ldc.i4 1 + IL_0008: mul + IL_0009: add + IL_000a: ldind.i1 + IL_000b: sizeof System.Byte* + IL_0011: ldc.i4.0 + IL_0012: mul + IL_0013: add + IL_0014: ldind.i + IL_0015: stloc.0 + IL_0016: ldarg.1 + IL_0017: sizeof System.Byte* + IL_001d: ldc.i4.1 + IL_001e: mul + IL_001f: add + IL_0020: ldind.i + IL_0021: ret + +System.Int32 ::(System.String[] args) + Locals: + System.Int32 V_0 + System.Byte*[] V_1 + System.Byte*[] V_2 + System.Byte*[] (pinned) V_3 + System.Int32 V_4 + IL_0000: ldarg.0 + IL_0001: ldlen + IL_0002: ldc.i4.1 + IL_0003: add + IL_0004: stloc.0 + IL_0005: ldarg.0 + IL_0006: call System.Byte*[] Cesium.Runtime.RuntimeHelpers::ArgsToArgv(System.String[]) + IL_000b: stloc.1 + IL_000c: ldloc.1 + IL_000d: ldlen + IL_000e: newarr System.Byte* + IL_0013: stloc.2 + IL_0014: ldloc.1 + IL_0015: ldloc.2 + IL_0016: ldc.i4.0 + IL_0017: call System.Void System.Array::CopyTo(System.Array,System.Int32) + IL_001c: ldloc.0 + IL_001d: ldloc.2 + IL_001e: stloc.3 + IL_001f: ldloc.3 + IL_0020: ldc.i4.0 + IL_0021: ldelema System.Byte* + IL_0026: call System.Int32 ::main(System.Int32,System.Byte**) + IL_002b: stloc.s V_4 + IL_002d: ldnull + IL_002e: stloc.3 + IL_002f: ldloc.1 + IL_0030: call System.Void Cesium.Runtime.RuntimeHelpers::FreeArgv(System.Byte*[]) + IL_0035: ldloc.s V_4 + IL_0037: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_003c: ldloc.s V_4 + IL_003e: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitialization.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitialization.verified.txt index 448c2474..86fefa37 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitialization.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitialization.verified.txt @@ -1,4 +1,4 @@ -System.Void ::.cctor() +System.Void ::.cctor() IL_0000: ldc.i4.s 16 IL_0002: conv.u IL_0003: call System.Void* Cesium.Runtime.RuntimeHelpers::AllocateGlobalField(System.UInt32) diff --git a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitializationWithoutSize.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitializationWithoutSize.verified.txt index 0c548001..7bc72340 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitializationWithoutSize.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.GlobalArrayInitializationWithoutSize.verified.txt @@ -1,4 +1,4 @@ -System.Void ::.cctor() +System.Void ::.cctor() IL_0000: ldc.i4.s 12 IL_0002: conv.u IL_0003: call System.Void* Cesium.Runtime.RuntimeHelpers::AllocateGlobalField(System.UInt32) diff --git a/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.PointerArrayIndexing.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.PointerArrayIndexing.verified.txt new file mode 100644 index 00000000..e66d6737 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenArrayTests.PointerArrayIndexing.verified.txt @@ -0,0 +1,37 @@ +System.Int32 ::f(System.Byte*** t) + Locals: + System.Byte* V_0 + IL_0000: ldarg.0 + IL_0001: ldc.i4.2 + IL_0002: conv.i + IL_0003: sizeof System.IntPtr + IL_0009: mul + IL_000a: add + IL_000b: ldind.i + IL_000c: sizeof System.Byte** + IL_0012: ldc.i4.3 + IL_0013: mul + IL_0014: add + IL_0015: ldind.i + IL_0016: stloc.0 + IL_0017: ldloc.0 + IL_0018: ldc.i4.1 + IL_0019: ldc.i4.1 + IL_001a: mul + IL_001b: add + IL_001c: ldind.i1 + IL_001d: ret + +System.Int32 ::main() + IL_0000: ldc.i4.s 42 + IL_0002: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::main() + IL_0005: stloc.s V_0 + IL_0007: ldloc.s V_0 + IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_000e: ldloc.s V_0 + IL_0010: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInSwitchEnclosedWithLoop.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInSwitchEnclosedWithLoop.verified.txt index 94b56bc4..a3187645 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInSwitchEnclosedWithLoop.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenContinueStatementTests.ContinueInSwitchEnclosedWithLoop.verified.txt @@ -32,31 +32,33 @@ System.Int32 ::(System.String[] args) System.Int32 V_4 IL_0000: ldarg.0 IL_0001: ldlen - IL_0002: stloc.0 - IL_0003: ldarg.0 - IL_0004: call System.Byte*[] Cesium.Runtime.RuntimeHelpers::ArgsToArgv(System.String[]) - IL_0009: stloc.1 - IL_000a: ldloc.1 - IL_000b: ldlen - IL_000c: newarr System.Byte* - IL_0011: stloc.2 - IL_0012: ldloc.1 - IL_0013: ldloc.2 - IL_0014: ldc.i4.0 - IL_0015: call System.Void System.Array::CopyTo(System.Array,System.Int32) - IL_001a: ldloc.0 - IL_001b: ldloc.2 - IL_001c: stloc.3 - IL_001d: ldloc.3 - IL_001e: ldc.i4.0 - IL_001f: ldelema System.Byte* - IL_0024: call System.Int32 ::main(System.Int32,System.Byte**) - IL_0029: stloc.s V_4 - IL_002b: ldnull - IL_002c: stloc.3 - IL_002d: ldloc.1 - IL_002e: call System.Void Cesium.Runtime.RuntimeHelpers::FreeArgv(System.Byte*[]) - IL_0033: ldloc.s V_4 - IL_0035: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) - IL_003a: ldloc.s V_4 - IL_003c: ret + IL_0002: ldc.i4.1 + IL_0003: add + IL_0004: stloc.0 + IL_0005: ldarg.0 + IL_0006: call System.Byte*[] Cesium.Runtime.RuntimeHelpers::ArgsToArgv(System.String[]) + IL_000b: stloc.1 + IL_000c: ldloc.1 + IL_000d: ldlen + IL_000e: newarr System.Byte* + IL_0013: stloc.2 + IL_0014: ldloc.1 + IL_0015: ldloc.2 + IL_0016: ldc.i4.0 + IL_0017: call System.Void System.Array::CopyTo(System.Array,System.Int32) + IL_001c: ldloc.0 + IL_001d: ldloc.2 + IL_001e: stloc.3 + IL_001f: ldloc.3 + IL_0020: ldc.i4.0 + IL_0021: ldelema System.Byte* + IL_0026: call System.Int32 ::main(System.Int32,System.Byte**) + IL_002b: stloc.s V_4 + IL_002d: ldnull + IL_002e: stloc.3 + IL_002f: ldloc.1 + IL_0030: call System.Void Cesium.Runtime.RuntimeHelpers::FreeArgv(System.Byte*[]) + IL_0035: ldloc.s V_4 + IL_0037: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_003c: ldloc.s V_4 + IL_003e: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.CanHaveTwoFunctionDeclarationsWithDifferentParameterNames.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.CanHaveTwoFunctionDeclarationsWithDifferentParameterNames.verified.txt new file mode 100644 index 00000000..27cee149 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.CanHaveTwoFunctionDeclarationsWithDifferentParameterNames.verified.txt @@ -0,0 +1,3 @@ +System.Int32 ::console_read(System.Int32 argc) + IL_0000: ldc.i4.0 + IL_0001: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.StandardMain.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.StandardMain.verified.txt index 783ea3ca..5fbf42bb 100644 --- a/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.StandardMain.verified.txt +++ b/Cesium.CodeGen.Tests/verified/CodeGenMethodTests.StandardMain.verified.txt @@ -11,31 +11,33 @@ System.Int32 ::(System.String[] args) System.Int32 V_4 IL_0000: ldarg.0 IL_0001: ldlen - IL_0002: stloc.0 - IL_0003: ldarg.0 - IL_0004: call System.Byte*[] Cesium.Runtime.RuntimeHelpers::ArgsToArgv(System.String[]) - IL_0009: stloc.1 - IL_000a: ldloc.1 - IL_000b: ldlen - IL_000c: newarr System.Byte* - IL_0011: stloc.2 - IL_0012: ldloc.1 - IL_0013: ldloc.2 - IL_0014: ldc.i4.0 - IL_0015: call System.Void System.Array::CopyTo(System.Array,System.Int32) - IL_001a: ldloc.0 - IL_001b: ldloc.2 - IL_001c: stloc.3 - IL_001d: ldloc.3 - IL_001e: ldc.i4.0 - IL_001f: ldelema System.Byte* - IL_0024: call System.Int32 ::main(System.Int32,System.Byte**) - IL_0029: stloc.s V_4 - IL_002b: ldnull - IL_002c: stloc.3 - IL_002d: ldloc.1 - IL_002e: call System.Void Cesium.Runtime.RuntimeHelpers::FreeArgv(System.Byte*[]) - IL_0033: ldloc.s V_4 - IL_0035: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) - IL_003a: ldloc.s V_4 - IL_003c: ret + IL_0002: ldc.i4.1 + IL_0003: add + IL_0004: stloc.0 + IL_0005: ldarg.0 + IL_0006: call System.Byte*[] Cesium.Runtime.RuntimeHelpers::ArgsToArgv(System.String[]) + IL_000b: stloc.1 + IL_000c: ldloc.1 + IL_000d: ldlen + IL_000e: newarr System.Byte* + IL_0013: stloc.2 + IL_0014: ldloc.1 + IL_0015: ldloc.2 + IL_0016: ldc.i4.0 + IL_0017: call System.Void System.Array::CopyTo(System.Array,System.Int32) + IL_001c: ldloc.0 + IL_001d: ldloc.2 + IL_001e: stloc.3 + IL_001f: ldloc.3 + IL_0020: ldc.i4.0 + IL_0021: ldelema System.Byte* + IL_0026: call System.Int32 ::main(System.Int32,System.Byte**) + IL_002b: stloc.s V_4 + IL_002d: ldnull + IL_002e: stloc.3 + IL_002f: ldloc.1 + IL_0030: call System.Void Cesium.Runtime.RuntimeHelpers::FreeArgv(System.Byte*[]) + IL_0035: ldloc.s V_4 + IL_0037: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_003c: ldloc.s V_4 + IL_003e: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenPointersTests.CanSubtractPointerTypes.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenPointersTests.CanSubtractPointerTypes.verified.txt new file mode 100644 index 00000000..32083b74 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenPointersTests.CanSubtractPointerTypes.verified.txt @@ -0,0 +1,11 @@ +System.Void ::foo(System.Int32* x, System.Int32* y) + Locals: + System.Int32 V_0 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: sub + IL_0003: ldc.i4.4 + IL_0004: div + IL_0005: conv.i4 + IL_0006: stloc.0 + IL_0007: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenPointersTests.CanSubtractPointerWithConstTypes.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenPointersTests.CanSubtractPointerWithConstTypes.verified.txt new file mode 100644 index 00000000..32083b74 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenPointersTests.CanSubtractPointerWithConstTypes.verified.txt @@ -0,0 +1,11 @@ +System.Void ::foo(System.Int32* x, System.Int32* y) + Locals: + System.Int32 V_0 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: sub + IL_0003: ldc.i4.4 + IL_0004: div + IL_0005: conv.i4 + IL_0006: stloc.0 + IL_0007: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenSizeofTests.ArraySizeofLong.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenSizeofTests.ArraySizeofLong.verified.txt new file mode 100644 index 00000000..cd13e76c --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenSizeofTests.ArraySizeofLong.verified.txt @@ -0,0 +1,24 @@ +System.Int32 ::main() + Locals: + System.Int64* V_0 + IL_0000: ldc.i4.s 40 + IL_0002: conv.u + IL_0003: localloc + IL_0005: stloc.0 + IL_0006: ldsflda / ::ConstDataBuffer0 + IL_000b: ldloc V_0 + IL_000f: ldc.i4.s 40 + IL_0011: conv.u + IL_0012: call System.Void Cesium.Runtime.RuntimeHelpers::InitializeCompound(System.Void*,System.Void*,System.UInt32) + IL_0017: ldc.i4.s 40 + IL_0019: ret + +System.Int32 ::() + Locals: + System.Int32 V_0 + IL_0000: call System.Int32 ::main() + IL_0005: stloc.s V_0 + IL_0007: ldloc.s V_0 + IL_0009: call System.Void Cesium.Runtime.RuntimeHelpers::Exit(System.Int32) + IL_000e: ldloc.s V_0 + IL_0010: ret diff --git a/Cesium.CodeGen.Tests/verified/CodeGenTypeTests.StaticStruct.verified.txt b/Cesium.CodeGen.Tests/verified/CodeGenTypeTests.StaticStruct.verified.txt new file mode 100644 index 00000000..01b50fd7 --- /dev/null +++ b/Cesium.CodeGen.Tests/verified/CodeGenTypeTests.StaticStruct.verified.txt @@ -0,0 +1,23 @@ +Module: Primary + Type: + + Type: _foo + Nested types: + Type: _foo/x + Pack: 0 + Size: 16 + Custom attributes: + - CompilerGeneratedAttribute() + - UnsafeValueTypeAttribute() + + Fields: + System.Int32 _foo/x::FixedElementField + Fields: + _foo/x _foo::x + Custom attributes: + - FixedBufferAttribute(System.Int32, 16) + + + Type: testInput + Fields: + _foo testInput::foo diff --git a/Cesium.CodeGen/ConstantEvaluator.cs b/Cesium.CodeGen/ConstantEvaluator.cs index 7839c6b8..111bff77 100644 --- a/Cesium.CodeGen/ConstantEvaluator.cs +++ b/Cesium.CodeGen/ConstantEvaluator.cs @@ -40,15 +40,15 @@ public static IConstant GetConstantValue(IExpression expression) rightConstant is not IntegerConstant rightInt) throw new CompilationException("Evaluated constants are not integer"); - return binOp.Operator switch + return binOp.Operator switch { BinaryOperator.Add => new IntegerConstant(leftInt.Value + rightInt.Value), BinaryOperator.Subtract => new IntegerConstant(leftInt.Value - rightInt.Value), BinaryOperator.Multiply => new IntegerConstant(leftInt.Value * rightInt.Value), BinaryOperator.Divide => new IntegerConstant(leftInt.Value / rightInt.Value), BinaryOperator.Remainder => new IntegerConstant(leftInt.Value % rightInt.Value), - BinaryOperator.BitwiseLeftShift => new IntegerConstant(leftInt.Value << rightInt.Value), - BinaryOperator.BitwiseRightShift => new IntegerConstant(leftInt.Value >> rightInt.Value), + BinaryOperator.BitwiseLeftShift => new IntegerConstant(leftInt.Value << (int)rightInt.Value), + BinaryOperator.BitwiseRightShift => new IntegerConstant(leftInt.Value >> (int)rightInt.Value), BinaryOperator.BitwiseOr => new IntegerConstant(leftInt.Value | rightInt.Value), BinaryOperator.BitwiseAnd => new IntegerConstant(leftInt.Value & rightInt.Value), BinaryOperator.BitwiseXor => new IntegerConstant(leftInt.Value ^ rightInt.Value), diff --git a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs index cae6723e..14b16845 100644 --- a/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs +++ b/Cesium.CodeGen/Contexts/Meta/FunctionInfo.cs @@ -1,3 +1,4 @@ +using Cesium.CodeGen.Extensions; using Cesium.CodeGen.Ir; using Cesium.CodeGen.Ir.Declarations; using Cesium.CodeGen.Ir.Types; @@ -46,7 +47,7 @@ public void VerifySignatureEquality(string name, ParametersInfo? parameters, ITy var declaredParams = Parameters?.Parameters ?? Array.Empty(); foreach (var (a, b) in actualParams.Zip(declaredParams)) { - if (a != b) + if (!a.Type.IsEqualTo(b.Type)) throw new CompilationException( $"Incorrect type for parameter {a.Name}: declared as {b.Type}, defined as {a.Type}."); } diff --git a/Cesium.CodeGen/Extensions/TranslationUnitEx.cs b/Cesium.CodeGen/Extensions/TranslationUnitEx.cs index 862d84b7..548e030a 100644 --- a/Cesium.CodeGen/Extensions/TranslationUnitEx.cs +++ b/Cesium.CodeGen/Extensions/TranslationUnitEx.cs @@ -47,7 +47,8 @@ private static IEnumerable GetTopLevelDeclarations(Ast.SymbolDeclara throw new CompilationException($"CLI initializer should be a function for identifier {identifier}."); } - if (type is PrimitiveType or PointerType or InPlaceArrayType) + if (type is PrimitiveType or PointerType or InPlaceArrayType + || (type is StructType varStructType && varStructType.Identifier != identifier)) { var variable = new GlobalVariableDefinition(storageClass, type, identifier, initializer); yield return variable; @@ -56,7 +57,7 @@ private static IEnumerable GetTopLevelDeclarations(Ast.SymbolDeclara if (type is EnumType enumType) { - int currentValue = -1; + long currentValue = -1; foreach (var enumeratorDeclaration in enumType.Members) { var enumeratorName = enumeratorDeclaration.Declaration.Identifier ?? throw new CompilationException( diff --git a/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs b/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs index 15939fa0..b94f7bbc 100644 --- a/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs +++ b/Cesium.CodeGen/Ir/BlockItems/FunctionDefinition.cs @@ -185,6 +185,9 @@ private static MethodDefinition GenerateSyntheticEntryPointStrArray( // argC = args.Length; instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); // args instructions.Add(Instruction.Create(OpCodes.Ldlen)); + // argC = 1 + argC; + instructions.Add(Instruction.Create(OpCodes.Ldc_I4_1)); + instructions.Add(Instruction.Create(OpCodes.Add)); instructions.Add(Instruction.Create(OpCodes.Stloc_0)); // 0 = argC.Index // argV = Cesium.Runtime.RuntimeHelpers.ArgsToArgv(args); instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); // args diff --git a/Cesium.CodeGen/Ir/Declarations/ScopedDeclarationInfo.cs b/Cesium.CodeGen/Ir/Declarations/ScopedDeclarationInfo.cs index 6a577e80..778712f4 100644 --- a/Cesium.CodeGen/Ir/Declarations/ScopedDeclarationInfo.cs +++ b/Cesium.CodeGen/Ir/Declarations/ScopedDeclarationInfo.cs @@ -25,20 +25,21 @@ public static IScopedDeclarationInfo Of(Declaration declaration) return TypeDefOf(specifiers.RemoveAt(0), initDeclarators); } - if (specifiers.Length > 0 && (specifiers[0] is StructOrUnionSpecifier || specifiers[0] is EnumSpecifier)) + var (storageClass, declarationSpecifiers) = ExtractStorageClass(specifiers); + if (declarationSpecifiers.Count > 0 && (declarationSpecifiers[0] is StructOrUnionSpecifier || declarationSpecifiers[0] is EnumSpecifier)) { if (initDeclarators == null) { Declarator? declarator = null; - return new ScopedIdentifierDeclaration(StorageClass.Auto, - specifiers.Select(_ => + return new ScopedIdentifierDeclaration(storageClass, + declarationSpecifiers.Select(_ => { var ld = LocalDeclarationInfo.Of(new[] { _ }, declarator); return new InitializableDeclarationInfo(ld, null); }).ToImmutableArray()); } - var initializationDeclarators = initDeclarators.Value.SelectMany(id => specifiers.Select(_ => + var initializationDeclarators = initDeclarators.Value.SelectMany(id => declarationSpecifiers.Select(_ => { var ld = LocalDeclarationInfo.Of(new[] { _ }, id.Declarator); if (id.Initializer is AssignmentInitializer assignmentInitializer) @@ -49,7 +50,7 @@ public static IScopedDeclarationInfo Of(Declaration declaration) throw new CompilationException($"Struct initializers are not supported."); })).ToImmutableArray(); - return new ScopedIdentifierDeclaration(StorageClass.Auto, initializationDeclarators); + return new ScopedIdentifierDeclaration(storageClass, initializationDeclarators); } if (initDeclarators == null) diff --git a/Cesium.CodeGen/Ir/Expressions/BinaryOperators/BinaryOperatorExpression.cs b/Cesium.CodeGen/Ir/Expressions/BinaryOperators/BinaryOperatorExpression.cs index c72b9e7f..eabf033c 100644 --- a/Cesium.CodeGen/Ir/Expressions/BinaryOperators/BinaryOperatorExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/BinaryOperators/BinaryOperatorExpression.cs @@ -43,6 +43,16 @@ public IExpression Lower(IDeclarationScope scope) if (Operator.IsComparison()) { + if (leftType is ConstType leftTypeConst) + { + leftType = leftTypeConst.Base; + } + + if (rightType is ConstType rightTypeConst) + { + rightType = rightTypeConst.Base; + } + if ((!scope.CTypeSystem.IsNumeric(leftType) && leftType is not PointerType) || (!scope.CTypeSystem.IsNumeric(rightType) && rightType is not PointerType)) throw new CompilationException($"Unable to compare {leftType} to {rightType}"); diff --git a/Cesium.CodeGen/Ir/Expressions/CompoundInitializationExpression.cs b/Cesium.CodeGen/Ir/Expressions/CompoundInitializationExpression.cs index bc0fbf2a..6fcf1ddb 100644 --- a/Cesium.CodeGen/Ir/Expressions/CompoundInitializationExpression.cs +++ b/Cesium.CodeGen/Ir/Expressions/CompoundInitializationExpression.cs @@ -56,9 +56,18 @@ private void WriteInitializer(MemoryStream stream, IExpression initializer) { case PrimitiveTypeKind.Int: case PrimitiveTypeKind.SignedInt: - var data = BitConverter.GetBytes(integer.Value); + var data = BitConverter.GetBytes((int)integer.Value); stream.Write(data); break; + case PrimitiveTypeKind.Long: + case PrimitiveTypeKind.SignedLong: + case PrimitiveTypeKind.SignedLongInt: + stream.Write(BitConverter.GetBytes(integer.Value)); + break; + case PrimitiveTypeKind.UnsignedLong: + case PrimitiveTypeKind.UnsignedLongInt: + stream.Write(BitConverter.GetBytes(unchecked((ulong)integer.Value))); + break; case PrimitiveTypeKind.UnsignedInt: stream.Write(BitConverter.GetBytes(unchecked((uint)integer.Value))); break; diff --git a/Cesium.CodeGen/Ir/Expressions/Constants/IntegerConstant.cs b/Cesium.CodeGen/Ir/Expressions/Constants/IntegerConstant.cs index a7cbe990..3244e65f 100644 --- a/Cesium.CodeGen/Ir/Expressions/Constants/IntegerConstant.cs +++ b/Cesium.CodeGen/Ir/Expressions/Constants/IntegerConstant.cs @@ -16,15 +16,21 @@ public IntegerConstant(string value) Value = intValue; } - public IntegerConstant(int value) + public IntegerConstant(long value) { Value = value; } - public int Value { get; } + public long Value { get; } public void EmitTo(IEmitScope scope) { + if (Value > int.MaxValue || Value < int.MinValue) + { + scope.Method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_I8, Value)); + return; + } + scope.Method.Body.Instructions.Add(Value switch { 0 => Instruction.Create(OpCodes.Ldc_I4_0), @@ -38,7 +44,7 @@ public void EmitTo(IEmitScope scope) 8 => Instruction.Create(OpCodes.Ldc_I4_8), -1 => Instruction.Create(OpCodes.Ldc_I4_M1), >= sbyte.MinValue and <= sbyte.MaxValue => Instruction.Create(OpCodes.Ldc_I4_S, (sbyte) Value), - _ => Instruction.Create(OpCodes.Ldc_I4, Value) + _ => Instruction.Create(OpCodes.Ldc_I4, (int)Value) }); } @@ -46,11 +52,17 @@ public void EmitTo(IEmitScope scope) public override string ToString() => $"integer: {Value}"; - private static bool TryParse(string text, out int value) + private static bool TryParse(string text, out long value) { - if (text.StartsWith("0x")) + var textSpan = text.AsSpan(); + if (textSpan.EndsWith("L")) + { + textSpan = textSpan[..^1]; + } + + if (textSpan.StartsWith("0x")) { - if (int.TryParse(text[2..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value)) + if (long.TryParse(textSpan[2..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value)) { return true; } @@ -58,14 +70,14 @@ private static bool TryParse(string text, out int value) throw new CompilationException($"Invalid hex number {text}"); } - if (text.StartsWith("0")) + if (textSpan.StartsWith("0")) { value = 0; - for (var i = 0; i < text.Length; i++) + for (var i = 0; i < textSpan.Length; i++) { - if (text[i] >= '0' && text[i] <= '7') + if (textSpan[i] >= '0' && textSpan[i] <= '7') { - value = 8 * value + (text[i] - '0'); + value = 8 * value + (textSpan[i] - '0'); } else { @@ -76,7 +88,7 @@ private static bool TryParse(string text, out int value) return true; } - if (int.TryParse(text, CultureInfo.InvariantCulture, out value)) + if (long.TryParse(textSpan, CultureInfo.InvariantCulture, out value)) { return true; } diff --git a/Cesium.CodeGen/Ir/Expressions/Values/LValueArrayElement.cs b/Cesium.CodeGen/Ir/Expressions/Values/LValueArrayElement.cs index 37565ba5..df7324c5 100644 --- a/Cesium.CodeGen/Ir/Expressions/Values/LValueArrayElement.cs +++ b/Cesium.CodeGen/Ir/Expressions/Values/LValueArrayElement.cs @@ -2,6 +2,7 @@ using Cesium.CodeGen.Ir.Types; using Cesium.Core; using Mono.Cecil.Cil; +using System.Security.AccessControl; namespace Cesium.CodeGen.Ir.Expressions.Values; @@ -20,7 +21,7 @@ public void EmitGetValue(IEmitScope scope) { EmitPointerMoveToElement(scope); - var (loadOp, _) = PrimitiveTypeInfo.Opcodes[GetElementType().Kind]; + var (loadOp, _) = GetElementOpcodes(); scope.Method.Body.GetILProcessor().Emit(loadOp); } @@ -33,17 +34,38 @@ public void EmitSetValue(IEmitScope scope, IExpression value) { EmitPointerMoveToElement(scope); value.EmitTo(scope); - var (_, storeOp) = PrimitiveTypeInfo.Opcodes[GetElementType().Kind]; + var (_, storeOp) = GetElementOpcodes(); scope.Method.Body.GetILProcessor().Emit(storeOp); } + private (OpCode, OpCode) GetElementOpcodes() + { + var elementType = GetElementType(); + if (elementType is PrimitiveType primitive) + { + return PrimitiveTypeInfo.Opcodes[primitive.Kind]; + } + + if (elementType is PointerType) + { + return (OpCodes.Ldind_I, OpCodes.Stind_I); + } + + throw new InvalidOperationException("Arrays of structs are not supported"); + } + public IType GetValueType() => _array.GetValueType(); - private PrimitiveType GetElementType() + private IType GetElementType() { - InPlaceArrayType? type = GetValueType() as InPlaceArrayType ?? throw new AssertException("Array type expected."); - var primitiveType = (PrimitiveType)GetBaseType(type); + var valueType = GetValueType(); + var primitiveType = GetBaseType(valueType); + if (primitiveType is InPlaceArrayType or PointerType) + { + return GetBaseType(primitiveType); + } + return primitiveType; } @@ -53,19 +75,29 @@ private void EmitPointerMoveToElement(IEmitScope scope) _index.EmitTo(scope); var method = scope.Method.Body.GetILProcessor(); method.Emit(OpCodes.Conv_I); - var elementSize = PrimitiveTypeInfo.Size[GetElementType().Kind]; - method.Emit(OpCodes.Ldc_I4, elementSize); + var elementSize = GetElementType().GetSizeInBytes(scope.AssemblyContext.ArchitectureSet); + if (elementSize.HasValue) + { + method.Emit(OpCodes.Ldc_I4, elementSize.Value); + } + else + { + method.Emit(OpCodes.Sizeof, scope.Module.TypeSystem.IntPtr); + } + method.Emit(OpCodes.Mul); method.Emit(OpCodes.Add); } - private static IType GetBaseType(InPlaceArrayType valueType) + private static IType GetBaseType(IType valueType) { - if (valueType.Base is InPlaceArrayType inPlaceArrayType) + var baseType = valueType switch { - return GetBaseType(inPlaceArrayType); - } + InPlaceArrayType arrayType => arrayType.Base, + PointerType pointerType => pointerType.Base, + _ => throw new AssertException("Array or pointer type expected.") + }; - return valueType.Base; + return baseType; } } diff --git a/Cesium.CodeGen/Ir/Expressions/Values/LValueLocalVariable.cs b/Cesium.CodeGen/Ir/Expressions/Values/LValueLocalVariable.cs index f6b0f696..6b9133fd 100644 --- a/Cesium.CodeGen/Ir/Expressions/Values/LValueLocalVariable.cs +++ b/Cesium.CodeGen/Ir/Expressions/Values/LValueLocalVariable.cs @@ -34,7 +34,7 @@ public void EmitGetValue(IEmitScope scope) public void EmitGetAddress(IEmitScope scope) { var variable = GetVariableDefinition(scope); - if (variable.VariableType.IsPointer) + if (_variableType is InPlaceArrayType) { EmitGetValue(scope); } diff --git a/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs b/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs index 866be165..c516d2f9 100644 --- a/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs +++ b/Cesium.CodeGen/Ir/Lowering/BlockItemLowering.cs @@ -289,13 +289,15 @@ public static IBlockItem Lower(IDeclarationScope scope, IBlockItem blockItem) } case GlobalVariableDefinition d: { - scope.AddVariable(d.StorageClass, d.Identifier, d.Type, null); + var resolvedType = scope.ResolveType(d.Type); + scope.AddVariable(d.StorageClass, d.Identifier, resolvedType, null); return d with { Initializer = d.Initializer?.Lower(scope) }; } case EnumConstantDefinition d: { - scope.AddVariable(StorageClass.Static, d.Identifier, d.Type, d.Value); + var resolvedType = scope.ResolveType(d.Type); + scope.AddVariable(StorageClass.Static, d.Identifier, resolvedType, d.Value); return d; } diff --git a/Cesium.CodeGen/Ir/Types/PrimitiveType.cs b/Cesium.CodeGen/Ir/Types/PrimitiveType.cs index 0d1196eb..50fdc183 100644 --- a/Cesium.CodeGen/Ir/Types/PrimitiveType.cs +++ b/Cesium.CodeGen/Ir/Types/PrimitiveType.cs @@ -177,6 +177,7 @@ internal static class PrimitiveTypeInfo { { PrimitiveTypeKind.Char, (OpCodes.Ldind_I1, OpCodes.Stind_I1) }, { PrimitiveTypeKind.SignedChar, (OpCodes.Ldind_I1, OpCodes.Stind_I1) }, + { PrimitiveTypeKind.UnsignedChar, (OpCodes.Ldind_U1, OpCodes.Stind_I1) }, { PrimitiveTypeKind.Short, (OpCodes.Ldind_I2, OpCodes.Stind_I2) }, { PrimitiveTypeKind.UnsignedShort, (OpCodes.Ldind_I2, OpCodes.Stind_I2) }, { PrimitiveTypeKind.Int, (OpCodes.Ldind_I4, OpCodes.Stind_I4) }, diff --git a/Cesium.Compiler/stdlib/errno.h b/Cesium.Compiler/stdlib/errno.h new file mode 100644 index 00000000..701dfff2 --- /dev/null +++ b/Cesium.Compiler/stdlib/errno.h @@ -0,0 +1,50 @@ +#pragma once + +__cli_import("Cesium.Runtime.StdLibFunctions::GetErrNo") +int* _errno(void); +#define errno (*_errno()) + +// Error codes +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define EDOM 33 +#define EDEADLK 36 +#define ENAMETOOLONG 38 +#define ENOLCK 39 +#define ENOSYS 40 +#define ENOTEMPTY 41 + +#define EINVAL 22 +#define ERANGE 34 +#define EILSEQ 42 +#define STRUNCATE 80 + +// Support EDEADLOCK for compatibility with older Microsoft C versions +#define EDEADLOCK EDEADLK diff --git a/Cesium.Compiler/stdlib/limits.h b/Cesium.Compiler/stdlib/limits.h index b7704853..1d0db77e 100644 --- a/Cesium.Compiler/stdlib/limits.h +++ b/Cesium.Compiler/stdlib/limits.h @@ -10,8 +10,8 @@ #define INT_MIN (-2147483647 - 1) #define INT_MAX 2147483647 #define UINT_MAX 0xffffffff -#define LONG_MIN (-2147483647L - 1) -#define LONG_MAX 2147483647L +#define LONG_MIN (-9223372036854775807L - 1) +#define LONG_MAX 9223372036854775807L #define ULONG_MAX 0xffffffffUL #define LLONG_MAX 9223372036854775807LL #define LLONG_MIN (-9223372036854775807LL - 1) diff --git a/Cesium.Compiler/stdlib/stdlib.h b/Cesium.Compiler/stdlib/stdlib.h index 453767e8..1395ce9d 100644 --- a/Cesium.Compiler/stdlib/stdlib.h +++ b/Cesium.Compiler/stdlib/stdlib.h @@ -42,3 +42,14 @@ void abort(void); __cli_import("Cesium.Runtime.StdLibFunctions::Atoi") int atoi(const char* str); + +__cli_import("Cesium.Runtime.StdLibFunctions::StrToL") +long strtol(const char* str, char** str_end, int base); + +__cli_import("Cesium.Runtime.StdLibFunctions::GetErrNo") +int* _errno(void); + +#define errno (*_errno()) + +__cli_import("Cesium.Runtime.StdLibFunctions::GetEnv") +char* getenv(const char* name); diff --git a/Cesium.Compiler/stdlib/string.h b/Cesium.Compiler/stdlib/string.h index 3f4edf06..a1b4a3df 100644 --- a/Cesium.Compiler/stdlib/string.h +++ b/Cesium.Compiler/stdlib/string.h @@ -21,3 +21,6 @@ int strncmp(const char* lhs, const char* rhs, size_t count); __cli_import("Cesium.Runtime.StringFunctions::Memset") void* memset(void* dest, int ch, size_t count); + +__cli_import("Cesium.Runtime.StringFunctions::StrChr") +char* strchr(const char* str, int ch); diff --git a/Cesium.IntegrationTests/argc.c b/Cesium.IntegrationTests/argc.c new file mode 100644 index 00000000..53a57291 --- /dev/null +++ b/Cesium.IntegrationTests/argc.c @@ -0,0 +1,7 @@ +#include + +int main(int argc, char *argv[]) +{ + printf("Arg count: %d", argc); + return 42; +} diff --git a/Cesium.IntegrationTests/stdlib/program/getenv.c b/Cesium.IntegrationTests/stdlib/program/getenv.c new file mode 100644 index 00000000..9462c860 --- /dev/null +++ b/Cesium.IntegrationTests/stdlib/program/getenv.c @@ -0,0 +1,12 @@ +#include +#include + +int main(void) +{ + const char* name = "PATH"; + const char* env_p = getenv(name); + if (env_p) + printf("Your %s is %s\n", name, env_p); + + return 42; +} diff --git a/Cesium.IntegrationTests/stdlib/string/byte/strchr.c b/Cesium.IntegrationTests/stdlib/string/byte/strchr.c new file mode 100644 index 00000000..ae23ca53 --- /dev/null +++ b/Cesium.IntegrationTests/stdlib/string/byte/strchr.c @@ -0,0 +1,16 @@ +#include +#include + +int main(void) +{ + const char* str = "Try not. Do, or do not. There is no try."; + char target = 'T'; + const char* result = str; + + while ((result = strchr(result, target)) != NULL) { + printf("Found '%c' starting at '%s'\n", target, result); + ++result; // Increment result, otherwise we'll find target at the same location + } + + return 42; +} diff --git a/Cesium.IntegrationTests/stdlib/string/byte/strtol.c b/Cesium.IntegrationTests/stdlib/string/byte/strtol.c new file mode 100644 index 00000000..1d52ea4e --- /dev/null +++ b/Cesium.IntegrationTests/stdlib/string/byte/strtol.c @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include + +int main(void) +{ + // parsing with error handling + const char* p = "10 200000000000000000000000000000 -200000000000000000000000000000 30 -40 junk"; + printf("Parsing '%s':\n", p); + + for (;;) + { + // errno can be set to any non-zero value by a library function call + // regardless of whether there was an error, so it needs to be cleared + // in order to check the error set by strtol + errno = 0; + char* end; + const long i = strtol(p, &end, 10); + if (p == end) + break; + + const int range_error = errno == ERANGE; + if (i == LONG_MAX) + { + printf("Extracted '%.*s', strtol returned LONG_MAX.", (int)(end - p), p); + } + else if (i == LONG_MIN) + { + printf("Extracted '%.*s', strtol returned LONG_MIN.", (int)(end - p), p); + } + else + { + printf("Extracted '%.*s', strtol returned %ld.", (int)(end - p), p, i); + } + p = end; + + if (range_error) + printf("\n --> Range error occurred."); + + putchar('\n'); + } + + printf("Unextracted leftover: '%s'\n\n", p); + + // parsing without error handling + printf("\"1010\" in binary --> %ld\n", strtol("1010", NULL, 2)); + printf("\"12\" in octal --> %ld\n", strtol("12", NULL, 8)); + printf("\"A\" in hex --> %ld\n", strtol("A", NULL, 16)); + printf("\"junk\" in base-36 --> %ld\n", strtol("junk", NULL, 36)); + printf("\"012\" in auto-detected base --> %ld\n", strtol("012", NULL, 0)); + printf("\"0xA\" in auto-detected base --> %ld\n", strtol("0xA", NULL, 0)); + printf("\"junk\" in auto-detected base --> %ld\n", strtol("junk", NULL, 0)); + + return 42; +} diff --git a/Cesium.Parser.Tests/PreprocessorTests/PreprocessorTests.cs b/Cesium.Parser.Tests/PreprocessorTests/PreprocessorTests.cs index facb28d9..e361b4a6 100644 --- a/Cesium.Parser.Tests/PreprocessorTests/PreprocessorTests.cs +++ b/Cesium.Parser.Tests/PreprocessorTests/PreprocessorTests.cs @@ -389,5 +389,33 @@ public Task FunctionReplaceEllipsisMultipleParameters2() => DoTest( @"#define foo(x,y,...) x,y,__VA_ARGS__ int x,test; int main() { return foo(x,test,11); } +"); + + [Fact] + public Task IfExpressionDisableDefines() => DoTest( +@"#ifdef NON_EXISTS +#define foo fake_foo +#endif +int foo() { return 0; } +"); + + [Fact] + public Task IfExpressionDisableUnDefines() => DoTest( +@" +#define fake_foo foo + +#ifdef NON_EXISTS +#undef fake_foo +#endif + +int fake_foo() { return 0; } +"); + + [Fact] + public Task UnrollNestedDefines() => DoTest( +@" +#define nested_foo foo +#define _(code) nested_foo(code) +_(""test"") "); } diff --git a/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionDisableDefines.verified.txt b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionDisableDefines.verified.txt new file mode 100644 index 00000000..8c5841a6 --- /dev/null +++ b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionDisableDefines.verified.txt @@ -0,0 +1,2 @@ + +int foo() { return 0; } diff --git a/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionDisableUnDefines.verified.txt b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionDisableUnDefines.verified.txt new file mode 100644 index 00000000..084db59d --- /dev/null +++ b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionDisableUnDefines.verified.txt @@ -0,0 +1,4 @@ + + + +int foo() { return 0; } diff --git a/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionElseEnableDefines.verified.txt b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionElseEnableDefines.verified.txt new file mode 100644 index 00000000..21af032b --- /dev/null +++ b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.IfExpressionElseEnableDefines.verified.txt @@ -0,0 +1,3 @@ + + +int foo() { return 0; } diff --git a/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.UnrollNestedDefines.verified.txt b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.UnrollNestedDefines.verified.txt new file mode 100644 index 00000000..7484ca56 --- /dev/null +++ b/Cesium.Parser.Tests/PreprocessorTests/verified/PreprocessorTests.UnrollNestedDefines.verified.txt @@ -0,0 +1,2 @@ + +foo("test") diff --git a/Cesium.Preprocessor/CPreprocessor.cs b/Cesium.Preprocessor/CPreprocessor.cs index d95eb7b5..47c2dfd9 100644 --- a/Cesium.Preprocessor/CPreprocessor.cs +++ b/Cesium.Preprocessor/CPreprocessor.cs @@ -97,188 +97,204 @@ private async IAsyncEnumerable> GetPreprocessingR } break; case PreprocessingToken: - newLine = false; - if (IncludeTokens) { - if (MacroContext.TryResolveMacro(token.Text, out var macroDefinition, out var tokenReplacement)) + newLine = false; + if (IncludeTokens) { - Dictionary>> replacement = new(); - if (macroDefinition is FunctionMacroDefinition functionMacro) + foreach (var producedToken in ReplaceMacro(token, stream)) { - if (functionMacro.Parameters is { } parameters - && (parameters.Length > 0 || functionMacro.hasEllipsis)) - { - int parameterIndex = -1; - int openParensCount = 0; - bool hitOpenToken = false; - List> currentParameter = new(); - IToken parametersParsingToken; + yield return producedToken; + } + } + } + break; - if (functionMacro.hasEllipsis) - { - replacement.Add("__VA_ARGS__", new()); - } + default: + throw new PreprocessorException($"Illegal token {token.Kind} {token.Text}."); + } + } + } - do - { - parametersParsingToken = stream.Consume(); - switch (parametersParsingToken) - { - case { Kind: LeftParen }: - if (openParensCount != 0) - { - currentParameter.Add(parametersParsingToken); - } - - hitOpenToken = true; - openParensCount++; - if (parameterIndex == -1) - { - parameterIndex = 0; - } - break; - case { Kind: RightParen }: - openParensCount--; - if (openParensCount != 0) - { - currentParameter.Add(parametersParsingToken); - } - break; - case { Kind: Separator, Text: "," }: - if (parameterIndex == -1) - { - throw new PreprocessorException($"Expected '(' but got {parametersParsingToken.Kind} {parametersParsingToken.Text} at range {parametersParsingToken.Range}."); - } - - if (openParensCount == 1) - { - if (parameters.Length > parameterIndex) - { - replacement.Add(parameters[parameterIndex], currentParameter); - parameterIndex++; - currentParameter = new(); - } - else if (functionMacro.hasEllipsis) - { - if (replacement.TryGetValue("__VA_ARGS__", out var va_args)) - { - va_args.AddRange(currentParameter); - va_args.Add(new Token(token.Range, ",", CPreprocessorTokenType.Separator)); - } - } - else - { - throw new PreprocessorException($"The function {functionMacro.Name} defined at {parametersParsingToken.Range} has more parameters than the macro allows."); - } - - currentParameter = new(); - } - else - { - currentParameter.Add(parametersParsingToken); - } - break; - default: - if (openParensCount == 0 && parametersParsingToken.Kind == WhiteSpace) - { - continue; - } - - currentParameter.Add(parametersParsingToken); - break; - } - } - while (openParensCount > 0 || !hitOpenToken); + private IEnumerable> ReplaceMacro(IToken token, IStream> stream) + { + if (MacroContext.TryResolveMacro(token.Text, out var macroDefinition, out var tokenReplacement)) + { + Dictionary>> replacement = new(); + if (macroDefinition is FunctionMacroDefinition functionMacro) + { + if (functionMacro.Parameters is { } parameters + && (parameters.Length > 0 || functionMacro.hasEllipsis)) + { + int parameterIndex = -1; + int openParensCount = 0; + bool hitOpenToken = false; + List> currentParameter = new(); + IToken parametersParsingToken; + if (functionMacro.hasEllipsis) + { + replacement.Add("__VA_ARGS__", new()); + } - if (parameters.Length > parameterIndex) - { - replacement.Add(parameters[parameterIndex], currentParameter); - } - else - { - if (replacement.TryGetValue("__VA_ARGS__", out var va_args_)) - { - va_args_.AddRange(currentParameter); - } - } - } - else + do + { + parametersParsingToken = stream.Consume(); + switch (parametersParsingToken) + { + case { Kind: LeftParen }: + if (openParensCount != 0) { - IToken parametersParsingToken; - int openParensCount = 0; - - do - { - parametersParsingToken = stream.Consume(); - switch (parametersParsingToken) - { - case { Kind: LeftParen }: - openParensCount++; - break; - case { Kind: RightParen }: - openParensCount--; - break; - default: break; - } - } while (openParensCount > 0); + currentParameter.Add(parametersParsingToken); } - } - - bool performStringReplace = false; - foreach (var subToken in tokenReplacement) - { - if (subToken is { Kind: Hash }) + hitOpenToken = true; + openParensCount++; + if (parameterIndex == -1) { - performStringReplace = true; - continue; + parameterIndex = 0; + } + break; + case { Kind: RightParen }: + openParensCount--; + if (openParensCount != 0) + { + currentParameter.Add(parametersParsingToken); + } + break; + case { Kind: Separator, Text: "," }: + if (parameterIndex == -1) + { + throw new PreprocessorException($"Expected '(' but got {parametersParsingToken.Kind} {parametersParsingToken.Text} at range {parametersParsingToken.Range}."); } - if (subToken is { Kind: PreprocessingToken }) + if (openParensCount == 1) { - if (replacement.TryGetValue(subToken.Text, out var parameterTokens)) + if (parameters.Length > parameterIndex) { - if (performStringReplace) - { - var stringValue = string.Join(string.Empty, parameterTokens.Select(t => t.Text)); - var escapedStringValue = stringValue.Replace("\\", "\\\\") - .Replace("\"", "\\\"") - ; - escapedStringValue = $"\"{escapedStringValue}\""; - yield return new Token(token.Range, escapedStringValue, token.Kind); - performStringReplace = false; - } - else + replacement.Add(parameters[parameterIndex], currentParameter); + parameterIndex++; + currentParameter = new(); + } + else if (functionMacro.hasEllipsis) + { + if (replacement.TryGetValue("__VA_ARGS__", out var va_args)) { - foreach (var parameterToken in parameterTokens) - { - yield return new Token(token.Range, parameterToken.Text, parameterToken.Kind); - } + va_args.AddRange(currentParameter); + va_args.Add(new Token(token.Range, ",", CPreprocessorTokenType.Separator)); } } else { - yield return new Token(token.Range, subToken.Text, subToken.Kind); + throw new PreprocessorException($"The function {functionMacro.Name} defined at {parametersParsingToken.Range} has more parameters than the macro allows."); } + + currentParameter = new(); } else { - yield return new Token(token.Range, subToken.Text, subToken.Kind); + currentParameter.Add(parametersParsingToken); } - } + break; + default: + if (openParensCount == 0 && parametersParsingToken.Kind == WhiteSpace) + { + continue; + } + + currentParameter.Add(parametersParsingToken); + break; + } + } + while (openParensCount > 0 || !hitOpenToken); + + + if (parameters.Length > parameterIndex) + { + replacement.Add(parameters[parameterIndex], currentParameter); + } + else + { + if (replacement.TryGetValue("__VA_ARGS__", out var va_args_)) + { + va_args_.AddRange(currentParameter); + } + } + } + else + { + IToken parametersParsingToken; + int openParensCount = 0; + + do + { + parametersParsingToken = stream.Consume(); + switch (parametersParsingToken) + { + case { Kind: LeftParen }: + openParensCount++; + break; + case { Kind: RightParen }: + openParensCount--; + break; + default: break; + } + } while (openParensCount > 0); + } + + } + + bool performStringReplace = false; + var nestedStream = new EnumerableStream>(tokenReplacement); + while (!nestedStream.IsEnd) + { + var subToken = nestedStream.Consume(); + if (subToken is { Kind: Hash }) + { + performStringReplace = true; + continue; + } + + if (subToken is { Kind: PreprocessingToken }) + { + if (replacement.TryGetValue(subToken.Text, out var parameterTokens)) + { + if (performStringReplace) + { + var stringValue = string.Join(string.Empty, parameterTokens.Select(t => t.Text)); + var escapedStringValue = stringValue.Replace("\\", "\\\\") + .Replace("\"", "\\\"") + ; + escapedStringValue = $"\"{escapedStringValue}\""; + yield return new Token(token.Range, escapedStringValue, token.Kind); + performStringReplace = false; } else { - yield return token; + foreach (var parameterToken in parameterTokens) + { + yield return new Token(token.Range, parameterToken.Text, parameterToken.Kind); + } } } - break; + else + { + foreach (var nestedT in ReplaceMacro(subToken, nestedStream)) + { + yield return nestedT; - default: - throw new PreprocessorException($"Illegal token {token.Kind} {token.Text}."); + } + } + } + else + { + yield return new Token(token.Range, subToken.Text, subToken.Kind); + } } } + else + { + yield return token; + } } private IEnumerable> ReadDirectiveLine( @@ -436,15 +452,23 @@ IEnumerable> ConsumeLineAll() case "define": { var expressionTokens = ConsumeLineAll(); - var (macroDefinition, replacement) = EvaluateMacroDefinition(expressionTokens.ToList()); - MacroContext.DefineMacro(macroDefinition.Name, macroDefinition, replacement); + if (IncludeTokens) + { + var (macroDefinition, replacement) = EvaluateMacroDefinition(expressionTokens.ToList()); + MacroContext.DefineMacro(macroDefinition.Name, macroDefinition, replacement); + } + return Array.Empty>(); } case "undef": { var expressionTokens = ConsumeLineAll(); - var (macroDefinition, replacement) = EvaluateMacroDefinition(expressionTokens.ToList()); - MacroContext.UndefineMacro(macroDefinition.Name); + if (IncludeTokens) + { + var (macroDefinition, replacement) = EvaluateMacroDefinition(expressionTokens.ToList()); + MacroContext.UndefineMacro(macroDefinition.Name); + } + return Array.Empty>(); } case "ifdef": diff --git a/Cesium.Runtime.Tests/StdLibFunctionTests.cs b/Cesium.Runtime.Tests/StdLibFunctionTests.cs new file mode 100644 index 00000000..7ae8539c --- /dev/null +++ b/Cesium.Runtime.Tests/StdLibFunctionTests.cs @@ -0,0 +1,58 @@ +using System.Text; + +namespace Cesium.Runtime.Tests; + +public class StdLibFunctionTests +{ + [Theory] + [InlineData("1010", 2, 10)] + [InlineData("12", 8, 10)] + [InlineData("A", 16, 10)] + [InlineData("junk", 36, 926192)] + [InlineData(" -40", 10, -40)] + [InlineData("012", 0, 10)] + [InlineData("0xA", 0, 10)] + [InlineData("junk", 0, 0)] + public unsafe void StrToL(string input, int @base, long expectedResult) + { + var stringBytes = Encoding.UTF8.GetBytes(input); + fixed (byte* str = stringBytes) + { + var actual = StdLibFunctions.StrToL(str, null, @base); + + Assert.Equal(expectedResult, actual); + } + + byte* strEnd; + fixed (byte* str = stringBytes) + { + var actual = StdLibFunctions.StrToL(str, &strEnd, @base); + + Assert.Equal(expectedResult, actual); + } + } + + [Fact] + public unsafe void StrToLOutOfRange() + { + var stringBytes = Encoding.UTF8.GetBytes("10 200000000000000000000000000000"); + fixed (byte* str = stringBytes) + { + byte* str_end; + StdLibFunctions.StrToL(str, &str_end, 10); + var actual = StdLibFunctions.StrToL(str_end, &str_end, 10); + var errorCode = *StdLibFunctions.GetErrNo(); + Assert.Equal(34, errorCode); + Assert.Equal(long.MaxValue, actual); + } + + stringBytes = Encoding.UTF8.GetBytes(" -200000000000000000000000000000"); + fixed (byte* str = stringBytes) + { + var actual = StdLibFunctions.StrToL(str, null, 10); + var errorCode = *StdLibFunctions.GetErrNo(); + Assert.Equal(34, errorCode); + Assert.Equal(long.MinValue, actual); + } + } +} diff --git a/Cesium.Runtime/StdIoFunctions.cs b/Cesium.Runtime/StdIoFunctions.cs index 44bb1328..4a983e77 100644 --- a/Cesium.Runtime/StdIoFunctions.cs +++ b/Cesium.Runtime/StdIoFunctions.cs @@ -1,8 +1,5 @@ -#if NETSTANDARD -using System.Text; -#else using System.Runtime.InteropServices; -#endif +using System.Text; namespace Cesium.Runtime; @@ -141,6 +138,23 @@ public static int FPrintF(void* stream, byte* str, void* varargs) addition++; } + int precision = 0; // 0 - not set, -1 - star + if (formatString[formatStartPosition + addition] == '.') + { + addition++; + if (formatString[formatStartPosition + addition] == '*') + { + precision = -1; + addition++; + + while (formatString[formatStartPosition + addition] >= '0' && formatString[formatStartPosition + addition] <= '9') + { + precision = precision * 10 + (formatString[formatStartPosition + addition] - '0'); + addition++; + } + } + } + string formatSpecifier = formatString[formatStartPosition + addition].ToString(); if (formatString[formatStartPosition + addition] == 'l') { @@ -151,8 +165,22 @@ public static int FPrintF(void* stream, byte* str, void* varargs) switch (formatSpecifier) { case "s": + int trim = -1; + if (precision == -1) + { + trim = (int)((long*)varargs)[consumedArgs]; + consumedArgs++; + } + string? stringValue = RuntimeHelpers.Unmarshal((byte*)((long*)varargs)[consumedArgs]); - streamWriter.Write(stringValue); + if (precision == -1) + { + streamWriter.Write(stringValue?.Substring(0, Math.Max(0, Math.Min(stringValue.Length - 1, trim)))); + } + else + { + streamWriter.Write(stringValue); + } consumedBytes += stringValue?.Length ?? 0; consumedArgs++; break; @@ -162,8 +190,9 @@ public static int FPrintF(void* stream, byte* str, void* varargs) consumedArgs++; break; case "d": - case "li": + case "ld": case "i": + case "li": int intValue = (int)((long*)varargs)[consumedArgs]; var intValueString = intValue.ToString(); streamWriter.Write(intValueString); @@ -247,6 +276,25 @@ public static int FPrintF(void* stream, byte* str, void* varargs) #endif } + internal static byte* MarshalStr(string? str) + { + Encoding encoding = Encoding.UTF8; + if (str is null) + { + return null; + } + + var bytes = encoding.GetBytes(str); + var storage = (byte*)StdLibFunctions.Malloc((nuint)bytes.Length + 1); + for (var i = 0; i < bytes.Length;i++) + { + storage[i] = bytes[i]; + } + + storage[bytes.Length] = 0; + return storage; + } + private static StreamHandle? GetStreamHandle(void* stream) { var handleIndex = (int)(IntPtr)stream; diff --git a/Cesium.Runtime/StdLibFunctions.cs b/Cesium.Runtime/StdLibFunctions.cs index 52be5611..3b539923 100644 --- a/Cesium.Runtime/StdLibFunctions.cs +++ b/Cesium.Runtime/StdLibFunctions.cs @@ -1,4 +1,6 @@ +using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Cesium.Runtime; @@ -11,6 +13,9 @@ public unsafe static class StdLibFunctions public const int RAND_MAX = 0x7FFFFFFF; private static Random shared = new(); + [FixedAddressValueType] + private static int errNo; + public static int Abs(int value) { if (value == int.MinValue) return int.MinValue; @@ -91,6 +96,19 @@ public static void Free(void* ptr) #endif } + public static int* GetErrNo() + { + fixed (int* errNoPtr = &errNo) + { + return errNoPtr; + } + } + + internal static void SetErrNo(int newErrorCode) + { + errNo = newErrorCode; + } + public static void* Сalloc(UIntPtr num, UIntPtr size) { #if NETSTANDARD @@ -119,4 +137,85 @@ public static int Atoi(byte* ptr) var str = StdIoFunctions.Unmarshal(ptr); return Convert.ToInt32(str); } + + public static byte* GetEnv(byte* ptr) + { + var str = StdIoFunctions.Unmarshal(ptr); + if (str is null) + { + return null; + } + + return StdIoFunctions.MarshalStr(Environment.GetEnvironmentVariable(str)); + } + + public static long StrToL(byte* str, byte** str_end, int @base) + { + byte* current = str; + byte currentChar; + do + { + currentChar = *current++; + } + while (CTypeFunctions.IsSpace(currentChar) != 0); + + bool negate = false; + if (currentChar == '-') + { + negate = true; + currentChar = *current++; + } + else if (currentChar == '+') + currentChar = *current++; + + if ((@base == 0 || @base == 16) && + currentChar == '0' && (*current == 'x' || *current == 'X')) + { + currentChar = current[1]; + current += 2; + @base = 16; + } + + if (@base == 0) + { + @base = currentChar == '0' ? 8 : 10; + } + + long cutoff = negate ? long.MinValue : long.MaxValue; + long cutlim = negate ? -(cutoff % @base) : (cutoff % @base); + cutoff /= @base; + if (negate) cutoff = -cutoff; + long result = 0; + int foundSomething = 0; + for (result = 0, foundSomething = 0; ; currentChar = *current++) + { + if (CTypeFunctions.IsDigit(currentChar) != 0) + currentChar -= (byte)'0'; + else if (CTypeFunctions.IsAlpha(currentChar) != 0) + currentChar -= (byte)((CTypeFunctions.IsUpper(currentChar) != 0) ? (byte)'A' - 10 : (byte)'a' - 10); + else + break; + if (currentChar >= @base) + break; + if (foundSomething < 0 || result > cutoff || (result == cutoff && currentChar > cutlim)) + foundSomething = -1; + else + { + foundSomething = 1; + result *= @base; + result += currentChar; + } + } + + if (foundSomething < 0) + { + result = negate ? long.MinValue : long.MaxValue; + errNo = 34 /*ERANGE*/; + } + else if (negate) + result = -result; + if (str_end != null) + *str_end = (foundSomething != 0) ? current - 1 : str; + return result; + } } diff --git a/Cesium.Runtime/StringFunctions.cs b/Cesium.Runtime/StringFunctions.cs index c3338f3e..78bdeda4 100644 --- a/Cesium.Runtime/StringFunctions.cs +++ b/Cesium.Runtime/StringFunctions.cs @@ -184,4 +184,23 @@ public static int StrNCmp(byte* lhs, byte* rhs, nuint count) return dest; } + public static byte* StrChr(byte* str, int ch) + { + if (str == null) + { + return null; + } + + while (*str != 0) + { + if (*str == ch) + { + return str; + } + + str++; + } + + return null; + } }