diff --git a/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs b/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs index 7f1ca60b1..435b83bb8 100644 --- a/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs +++ b/src/Draco.Compiler.Tests/EndToEnd/CompilingCodeTests.cs @@ -703,4 +703,19 @@ func main() { memory[0] = 1; } """); + + [Fact] + public void DefaultValueIntrinsic() + { + var assembly = Compile(""" + public func nullObj(): object = default(); + public func zeroInt(): int32 = default(); + """); + + var nullObj = Invoke(assembly, "nullObj"); + var zeroInt = Invoke(assembly, "zeroInt"); + + Assert.Null(nullObj); + Assert.Equal(0, zeroInt); + } } diff --git a/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs b/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs index 41a2e1e74..94ac58361 100644 --- a/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs +++ b/src/Draco.Compiler/Internal/Codegen/CilCodegen.cs @@ -50,6 +50,7 @@ internal sealed class CilCodegen private readonly Dictionary labels = []; private readonly Stackifier stackifier; private int treeDepth; + private int inlineAllocatedRegisterCount = 0; public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) { @@ -85,7 +86,7 @@ public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) private int? GetRegisterIndex(Register register) { if (SymbolEqualityComparer.Default.Equals(register.Type, WellKnownTypes.Unit)) return null; - if (this.stackifier.RegisterUses[register] == 0) return null; + if (register.Index >= 0 && this.stackifier.RegisterUses[register] == 0) return null; if (!this.allocatedRegisters.TryGetValue(register, out var allocatedRegister)) { // NOTE: We need to offset by the number of locals @@ -95,6 +96,13 @@ public CilCodegen(MetadataCodegen metadataCodegen, IProcedure procedure) return allocatedRegister; } + private int? AllocateRegister(TypeSymbol type) + { + --this.inlineAllocatedRegisterCount; + var register = new Register(type, this.inlineAllocatedRegisterCount); + return this.GetRegisterIndex(register); + } + private LabelHandle GetLabel(IBasicBlock block) { if (!this.labels.TryGetValue(block, out var label)) @@ -472,6 +480,16 @@ private void EncodePush(IOperand operand) throw new NotImplementedException(); } break; + case DefaultValue d: + { + var local = this.AllocateRegister(d.Type); + if (local is null) return; + this.InstructionEncoder.LoadLocalAddress(local.Value); + this.InstructionEncoder.OpCode(ILOpCode.Initobj); + this.EncodeToken(d.Type); + this.InstructionEncoder.LoadLocal(local.Value); + break; + } default: throw new ArgumentOutOfRangeException(nameof(operand)); } diff --git a/src/Draco.Compiler/Internal/OptimizingIr/Model/DefaultValue.cs b/src/Draco.Compiler/Internal/OptimizingIr/Model/DefaultValue.cs new file mode 100644 index 000000000..f0071b220 --- /dev/null +++ b/src/Draco.Compiler/Internal/OptimizingIr/Model/DefaultValue.cs @@ -0,0 +1,12 @@ +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.OptimizingIr.Model; + +/// +/// Represents the default value of a type. +/// +/// The type the default value corresponds to. +internal readonly record struct DefaultValue(TypeSymbol Type) : IOperand +{ + public string ToOperandString() => $"default({this.Type})"; +} diff --git a/src/Draco.Compiler/Internal/Solver/ConstraintGraphTracer.cs b/src/Draco.Compiler/Internal/Solver/ConstraintGraphTracer.cs index a3c8bd21c..649455f81 100644 --- a/src/Draco.Compiler/Internal/Solver/ConstraintGraphTracer.cs +++ b/src/Draco.Compiler/Internal/Solver/ConstraintGraphTracer.cs @@ -7,7 +7,6 @@ using Draco.Chr.Tracing; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Utilities; -using static System.Net.Mime.MediaTypeNames; using Constraint = Draco.Compiler.Internal.Solver.Constraints.Constraint; using IChrConstraint = Draco.Chr.Constraints.IConstraint; diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultValueFunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultValueFunctionSymbol.cs new file mode 100644 index 000000000..8af71a616 --- /dev/null +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/DefaultValueFunctionSymbol.cs @@ -0,0 +1,39 @@ +using System.Collections.Immutable; +using Draco.Compiler.Internal.OptimizingIr.Model; + +namespace Draco.Compiler.Internal.Symbols.Synthetized; + +/// +/// An intrinsic to provide the default value of a type (like `default(T)` in C#). +/// +/// Signature: +/// func default(): T; +/// +internal sealed class DefaultValueFunctionSymbol : FunctionSymbol +{ + /// + /// A singleton instance of the default value function intrinsic. + /// + public static DefaultValueFunctionSymbol Instance { get; } = new(); + + public override string Name => "default"; + public override Api.Semantics.Visibility Visibility => Api.Semantics.Visibility.Public; + + public override ImmutableArray GenericParameters => + InterlockedUtils.InitializeDefault(ref this.genericParameters, this.BuildGenericParameters); + private ImmutableArray genericParameters; + + public override ImmutableArray Parameters => []; + + public override TypeSymbol ReturnType => this.GenericParameters[0]; + + public override CodegenDelegate Codegen => (codegen, targetType, args) => new DefaultValue(targetType); + + private DefaultValueFunctionSymbol() + { + } + + private ImmutableArray BuildGenericParameters() => [ + new SynthetizedTypeParameterSymbol(this, "T"), + ]; +} diff --git a/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs b/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs index a9d31931d..3a3d236c9 100644 --- a/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs +++ b/src/Draco.Compiler/Internal/Symbols/WellKnownTypes.cs @@ -53,6 +53,9 @@ private IEnumerable GenerateWellKnownTypes() yield return Alias("string", this.SystemString); yield return Alias("object", this.SystemObject); + // Default value + yield return DefaultValueFunctionSymbol.Instance; + // 1D array yield return this.ArrayType; yield return this.ArrayCtor;