diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..d7e67c9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "ref.h": "c" + } +} \ No newline at end of file diff --git a/compiler/NubLang/Ast/Node.cs b/compiler/NubLang/Ast/Node.cs index d49ab12..df42b4a 100644 --- a/compiler/NubLang/Ast/Node.cs +++ b/compiler/NubLang/Ast/Node.cs @@ -554,6 +554,16 @@ public class DereferenceNode(List tokens, NubType type, ExpressionNode ta } } +public class RefDereferenceNode(List tokens, NubType type, ExpressionNode target) : LValueExpressionNode(tokens, type) +{ + public ExpressionNode Target { get; } = target; + + public override IEnumerable Children() + { + yield return Target; + } +} + public class SizeNode(List tokens, NubType TargetType) : RValueExpressionNode(tokens, new NubIntType(false, 64)) { public NubType TargetType { get; } = TargetType; @@ -574,6 +584,19 @@ public class CastNode(List tokens, NubType type, ExpressionNode value) : } } +public class RefStructInitializerNode(List tokens, NubType type, Dictionary initializers) : RValueExpressionNode(tokens, type) +{ + public Dictionary Initializers { get; } = initializers; + + public override IEnumerable Children() + { + foreach (var initializer in Initializers) + { + yield return initializer.Value; + } + } +} + public class EnumReferenceIntermediateNode(List tokens, IdentifierToken moduleToken, IdentifierToken nameToken) : IntermediateExpression(tokens) { public IdentifierToken ModuleToken { get; } = moduleToken; diff --git a/compiler/NubLang/Ast/NubType.cs b/compiler/NubLang/Ast/NubType.cs index 887babd..40251cb 100644 --- a/compiler/NubLang/Ast/NubType.cs +++ b/compiler/NubLang/Ast/NubType.cs @@ -57,6 +57,15 @@ public sealed class NubPointerType(NubType baseType) : NubType public override int GetHashCode() => HashCode.Combine(typeof(NubPointerType), BaseType); } +public class NubRefType(NubType baseType) : NubType +{ + public NubType BaseType { get; } = baseType; + + public override string ToString() => "&" + BaseType; + public override bool Equals(NubType? other) => other is NubRefType; + public override int GetHashCode() => HashCode.Combine(typeof(NubRefType)); +} + public class NubFuncType(List parameters, NubType returnType) : NubType { public List Parameters { get; } = parameters; diff --git a/compiler/NubLang/Ast/TypeChecker.cs b/compiler/NubLang/Ast/TypeChecker.cs index 1323245..147dc83 100644 --- a/compiler/NubLang/Ast/TypeChecker.cs +++ b/compiler/NubLang/Ast/TypeChecker.cs @@ -795,15 +795,16 @@ public sealed class TypeChecker } } - private DereferenceNode CheckDereference(DereferenceSyntax expression, NubType? _) + private ExpressionNode CheckDereference(DereferenceSyntax expression, NubType? _) { var target = CheckExpression(expression.Target); - if (target.Type is not NubPointerType pointerType) - { - throw new TypeCheckerException(Diagnostic.Error($"Cannot dereference non-pointer type {target.Type}").At(expression).Build()); - } - return new DereferenceNode(expression.Tokens, pointerType.BaseType, target); + return target.Type switch + { + NubPointerType pointerType => new DereferenceNode(expression.Tokens, pointerType.BaseType, target), + NubRefType refType => new RefDereferenceNode(expression.Tokens, refType.BaseType, target), + _ => throw new TypeCheckerException(Diagnostic.Error($"Cannot dereference non-pointer type {target.Type}").At(expression).Build()) + }; } private FuncCallNode CheckFuncCall(FuncCallSyntax expression, NubType? _) @@ -1019,24 +1020,29 @@ public sealed class TypeChecker } } - if (target.Type is NubStructType structType) + switch (target.Type) { - var field = structType.Fields.FirstOrDefault(x => x.Name == expression.MemberToken.Value); - if (field == null) + case NubStructType structType: + { + var field = structType.Fields.FirstOrDefault(x => x.Name == expression.MemberToken.Value); + if (field == null) + { + throw new TypeCheckerException(Diagnostic + .Error($"Struct {target.Type} does not have a field with the name {expression.MemberToken.Value}") + .At(expression) + .Build()); + } + + return new StructFieldAccessNode(expression.Tokens, field.Type, target, expression.MemberToken); + } + default: { throw new TypeCheckerException(Diagnostic - .Error($"Struct {target.Type} does not have a field with the name {expression.MemberToken.Value}") + .Error($"Cannot access struct member {expression.MemberToken.Value} on type {target.Type}") .At(expression) .Build()); } - - return new StructFieldAccessNode(expression.Tokens, field.Type, target, expression.MemberToken); } - - throw new TypeCheckerException(Diagnostic - .Error($"Cannot access struct member {expression.MemberToken.Value} on type {target.Type}") - .At(expression) - .Build()); } private static long CalculateSignedEnumFieldValue(EnumSyntax enumDef, EnumFieldSyntax field) @@ -1083,7 +1089,7 @@ public sealed class TypeChecker throw new UnreachableException(); } - private StructInitializerNode CheckStructInitializer(StructInitializerSyntax expression, NubType? expectedType) + private ExpressionNode CheckStructInitializer(StructInitializerSyntax expression, NubType? expectedType) { NubStructType? structType = null; @@ -1097,9 +1103,14 @@ public sealed class TypeChecker structType = checkedStructType; } - else if (expectedType is NubStructType expectedStructType) + else { - structType = expectedStructType; + structType = expectedType switch + { + NubStructType expectedStructType => expectedStructType, + NubRefType { BaseType: NubStructType expectedStructType } => expectedStructType, + _ => structType + }; } if (structType == null) @@ -1142,7 +1153,14 @@ public sealed class TypeChecker .Build()); } - return new StructInitializerNode(expression.Tokens, structType, initializers); + if (expectedType is NubRefType refType && refType.BaseType == structType) + { + return new RefStructInitializerNode(expression.Tokens, refType, initializers); + } + else + { + return new StructInitializerNode(expression.Tokens, structType, initializers); + } } private BlockNode CheckBlock(BlockSyntax node) @@ -1197,6 +1215,7 @@ public sealed class TypeChecker SliceTypeSyntax slice => new NubSliceType(ResolveType(slice.BaseType)), ConstArrayTypeSyntax arr => new NubConstArrayType(ResolveType(arr.BaseType), arr.Size), PointerTypeSyntax ptr => new NubPointerType(ResolveType(ptr.BaseType)), + RefTypeSyntax r => new NubRefType(ResolveType(r.BaseType)), StringTypeSyntax => new NubStringType(), CustomTypeSyntax c => ResolveCustomType(c), VoidTypeSyntax => new NubVoidType(), diff --git a/compiler/NubLang/Generation/CType.cs b/compiler/NubLang/Generation/CType.cs index 9fe4287..5da4c67 100644 --- a/compiler/NubLang/Generation/CType.cs +++ b/compiler/NubLang/Generation/CType.cs @@ -10,15 +10,16 @@ public static class CType { NubVoidType => "void" + (variableName != null ? $" {variableName}" : ""), NubBoolType => "bool" + (variableName != null ? $" {variableName}" : ""), - NubIntType intType => CreateIntType(intType, variableName), - NubFloatType floatType => CreateFloatType(floatType, variableName), - NubPointerType ptr => CreatePointerType(ptr, variableName), + NubIntType i => CreateIntType(i, variableName), + NubFloatType f => CreateFloatType(f, variableName), + NubPointerType p => CreatePointerType(p, variableName), + NubRefType r => CreateRefType(r, variableName), NubSliceType => "struct nub_slice" + (variableName != null ? $" {variableName}" : ""), NubStringType => "struct nub_string" + (variableName != null ? $" {variableName}" : ""), - NubConstArrayType arr => CreateConstArrayType(arr, variableName, constArraysAsPointers), - NubArrayType arr => CreateArrayType(arr, variableName), - NubFuncType fn => CreateFuncType(fn, variableName), - NubStructType st => $"struct {st.Module}_{st.Name}_{NameMangler.Mangle(st)}" + (variableName != null ? $" {variableName}" : ""), + NubConstArrayType a => CreateConstArrayType(a, variableName, constArraysAsPointers), + NubArrayType a => CreateArrayType(a, variableName), + NubFuncType f => CreateFuncType(f, variableName), + NubStructType s => $"struct {s.Module}_{s.Name}_{NameMangler.Mangle(s)}" + (variableName != null ? $" {variableName}" : ""), _ => throw new NotSupportedException($"C type generation not supported for: {type}") }; } @@ -47,6 +48,12 @@ public static class CType return cType + (varName != null ? $" {varName}" : ""); } + private static string CreateRefType(NubRefType ptr, string? varName) + { + var baseType = Create(ptr.BaseType); + return baseType + "*" + (varName != null ? $" {varName}" : ""); + } + private static string CreatePointerType(NubPointerType ptr, string? varName) { var baseType = Create(ptr.BaseType); diff --git a/compiler/NubLang/Generation/Generator.cs b/compiler/NubLang/Generation/Generator.cs index 432b4b0..fc16f28 100644 --- a/compiler/NubLang/Generation/Generator.cs +++ b/compiler/NubLang/Generation/Generator.cs @@ -10,6 +10,7 @@ public class Generator private readonly CompilationUnit _compilationUnit; private readonly IndentedTextWriter _writer; private readonly Stack> _deferStack = []; + private readonly Stack> _refCleanupStack = []; private int _tmpIndex; public Generator(CompilationUnit compilationUnit) @@ -32,6 +33,12 @@ public class Generator public string Emit() { _writer.WriteLine(""" + #include + + void *rc_alloc(size_t size, void (*destructor)(void *self)); + void rc_retain(void *obj); + void rc_release(void *obj); + struct nub_string { unsigned long long length; @@ -262,31 +269,35 @@ public class Generator EmitStatement(blockDefers[i].Statement); } + var refCleanups = _refCleanupStack.Peek(); + foreach (var refCleanup in refCleanups) + { + _writer.WriteLine($"rc_release({refCleanup});"); + } + _writer.WriteLine("return;"); } else { var returnValue = EmitExpression(returnNode.Value); - if (_deferStack.Peek().Count != 0) - { - var tmp = NewTmp(); - _writer.WriteLine($"{CType.Create(returnNode.Value.Type, tmp)} = {returnValue};"); + var tmp = NewTmp(); + _writer.WriteLine($"{CType.Create(returnNode.Value.Type, tmp)} = {returnValue};"); - var blockDefers = _deferStack.Peek(); - for (var i = blockDefers.Count - 1; i >= 0; i--) - { - EmitStatement(blockDefers[i].Statement); - } - - EmitLine(returnNode.Tokens.FirstOrDefault()); - _writer.WriteLine($"return {tmp};"); - } - else + var blockDefers = _deferStack.Peek(); + for (var i = blockDefers.Count - 1; i >= 0; i--) { - EmitLine(returnNode.Tokens.FirstOrDefault()); - _writer.WriteLine($"return {returnValue};"); + EmitStatement(blockDefers[i].Statement); } + + var refCleanups = _refCleanupStack.Peek(); + foreach (var refCleanup in refCleanups) + { + _writer.WriteLine($"rc_release({refCleanup});"); + } + + EmitLine(returnNode.Tokens.FirstOrDefault()); + _writer.WriteLine($"return {tmp};"); } } @@ -345,6 +356,8 @@ public class Generator FuncCallNode funcCallNode => EmitFuncCall(funcCallNode), FuncIdentifierNode funcIdentifierNode => FuncName(funcIdentifierNode.ModuleToken.Value, funcIdentifierNode.NameToken.Value, funcIdentifierNode.ExternSymbolToken?.Value), AddressOfNode addressOfNode => EmitAddressOf(addressOfNode), + RefDereferenceNode refDereferenceNode => EmitRefDereference(refDereferenceNode), + RefStructInitializerNode refStructInitializerNode => EmitRefStructInitializer(refStructInitializerNode), SizeNode sizeBuiltinNode => $"sizeof({CType.Create(sizeBuiltinNode.TargetType)})", SliceIndexAccessNode sliceIndexAccessNode => EmitSliceArrayIndexAccess(sliceIndexAccessNode), StringLiteralNode stringLiteralNode => EmitStringLiteral(stringLiteralNode), @@ -489,6 +502,38 @@ public class Generator return $"&{value}"; } + private string EmitRefDereference(RefDereferenceNode refDereferenceNode) + { + var pointer = EmitExpression(refDereferenceNode.Target); + return $"*{pointer}"; + } + + private string EmitRefStructInitializer(RefStructInitializerNode refStructInitializerNode) + { + var type = (NubRefType)refStructInitializerNode.Type; + var structType = (NubStructType)type.BaseType; + + var tmp = NewTmp(); + _writer.WriteLine($"{CType.Create(type)} {tmp} = ({CType.Create(type)})rc_alloc(sizeof({CType.Create(structType)}), NULL);"); + + var initValues = new List(); + foreach (var initializer in refStructInitializerNode.Initializers) + { + var value = EmitExpression(initializer.Value); + initValues.Add($".{initializer.Key.Value} = {value}"); + } + + var initString = initValues.Count == 0 + ? "0" + : string.Join(", ", initValues); + + _writer.WriteLine($"*{tmp} = ({CType.Create(structType)}){{{initString}}};"); + + _refCleanupStack.Peek().Add(tmp); + + return tmp; + } + private string EmitSliceArrayIndexAccess(SliceIndexAccessNode sliceIndexAccessNode) { var targetType = (NubSliceType)sliceIndexAccessNode.Target.Type; @@ -581,6 +626,7 @@ public class Generator private void EmitBlock(BlockNode blockNode) { _deferStack.Push([]); + _refCleanupStack.Push([]); foreach (var statementNode in blockNode.Statements) { @@ -592,5 +638,11 @@ public class Generator { EmitStatement(blockDefers[i].Statement); } + + var refCleanups = _refCleanupStack.Pop(); + foreach (var refCleanup in refCleanups) + { + _writer.WriteLine($"rc_release({refCleanup});"); + } } } \ No newline at end of file diff --git a/compiler/NubLang/Syntax/Parser.cs b/compiler/NubLang/Syntax/Parser.cs index 981bc54..cf05deb 100644 --- a/compiler/NubLang/Syntax/Parser.cs +++ b/compiler/NubLang/Syntax/Parser.cs @@ -716,6 +716,12 @@ public sealed class Parser } } + if (TryExpectSymbol(Symbol.Ampersand)) + { + var baseType = ParseType(); + return new RefTypeSyntax(GetTokens(startIndex), baseType); + } + if (TryExpectSymbol(Symbol.Caret)) { var baseType = ParseType(); diff --git a/compiler/NubLang/Syntax/Syntax.cs b/compiler/NubLang/Syntax/Syntax.cs index 8fb9c3a..0db9cb7 100644 --- a/compiler/NubLang/Syntax/Syntax.cs +++ b/compiler/NubLang/Syntax/Syntax.cs @@ -150,4 +150,6 @@ public record ConstArrayTypeSyntax(List Tokens, TypeSyntax BaseType, ulon public record CustomTypeSyntax(List Tokens, IdentifierToken? ModuleToken, IdentifierToken NameToken) : TypeSyntax(Tokens); +public record RefTypeSyntax(List Tokens, TypeSyntax BaseType) : TypeSyntax(Tokens); + #endregion \ No newline at end of file diff --git a/examples/playgroud/build.sh b/examples/playgroud/build.sh new file mode 100755 index 0000000..ff21919 --- /dev/null +++ b/examples/playgroud/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -euo pipefail + +obj=$(nubc main.nub) +clang $obj ../../runtime/.build/runtime.o -o .build/out \ No newline at end of file diff --git a/examples/playgroud/main.nub b/examples/playgroud/main.nub new file mode 100644 index 0000000..6e14781 --- /dev/null +++ b/examples/playgroud/main.nub @@ -0,0 +1,20 @@ +module main + +extern "puts" func puts(text: ^i8) + +struct Human +{ + age: u64 + name: ^i8 +} + +extern "main" func main(argc: i64, argv: [?]^i8): i64 +{ + let x: &Human = { + age = 23 + name = "test" + } + + puts(x^.name) + return 0 +} \ No newline at end of file diff --git a/runtime/.gitignore b/runtime/.gitignore new file mode 100644 index 0000000..b7f1399 --- /dev/null +++ b/runtime/.gitignore @@ -0,0 +1 @@ +.build \ No newline at end of file diff --git a/runtime/build.sh b/runtime/build.sh new file mode 100755 index 0000000..1772d13 --- /dev/null +++ b/runtime/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -euo pipefail + +mkdir -p .build +clang -c runtime.c -o .build/runtime.o \ No newline at end of file diff --git a/runtime/ref.c b/runtime/ref.c new file mode 100644 index 0000000..17ab78b --- /dev/null +++ b/runtime/ref.c @@ -0,0 +1,42 @@ +#include "ref.h" +#include +#include +#include + +void *rc_alloc(size_t size, void (*destructor)(void *self)) +{ + printf("rc_alloc %zu bytes\n", size); + ref_header *header = malloc(sizeof(ref_header) + size); + if (!header) + { + exit(69); + } + + header->ref_count = 1; + header->destructor = destructor; + + return (void *)(header + 1); +} + +void rc_retain(void *obj) +{ + printf("rc_retain\n"); + ref_header *header = ((ref_header *)obj) - 1; + header->ref_count++; +} + +void rc_release(void *obj) +{ + ref_header *header = ((ref_header *)obj) - 1; + printf("rc_release\n"); + if (--header->ref_count == 0) + { + if (header->destructor) + { + header->destructor(obj); + } + + free(header); + printf("rc_free\n"); + } +} \ No newline at end of file diff --git a/runtime/ref.h b/runtime/ref.h new file mode 100644 index 0000000..a7b1ce3 --- /dev/null +++ b/runtime/ref.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct +{ + int ref_count; + void (*destructor)(void *self); +} ref_header; + +void *rc_alloc(size_t size, void (*destructor)(void *self)); +void rc_retain(void *obj); +void rc_release(void *obj); diff --git a/runtime/runtime.c b/runtime/runtime.c new file mode 100644 index 0000000..f2c532a --- /dev/null +++ b/runtime/runtime.c @@ -0,0 +1 @@ +#include "ref.c" \ No newline at end of file