diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c2f84b2..84e2e55 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -40,5 +40,5 @@ jobs:
- name: Build dotnet
run: dotnet build
- # - name: Test
- # run: dotnet test
+ - name: Test
+ run: dotnet test
diff --git a/.gitignore b/.gitignore
index 5fe29b7..aaba79d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,4 @@ surab-lsp-server.log
*.so
*.nupkg
editor-groups.json
+*.csproj.user
diff --git a/src/common/Helpers/PathHelper.cs b/src/common/Helpers/PathHelper.cs
index d1e43bd..dfe3c5d 100644
--- a/src/common/Helpers/PathHelper.cs
+++ b/src/common/Helpers/PathHelper.cs
@@ -6,7 +6,17 @@ internal static class PathHelper
{
public static string GetRepoRootFolderPath()
{
- return Path.Combine(GetCallerFilePath(), "../../../../");
+ // Climb up until we find it.
+ var cd = Environment.CurrentDirectory;
+ // Match when we find surab/src.
+ while (Path.GetFileName(cd) != "src" && Path.GetFileName(Path.GetDirectoryName(cd)) != "surab")
+ {
+ cd = Path.GetDirectoryName(cd);
+ }
+ // One more to get repo directory here.
+ cd = Path.GetDirectoryName(cd);
+
+ return cd!;
}
public static string GetSrcFolderPath()
@@ -29,6 +39,9 @@ public static string GetRuntimeTargetFolderPath()
return Path.Combine(GetRepoRootFolderPath(), "target");
}
+ // Note: Used to use this in GetRepoRootFolderPath but CallerFilePath computes the path on
+ // compile time so it might produce results not compatible with linux (e.g. if debugging in wsl
+ // in VS).
private static string GetCallerFilePath([CallerFilePath] string callerFilePath = null!)
{
return callerFilePath;
diff --git a/src/compilation/Compiler.cs b/src/compilation/Compiler.cs
index 907a31c..efb4266 100644
--- a/src/compilation/Compiler.cs
+++ b/src/compilation/Compiler.cs
@@ -1,5 +1,6 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
+
using Surab.Analysis;
using Surab.Compilation.LLVM;
@@ -34,6 +35,20 @@ public static string Compile(
{
// Note: We're not taking any locks (particularly read) as the compilation doesn't run in an asynchronous environment right now.
+ if (string.IsNullOrEmpty(targetTriple))
+ {
+ // TODO: temp
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ targetTriple = "x86_64-pc-windows-msvc";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ targetTriple = "x86_64-pc-linux-gnu";
+ }
+ Console.WriteLine($"Setting target triple to: {targetTriple}");
+ }
+
// TODO: std project diags are not being reported right now
var involvedProjects = project.GetInvolvedProjects();
var tasks = involvedProjects.Select(p => p.EnsureAnalyzedAsync());
@@ -106,7 +121,18 @@ private string Compile()
private static Target ParseTargetTriple(string targetTriple)
{
// TODO
- return new x86_64WindowsMSVCTarget();
+ if (targetTriple == "x86_64-pc-windows-msvc")
+ {
+ return new x86_64WindowsMSVCTarget();
+ }
+ else if (targetTriple == "x86_64-pc-linux-gnu")
+ {
+ return new x86_64LinuxGNUTarget();
+ }
+ else
+ {
+ throw new UnreachableException();
+ }
}
}
@@ -293,12 +319,12 @@ private void SetupParams()
// Adaptations of params inside fn body
// ---
- var lowering = GetLLVMLowering(t.Type);
- switch (lowering.Kind)
+ var info = LLVMTarget.ComputeInfo(t.Type, isReturnType: false);
+ switch (info)
{
- case LLVMTypeLoweringKind.ByValue:
+ case DirectABIArgInfo:
{
- var alignment = Target.GetAbiAlignment(paramType);
+ var alignment = Target.GetAlignment(paramType);
var ptr = LLVM.Builder.BuildAlloca(llvmType, paramName);
ptr.SetAlignment(alignment.Bytes);
@@ -308,19 +334,20 @@ private void SetupParams()
CreateParamUnit(t.Value, unit);
}
break;
- case LLVMTypeLoweringKind.ByCopyRef:
+ case IndirectABIArgInfo:
{
// paramValue is already a ptr. Treat it as the wrapper ptr directly.
- var alignment = Target.GetAbiAlignment(t.Type);
+ var alignment = Target.GetAlignment(t.Type);
Debug.Assert(paramValue.TypeOf.Kind is SCPPTypeKind.pointer, "Param value should already be a ptr.");
var unit = LLVMUnit.Wrapped(llvmType, paramValue);
CreateParamUnit(t.Value, unit);
}
break;
- case LLVMTypeLoweringKind.AsInt:
+ case AsIntegerABIArgInfo:
{
var layout = Target.GetLayout(t.Type);
var llvmIntType = SCPPTypeRef.CreateInt(layout.Size.Bits);
+ // TODO: We don't use GetAbiAlignmentOfType in other places, why do we have it here?
var alignment = Math.Max(layout.Alignment.Bytes, LLVM.TargetMachine.GetAbiAlignmentOfType(llvmIntType)).AsBytes();
var ptr = LLVM.Builder.BuildAlloca(llvmType, paramName);
@@ -331,6 +358,25 @@ private void SetupParams()
CreateParamUnit(t.Value, unit);
}
break;
+ case AsRealABIArgInfo:
+ {
+ var layout = Target.GetLayout(t.Type);
+ // TODO: Refactor this mapping into a place.
+ var llvmRealType = layout.Size.Bits switch
+ {
+ 32 => SCPPTypeRef.Float,
+ 64 => SCPPTypeRef.Double,
+ _ => throw new UnreachableException(),
+ };
+
+ // TODO: Same as in AsInteger, do we need GEP or memcpy instead here?
+ var ptr = LLVM.Builder.BuildAlloca(llvmType, paramName);
+ LLVM.Builder.BuildStore(paramValue, ptr);
+ var unit = LLVMUnit.Wrapped(llvmType, ptr);
+
+ CreateParamUnit(t.Value, unit);
+ }
+ break;
}
llvmParamOffset++;
@@ -370,7 +416,67 @@ public override void VisitReturnStmt(HirReturnStmt node)
{
sretPtr = _llvmFnUnit.Value.GetParam(0);
}
- LLVMTarget.CompileFnReturnStmt(FnValue.ReturnType, exprUnit, sretPtr);
+ CompileFnReturnStmt(FnValue.ReturnType, exprUnit, sretPtr);
+ }
+ }
+
+ private void CompileFnReturnStmt(HirType returnType, LLVMUnit exprUnit, SCPPValueRef? sretOpt)
+ {
+ var info = LLVMTarget.ComputeInfo(returnType, isReturnType: true);
+
+ if (info is IgnoreABIArgInfo)
+ {
+ return;
+ }
+
+ if (info is DirectABIArgInfo)
+ {
+ var (_, value) = exprUnit.Unwrap(LLVM);
+ LLVM.Builder.BuildRet(value);
+ return;
+ }
+
+ if (info is IndirectABIArgInfo)
+ {
+ // sret
+ Debug.Assert(sretOpt.HasValue);
+ var sretPtr = sretOpt.Value;
+ Debug.Assert(sretPtr.TypeOf.Kind is SCPPTypeKind.pointer);
+ var (_, exprValue) = exprUnit.Unwrap(LLVM);
+ LLVM.Builder.BuildStore(exprValue, sretPtr);
+ LLVM.Builder.BuildRetVoid();
+ return;
+ }
+
+ if (info is AsIntegerABIArgInfo)
+ {
+ // abi int
+ Debug.Assert(exprUnit.IsWrapped);
+ var (_, exprValuePtr) = exprUnit;
+ var layout = Target.GetLayout(returnType);
+ var llvmIntType = SCPPTypeRef.CreateInt(layout.Size.Bits);
+ var loaded = LLVM.Builder.BuildLoad(llvmIntType, exprValuePtr);
+ loaded.SetAlignment(layout.Alignment.Bytes);
+ LLVM.Builder.BuildRet(loaded);
+ return;
+ }
+
+ if (info is AsRealABIArgInfo)
+ {
+ // abi real
+ Debug.Assert(exprUnit.IsWrapped);
+ var (_, exprValuePtr) = exprUnit;
+ var layout = Target.GetLayout(returnType);
+ var llvmRealType = layout.Size.Bits switch
+ {
+ 32 => SCPPTypeRef.Float,
+ 64 => SCPPTypeRef.Double,
+ _ => throw new UnreachableException(),
+ };
+ var loaded = LLVM.Builder.BuildLoad(llvmRealType, exprValuePtr);
+ loaded.SetAlignment(layout.Alignment.Bytes);
+ LLVM.Builder.BuildRet(loaded);
+ return;
}
}
@@ -412,16 +518,16 @@ public override void VisitCallExpr(HirCallExpr node)
// ---
var paramType = fnType.ParamTypes[argOffset];
- var lowering = GetLLVMLowering(paramType);
- switch (lowering.Kind)
+ var info = LLVMTarget.ComputeInfo(paramType, isReturnType: false);
+ switch (info)
{
- case LLVMTypeLoweringKind.ByValue:
+ case DirectABIArgInfo:
{
var (_, argValue) = argUnit.Unwrap(LLVM);
args.Add(argValue);
}
break;
- case LLVMTypeLoweringKind.ByCopyRef:
+ case IndirectABIArgInfo:
{
var llvmParamType = LLVMTarget.LowerType(paramType);
// Copy the value into a new ptr to preserve *pass by copy* semantics.
@@ -431,7 +537,7 @@ public override void VisitCallExpr(HirCallExpr node)
args.Add(copyPtr);
}
break;
- case LLVMTypeLoweringKind.AsInt:
+ case AsIntegerABIArgInfo:
{
// Might not be right, so this assertion is to detect a case where this isn't
// true so we can look into it when it happens.
@@ -440,6 +546,24 @@ public override void VisitCallExpr(HirCallExpr node)
var layout = Target.GetLayout(paramType);
var llvmIntType = SCPPTypeRef.CreateInt(layout.Size.Bits);
var loaded = LLVM.Builder.BuildLoad(llvmIntType, argValuePtr);
+ // TODO: In SetupParams, we're computing the max between this and the llvm type alignment.
+ // Which one is right?
+ loaded.SetAlignment(layout.Alignment.Bytes);
+ args.Add(loaded);
+ }
+ break;
+ case AsRealABIArgInfo:
+ {
+ Debug.Assert(argUnit.IsWrapped);
+ var (_, argValuePtr) = argUnit;
+ var layout = Target.GetLayout(paramType);
+ var llvmRealType = layout.Size.Bits switch
+ {
+ 32 => SCPPTypeRef.Float,
+ 64 => SCPPTypeRef.Double,
+ _ => throw new UnreachableException(),
+ };
+ var loaded = LLVM.Builder.BuildLoad(llvmRealType, argValuePtr);
loaded.SetAlignment(layout.Alignment.Bytes);
args.Add(loaded);
}
@@ -686,7 +810,7 @@ private void GenerateValueStore(LLVMUnit destUnit, HirType destType, LLVMUnit to
if (destType is HirStructType && toType is HirStructType && toUnit.IsWrapped)
{
var (_, rhs) = toUnit;
- var size = Target.GetAbiSize(toType);
+ var size = Target.GetSize(toType);
LLVM.CallIntrinsic_memcpy_i64(value, rhs, size.Bytes);
}
else
@@ -734,7 +858,7 @@ public override void VisitFieldStoreExpr(HirFieldStoreStmt node)
if (leftType is HirStructType && rightType is HirStructType && rhsUnit.IsWrapped)
{
var (_, rhs) = rhsUnit;
- var size = Target.GetAbiSize(node.Expr.Type);
+ var size = Target.GetSize(node.Expr.Type);
LLVM.CallIntrinsic_memcpy_i64(ptrToField, rhs, size.Bytes);
}
else
@@ -859,6 +983,10 @@ private LLVMUnit CreateFnUnit(HirFnValue fnValue)
}
// TODO: switch (cc) (C this is already the LLVM default if not set)
+ // Unless overriden in a function attribute:
+ // - If target platform is windows x64 => win64
+ // - If target platform is linux x64 => c
+ // Note: c == win64 if host platform is windows x64
llvmFnValue.SetFunctionCallConv(CallConv.C);
attrs.Apply(llvmFnValue.AddAttributeAtIndex);
@@ -866,341 +994,4 @@ private LLVMUnit CreateFnUnit(HirFnValue fnValue)
// Create an unwrapped unit out of direct fn values. This means direct calls to this fn value won't be loaded (in VisitCallExpr).
return LLVMUnit.Unwrapped(llvmPtrType, llvmFnValue);
}
-
- public LLVMTypeLowering GetLLVMLowering(HirType type)
- {
- return LLVMTarget.GetLLVMLowering(type);
- }
-}
-
-///
-/// LLVM compilation related logic that changes with target.
-///
-internal abstract class LLVMTarget
-{
- protected LLVMTarget(CompilationContext context)
- {
- Context = context;
- }
-
- protected CompilationContext Context { get; }
-
- protected LLVMCompilationContext LLVM => Context.LLVM;
-
- protected Target Target => Context.Target;
-
- public static LLVMTarget Create(CompilationContext context)
- {
- var target = context.Target;
-
- return target switch
- {
- x86_64WindowsMSVCTarget => new x86_64WindowsMSVCLLVMTarget(context),
- _ => throw new UnreachableException(),
- };
- }
-
- public bool IsReturnSRet(HirFnType fnType)
- {
- if (Target.GetAbiSize(fnType.ReturnType).IsZeroSized)
- return false;
-
- return IsReturnSRetCore(fnType);
- }
-
- public abstract bool IsReturnSRetCore(HirFnType fnType);
-
- // TODO: Support different calling conventions. Right now it's always Win64 cc.
- public SCPPTypeRef LowerFnReturnType(HirFnType fnType)
- {
- if (fnType.ReturnType.KnownTypeTag == KnownTypeTag.@void)
- return SCPPTypeRef.Void;
-
- Debug.Assert(!Target.GetAbiSize(fnType.ReturnType).IsZeroSized);
-
- return LowerFnReturnTypeCore(fnType);
- }
-
- protected abstract SCPPTypeRef LowerFnReturnTypeCore(HirFnType fnType);
-
- public void CompileFnReturnStmt(HirType returnType, LLVMUnit exprUnit, SCPPValueRef? sretOpt)
- {
- CompileFnReturnStmtCore(returnType, exprUnit, sretOpt);
- }
-
- protected abstract void CompileFnReturnStmtCore(HirType returnType, LLVMUnit exprUnit, SCPPValueRef? sretOpt);
-
- ///
- /// Lowers a type from Surab to LLVM.
- ///
- /// The Surab type to lower.
- /// The lowered LLVM type.
- public SCPPTypeRef LowerType(HirType type)
- {
- if (Context.TypeToLLVMType.TryGetValue(type, out var llvmType))
- {
- return llvmType;
- }
-
- llvmType = LowerTypeCore(type);
- Context.TypeToLLVMType[type] = llvmType;
- return llvmType;
- }
-
- private SCPPTypeRef LowerTypeCore(HirType type)
- {
- if (type.Kind is HirTypeKind.GenericParam)
- {
- throw new Exception("Unexpected generic param type still present when lowering type to LLVM.");
- }
-
- var tag = type.KnownTypeTag;
-
- if (tag.IsNumericInteger())
- {
- var size = Target.GetAbiSize(type);
- return SCPPTypeRef.CreateInt(size.Bits);
- }
-
- if (tag is KnownTypeTag.@void)
- return SCPPTypeRef.Void;
- if (tag is KnownTypeTag.@char or KnownTypeTag.@bool)
- return SCPPTypeRef.Int1;
-
- if (tag is KnownTypeTag.f32)
- return SCPPTypeRef.Float;
- if (tag is KnownTypeTag.f64)
- return SCPPTypeRef.Double;
-
- if (tag is KnownTypeTag.@string || type.Kind is HirTypeKind.Ptr)
- return SCPPTypeRef.CreatePointer(SCPPTypeRef.Void, 0);
-
- if (type is HirStructType structType)
- {
- var fields = structType.Fields;
- var structLayout = Target.GetStructLayout(structType);
- var elements = structLayout.Sections.Select(m =>
- {
- return m.Kind switch
- {
- StructLayoutSectionKind.Padding => SCPPTypeRef.CreateArray(SCPPTypeRef.Int1, m.Size.Bytes),
- StructLayoutSectionKind.Data => LowerType(fields[m.FieldIndex].Type),
- _ => throw new UnreachableException(),
- };
- }).ToArray();
- // TODO: Optionally mangled name instead.
- var llvmType = SCPPTypeRef.CreateNamedStruct(structType.FullName);
- llvmType.StructSetBody(elements, packed: false);
- return llvmType;
- }
-
- if (type is HirFnType fnType)
- {
- var (llvmType, _) = LowerType(fnType);
- return llvmType;
- }
-
- throw new UnreachableException();
- }
-
- ///
- /// Lowers an fn type from Surab to LLVM.
- ///
- /// The Surab fn type to lower.
- /// The lowered LLVM type (which is an opaque ptr in the case of fns here) and the actual LLVM type (the actual fn type).
- public (SCPPTypeRef llvmType, SCPPTypeRef llvmActualType) LowerType(HirFnType fnType)
- {
- var (sret, _) = GetFnInfoFromFnType(fnType);
- var returnType = LowerFnReturnType(fnType);
-
- // TODO: Better way?
- var llvmIndex = 0;
- var capacity = fnType.ParamTypes.Count + 1; // Max length of llvm params.
- var @params = new List(capacity);
-
- if (sret.HasValue)
- {
- @params.Add(SCPPTypeRef.CreatePointer(LowerType(fnType.ReturnType), 0));
- llvmIndex++;
- }
-
- foreach (var paramType in fnType.ParamTypes)
- {
- // Create fn param types
- // ---
-
- var lowering = GetLLVMLowering(paramType);
- var paramLLVMType = LowerType(paramType);
- switch (lowering.Kind)
- {
- case LLVMTypeLoweringKind.ByValue:
- {
- @params.Add(paramLLVMType);
- }
- break;
- case LLVMTypeLoweringKind.ByCopyRef:
- {
- @params.Add(SCPPTypeRef.CreatePointer(paramLLVMType, 0));
- }
- break;
- case LLVMTypeLoweringKind.AsInt:
- {
- var layout = Target.GetLayout(paramType);
- var llvmIntType = SCPPTypeRef.CreateInt(layout.Size.Bits);
- @params.Add(llvmIntType);
- }
- break;
- }
-
- llvmIndex++;
- }
-
- // SCPPTypeRef.CreateFunctionType
- var llvmFnType = SCPPTypeRef.CreateFunctionType(returnType, CollectionsMarshal.AsSpan(@params), false);
-
- // TODO: AddressSpace?
- return (SCPPTypeRef.CreatePointer(llvmFnType, 0), llvmFnType);
- }
-
- // TODO: Optimize calls to this method? This is being called multiple times in sequence when we compile an fn. We could cache the result.
- public LLVMFn GetFnInfoFromFnType(HirFnType fnType)
- {
- //Debug.Assert(fnType is HirFnType fnType);
- var returnType = LowerType(fnType.ReturnType);
-
- // TODO: Better way?
- var llvmIndex = 0;
- var attrs = new SCPPFnAttrsDef();
-
- var sret = IsReturnSRet(fnType);
- if (sret)
- {
- attrs.AddParam(llvmIndex, SCPPAttributeRef.CreateTypeAttr(SCPPAttributeKind.StructRet, returnType));
- llvmIndex++;
- }
-
- foreach (var paramType in fnType.ParamTypes)
- {
- // Add attrs
- // ---
-
- var lowering = GetLLVMLowering(paramType);
- //var llvmType = Target.LowerType(paramType);
- switch (lowering.Kind)
- {
- case LLVMTypeLoweringKind.ByValue:
- {
- }
- break;
- case LLVMTypeLoweringKind.ByCopyRef:
- {
- }
- break;
- case LLVMTypeLoweringKind.AsInt:
- {
- }
- break;
- }
-
- llvmIndex++;
- }
-
- return new LLVMFn(sret ? returnType : (SCPPTypeRef?)null, attrs);
- }
-
- public LLVMTypeLowering GetLLVMLowering(HirType type)
- {
- if (Target.GetAbiSize(type).IsZeroSized)
- return LLVMTypeLowering.ZeroSized;
-
- return GetLLVMLoweringCore(type);
- }
-
- protected abstract LLVMTypeLowering GetLLVMLoweringCore(HirType type);
-}
-
-internal abstract class x86_64WindowsLLVMTarget(CompilationContext context) : LLVMTarget(context)
-{
- protected new x86_64WindowsTarget Target => (x86_64WindowsTarget)base.Target;
-
- public override bool IsReturnSRetCore(HirFnType fnType)
- {
- return Target.Classify(fnType.ReturnType) == Classification.Memory;
- }
-
- protected override SCPPTypeRef LowerFnReturnTypeCore(HirFnType fnType)
- {
- var returnType = fnType.ReturnType;
- var classification = Target.Classify(returnType);
- return classification switch
- {
- Classification.Integer => returnType.IsScalar() ? LowerType(returnType) : SCPPTypeRef.CreateInt(Target.GetAbiSize(returnType).Bits),
- //win_i128 =>
- Classification.Memory => SCPPTypeRef.Void,
- Classification.SSE => LowerType(returnType),
- _ => throw new UnreachableException(),
- };
- }
-
- // Logic sync'd with LowerFnReturnTypeCore above.
- protected override void CompileFnReturnStmtCore(HirType returnType, LLVMUnit exprUnit, SCPPValueRef? sretOpt)
- {
- Debug.Assert(!Target.GetAbiSize(returnType).IsZeroSized);
-
- var classification = Target.Classify(returnType);
-
- var passthroughCase =
- (classification is Classification.Integer && returnType.IsScalar()) ||
- classification is Classification.SSE;
- if (passthroughCase)
- {
- var (_, value) = exprUnit.Unwrap(LLVM);
- LLVM.Builder.BuildRet(value);
- return;
- }
-
- if (classification is Classification.Memory)
- {
- // sret
- Debug.Assert(sretOpt.HasValue);
- var sretPtr = sretOpt.Value;
- Debug.Assert(sretPtr.TypeOf.Kind is SCPPTypeKind.pointer);
- var (_, exprValue) = exprUnit.Unwrap(LLVM);
- LLVM.Builder.BuildStore(exprValue, sretPtr);
- LLVM.Builder.BuildRetVoid();
- return;
- }
-
- if (classification is Classification.Integer)
- {
- // abi int
- Debug.Assert(exprUnit.IsWrapped);
- var (_, exprValuePtr) = exprUnit;
- var layout = Target.GetLayout(returnType);
- var llvmIntType = SCPPTypeRef.CreateInt(layout.Size.Bits);
- var loaded = LLVM.Builder.BuildLoad(llvmIntType, exprValuePtr);
- loaded.SetAlignment(layout.Alignment.Bytes);
- LLVM.Builder.BuildRet(loaded);
- return;
- }
- }
-
- protected override LLVMTypeLowering GetLLVMLoweringCore(HirType type)
- {
- Debug.Assert(!Target.GetAbiSize(type).IsZeroSized);
-
- return Target.Classify(type) switch
- {
- Classification.Integer => type.IsScalar() ? LLVMTypeLowering.ByValue : LLVMTypeLowering.AsInt,
- //win_i128 =>
- Classification.Memory => LLVMTypeLowering.ByCopyRef,
- Classification.SSE => LLVMTypeLowering.ByValue,
- _ => throw new UnreachableException(),
- };
- }
-}
-
-internal sealed class x86_64WindowsMSVCLLVMTarget(CompilationContext context) : x86_64WindowsLLVMTarget(context)
-{
- private new x86_64WindowsMSVCTarget Target => (x86_64WindowsMSVCTarget)Context.Target;
}
diff --git a/src/compilation/LLVM/LLVMTarget.cs b/src/compilation/LLVM/LLVMTarget.cs
new file mode 100644
index 0000000..7c33ca8
--- /dev/null
+++ b/src/compilation/LLVM/LLVMTarget.cs
@@ -0,0 +1,613 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+using Surab.Analysis;
+using Surab.Compilation.LLVM;
+
+namespace Surab.Compilation;
+
+///
+/// LLVM compilation related logic that changes with target.
+///
+internal abstract class LLVMTarget
+{
+ protected LLVMTarget(CompilationContext context)
+ {
+ Context = context;
+ }
+
+ protected CompilationContext Context { get; }
+
+ protected LLVMCompilationContext LLVM => Context.LLVM;
+
+ protected Target Target => Context.Target;
+
+ public static LLVMTarget Create(CompilationContext context)
+ {
+ var target = context.Target;
+
+ return target switch
+ {
+ x86_64WindowsMSVCTarget => new x86_64WindowsMSVCLLVMTarget(context),
+ x86_64LinuxGNUTarget => new x86_64LinuxGNULLVMTarget(context),
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ // TODO: Support overriding calling convention on the fn. Right now it's always cc c.
+ public SCPPTypeRef LowerFnReturnType(HirFnType fnType)
+ {
+ var returnType = fnType.ReturnType;
+ var info = ComputeInfo(returnType, isReturnType: true);
+ return info switch
+ {
+ DirectABIArgInfo => LowerType(returnType),
+ IgnoreABIArgInfo or IndirectABIArgInfo => SCPPTypeRef.Void,
+ AsIntegerABIArgInfo => SCPPTypeRef.CreateInt(Target.GetSize(returnType).Bits),
+ AsRealABIArgInfo => Target.GetSize(returnType).Bits switch
+ {
+ 32 => SCPPTypeRef.Float,
+ 64 => SCPPTypeRef.Double,
+ _ => throw new UnreachableException(),
+ },
+ _ => throw new UnreachableException(),
+ };
+ }
+
+ ///
+ /// Lowers a type from Surab to LLVM.
+ ///
+ /// The Surab type to lower.
+ /// The lowered LLVM type.
+ public SCPPTypeRef LowerType(HirType type)
+ {
+ if (Context.TypeToLLVMType.TryGetValue(type, out var llvmType))
+ {
+ return llvmType;
+ }
+
+ llvmType = LowerTypeCore(type);
+ Context.TypeToLLVMType[type] = llvmType;
+ return llvmType;
+ }
+
+ private SCPPTypeRef LowerTypeCore(HirType type)
+ {
+ if (type.Kind is HirTypeKind.GenericParam)
+ {
+ throw new Exception("Unexpected generic param type still present when lowering type to LLVM.");
+ }
+
+ var tag = type.KnownTypeTag;
+
+ if (tag.IsNumericInteger())
+ {
+ var size = Target.GetSize(type);
+ return SCPPTypeRef.CreateInt(size.Bits);
+ }
+
+ if (tag is KnownTypeTag.@void)
+ return SCPPTypeRef.Void;
+ if (tag is KnownTypeTag.@char or KnownTypeTag.@bool)
+ return SCPPTypeRef.Int1;
+
+ if (tag is KnownTypeTag.f32)
+ return SCPPTypeRef.Float;
+ if (tag is KnownTypeTag.f64)
+ return SCPPTypeRef.Double;
+
+ if (tag is KnownTypeTag.@string || type.Kind is HirTypeKind.Ptr)
+ return SCPPTypeRef.CreatePointer(SCPPTypeRef.Void, 0);
+
+ if (type is HirStructType structType)
+ {
+ var fields = structType.Fields;
+ var structLayout = Target.GetStructLayout(structType);
+ var elements = structLayout.Sections.Select(m =>
+ {
+ return m.Kind switch
+ {
+ StructLayoutSectionKind.Padding => SCPPTypeRef.CreateArray(SCPPTypeRef.Int1, m.Size.Bytes),
+ StructLayoutSectionKind.Data => LowerType(fields[m.FieldIndex].Type),
+ _ => throw new UnreachableException(),
+ };
+ }).ToArray();
+ // TODO: Optionally mangled name instead.
+ var llvmType = SCPPTypeRef.CreateNamedStruct(structType.FullName);
+ llvmType.StructSetBody(elements, packed: false);
+ return llvmType;
+ }
+
+ if (type is HirFnType fnType)
+ {
+ var (llvmType, _) = LowerType(fnType);
+ return llvmType;
+ }
+
+ throw new UnreachableException();
+ }
+
+ ///
+ /// Lowers an fn type from Surab to LLVM.
+ ///
+ /// The Surab fn type to lower.
+ /// The lowered LLVM type (which is an opaque ptr in the case of fns here) and the actual LLVM type (the actual fn type).
+ public (SCPPTypeRef llvmType, SCPPTypeRef llvmActualType) LowerType(HirFnType fnType)
+ {
+ var (sret, _) = GetFnInfoFromFnType(fnType);
+ var returnType = LowerFnReturnType(fnType);
+
+ // TODO: Better way?
+ var llvmIndex = 0;
+ var capacity = fnType.ParamTypes.Count + 1; // Max length of llvm params.
+ var @params = new List(capacity);
+
+ if (sret.HasValue)
+ {
+ @params.Add(SCPPTypeRef.CreatePointer(LowerType(fnType.ReturnType), 0));
+ llvmIndex++;
+ }
+
+ foreach (var paramType in fnType.ParamTypes)
+ {
+ // Create fn param types
+ // ---
+
+ var info = ComputeInfo(paramType, isReturnType: false);
+ var paramLLVMType = LowerType(paramType);
+ switch (info)
+ {
+ case DirectABIArgInfo:
+ {
+ @params.Add(paramLLVMType);
+ }
+ break;
+ case IndirectABIArgInfo:
+ {
+ @params.Add(SCPPTypeRef.CreatePointer(paramLLVMType, 0));
+ }
+ break;
+ case AsIntegerABIArgInfo:
+ {
+ var layout = Target.GetLayout(paramType);
+ var llvmIntType = SCPPTypeRef.CreateInt(layout.Size.Bits);
+ @params.Add(llvmIntType);
+ }
+ break;
+ case AsRealABIArgInfo:
+ {
+ var layout = Target.GetLayout(paramType);
+ var llvmRealType = layout.Size.Bits switch
+ {
+ 32 => SCPPTypeRef.Float,
+ 64 => SCPPTypeRef.Double,
+ _ => throw new UnreachableException(),
+ };
+ @params.Add(llvmRealType);
+ }
+ break;
+ }
+
+ llvmIndex++;
+ }
+
+ var llvmFnType = SCPPTypeRef.CreateFunctionType(returnType, CollectionsMarshal.AsSpan(@params), false);
+
+ return (SCPPTypeRef.CreatePointer(llvmFnType, 0), llvmFnType);
+ }
+
+ // TODO: Optimize calls to this method? This is being called multiple times in sequence when we compile an fn. We could cache the result.
+ public LLVMFn GetFnInfoFromFnType(HirFnType fnType)
+ {
+ //Debug.Assert(fnType is HirFnType fnType);
+ var returnType = LowerType(fnType.ReturnType);
+
+ // TODO: Better way?
+ var llvmIndex = 0;
+ var attrs = new SCPPFnAttrsDef();
+
+ var sret = IsReturnSRet(fnType);
+ if (sret)
+ {
+ attrs.AddParam(llvmIndex, SCPPAttributeRef.CreateTypeAttr(SCPPAttributeKind.StructRet, returnType));
+ llvmIndex++;
+ }
+
+ foreach (var paramType in fnType.ParamTypes)
+ {
+ // Add attrs
+ // ---
+
+ var info = ComputeInfo(paramType, isReturnType: false);
+ //var llvmType = Target.LowerType(paramType);
+ switch (info)
+ {
+ case DirectABIArgInfo:
+ {
+ }
+ break;
+ case IndirectABIArgInfo:
+ {
+ }
+ break;
+ case AsIntegerABIArgInfo:
+ {
+ }
+ break;
+ case AsRealABIArgInfo:
+ {
+ }
+ break;
+ }
+
+ llvmIndex++;
+ }
+
+ return new LLVMFn(sret ? returnType : (SCPPTypeRef?)null, attrs);
+ }
+
+ private bool IsReturnSRet(HirFnType fnType)
+ {
+ return ComputeInfo(fnType.ReturnType, isReturnType: true) is IndirectABIArgInfo;
+ }
+
+ ///
+ /// Computes the ABI arg info for the type.
+ ///
+ /// In general, we only need to compute enough info to know how to correctly compile to LLVM IR.
+ /// We don't need to fully implement the ABI's specification because a big part of that is
+ /// handled by LLVM itself.
+ ///
+ public abstract ABIArgInfo ComputeInfo(HirType type, bool isReturnType);
+}
+
+// https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention
+internal abstract class x86_64WindowsLLVMTarget(CompilationContext context) : LLVMTarget(context)
+{
+ protected new x86_64WindowsTarget Target => (x86_64WindowsTarget)base.Target;
+
+ public override ABIArgInfo ComputeInfo(HirType type, bool isReturnType)
+ {
+ var size = Target.GetLayout(type).Size;
+ if (size.IsZeroSized)
+ {
+ return new IgnoreABIArgInfo();
+ }
+
+ if (!(size.Bytes is 1 or 2 or 4 or 8))
+ {
+ return new IndirectABIArgInfo();
+ }
+
+ return type.GetRuntimeKind() switch
+ {
+ RuntimeTypeKind.Bool or
+ RuntimeTypeKind.Char or
+ RuntimeTypeKind.Ptr
+ => new DirectABIArgInfo(),
+
+ RuntimeTypeKind.Numeric
+ => new DirectABIArgInfo(),
+
+ RuntimeTypeKind.Struct
+ => new AsIntegerABIArgInfo(),
+
+ _ => throw new UnreachableException(),
+ };
+ }
+}
+
+internal sealed class x86_64WindowsMSVCLLVMTarget(CompilationContext context) : x86_64WindowsLLVMTarget(context)
+{
+ private new x86_64WindowsMSVCTarget Target => (x86_64WindowsMSVCTarget)Context.Target;
+}
+
+// https://gitlab.com/x86-psABIs/x86-64-ABI
+internal abstract class x86_64LinuxLLVMTarget(CompilationContext context) : LLVMTarget(context)
+{
+ // AMD64 ABI 1.0 – March 12, 2025
+ //
+ // We don't exactly need these classes for emit since we emit LLVM, but we still need to follow
+ // the spec and classify everything in order to compute what we need (e.g. sret).
+ //
+ // Note: Right now we don't support many of the types that would require some of the classes
+ // such as SSEUP. For that reason, the classify algorithm is simplified and some shortcuts
+ // are taken.
+
+ internal enum Class
+ {
+ ///
+ /// This class is used as initializer in the algorithms. It will be used for padding and empty structures and unions.
+ ///
+ NO_CLASS,
+
+ ///
+ /// This class consists of integral types that fit into one of the general purpose registers.
+ ///
+ INTEGER,
+ ///
+ /// This class consists of types that fit into a vector register.
+ ///
+ SSE,
+ ///
+ /// This class consists of types that fit into a vector register and can be passed and returned in the upper bytes of it.
+ ///
+ SSEUP,
+ ///
+ /// This class consists of types that will be returned via the x87 FPU.
+ ///
+ X87,
+ ///
+ /// This class consists of types that will be returned via the x87 FPU and can be passed and returned in the upper bytes of it.
+ ///
+ X87UP,
+ ///
+ /// This class consists of types that will be returned via the x87 FPU.
+ ///
+ COMPLEX_X87,
+ ///
+ /// This class consists of types that will be passed and returned in memory via the stack.
+ ///
+ MEMORY,
+ }
+
+ // TODO: Optimize
+ ///
+ /// Useful wrapper around 8 classes representing all possible slots as defined by the ABI.
+ /// Each class corresponds to an eightbyte as defined in 3.2.3.
+ ///
+ internal class Classes
+ {
+ public readonly Class[] _arr = new Class[8];
+
+ public Classes(params IEnumerable classes)
+ {
+ var i = 0;
+ foreach (var c in classes)
+ {
+ if (i > 7)
+ {
+ throw new IndexOutOfRangeException("Classes have exactly 8 items.");
+ }
+ this[i] = c;
+ i++;
+ }
+ if (i <= 7)
+ {
+ // Fill rest with NO_CLASS.
+ for (var j = i; j < 8; j++)
+ {
+ this[j] = Class.NO_CLASS;
+ }
+ }
+ }
+
+ public int Length => _arr.Length;
+
+ public IEnumerable Enumerate()
+ {
+ return _arr;
+ }
+
+ public Class this[int i]
+ {
+ get
+ {
+ if (i > 8)
+ {
+ throw new IndexOutOfRangeException("Classes have exactly 8 slots.");
+ }
+ return _arr[i];
+ }
+ set
+ {
+ if (i > 8)
+ {
+ throw new IndexOutOfRangeException("Classes have exactly 8 slots.");
+ }
+ _arr[i] = value;
+ }
+ }
+
+ // TODO (self-host): Wrap BitSize into ClassOffset with additional static verification?
+
+ //public Class GetAtOffset(BitSize offset)
+ //{
+ // var i = GetSlotIndexAtOffset(offset);
+ // return _arr[i];
+ //}
+
+ //public void SetAtOffset(BitSize offset, Class c)
+ //{
+ // var i = GetSlotIndexAtOffset(offset);
+ // _arr[i] = c;
+ //}
+
+ //private int GetSlotIndexAtOffset(BitSize offset)
+ //{
+ // Debug.Assert(offset.Bytes >= 0 && offset.Bytes <= 8 * 8); // eightbytes * 8 slots
+ // return offset.Bytes / 8;
+ //}
+ }
+
+ private static readonly Classes MemoryClasses = new(Class.MEMORY);
+
+ protected new x86_64LinuxTarget Target => (x86_64LinuxTarget)base.Target;
+
+ internal Classes Classify(HirType type)
+ {
+ var layout = Target.GetLayout(type);
+ var runtimeKind = type.GetRuntimeKind();
+
+ // "Arguments of types (signed and unsigned) _Bool, char, short, int, long, long long, and pointers are in the INTEGER class."
+ if (runtimeKind is RuntimeTypeKind.Bool or RuntimeTypeKind.Char or RuntimeTypeKind.Ptr ||
+ type.KnownTypeTag.IsNumericInteger())
+ {
+ return new(Class.INTEGER);
+ }
+
+ // "Arguments of types _Float16, float, double, _Decimal32, _Decimal64 and __m64 are in class SSE."
+ if (type.KnownTypeTag.IsNumericFloat())
+ {
+ return new(Class.SSE);
+ }
+
+ // TODO: "Arguments of types __float128, _Decimal128 and __m128 are split into two halves. The least significant ones belong to class SSE, the most significant one to class SSEUP."
+ // TODO: "Arguments of type __m256 are split into four eightbyte chunks. The least significant one belongs to class SSE and all the others to class SSEUP."
+ // TODO: "Arguments of type __m512 are split into eight eightbyte chunks. The least significant one belongs to class SSE and all the others to class SSEUP."
+
+ // TODO: ? "The 64-bit mantissa of arguments of type long double belongs to class X87, the 16 - bit exponent plus 6 bytes of padding belongs to class X87UP."
+
+ // TODO: "Arguments of type __int128 offer the same operations as INTEGERs, yet they do not fit into one general purpose register but require two registers. [...]"
+
+ // TODO: If arbitrary bit sizes become supported:
+ // - "Arguments of type _BitInt(N) with N <= 64 are in the INTEGER class."
+ // - "Arguments of type _BitInt(N) with N > 64 are classified as if they were implemented as struct of 64-bit integer fields."
+
+ // TODO: "Arguments of complex T where T is one of the types _Float16, float, double or __float128 are treated as if they are implemented as: [...]"
+ // TODO: "A variable of type complex long double is classified as type COMPLEX_X87."
+
+ // "The classification of aggregate (structures and arrays) and union types works as follows:"
+
+ // "1. If the size of an object is larger than eight eightbytes, or it contains unaligned fields, it has class MEMORY."
+ if (layout.Size > (8 * 8).AsBytes()) // TODO: or unaligned fields
+ {
+ return MemoryClasses;
+ }
+
+ var aggregateClasses = new Classes();
+
+ if (runtimeKind is RuntimeTypeKind.Struct)
+ {
+ ClassifyStruct(ref aggregateClasses,(HirStructType)type, 0.AsBytes());
+ }
+
+ // TODO: Arrays
+
+ // "5. Then a post merger cleanup is done: [...]"
+
+ for (var i = 0; i < aggregateClasses.Length; i++)
+ {
+ var c = aggregateClasses[i];
+
+ // "(a) If one of the classes is MEMORY, the whole argument is passed in memory."
+ if (c is Class.MEMORY)
+ return MemoryClasses;
+ // "(b) If X87UP is not preceded by X87, the whole argument is passed in memory."
+ if (c is Class.X87UP && (i == 0 || aggregateClasses[i - 1] is not Class.X87))
+ return MemoryClasses;
+ }
+
+ // "(c) If the size of the aggregate exceeds two eightbytes and the first eightbyte
+ // isn’t SSE or any other eightbyte isn’t SSEUP, the whole argument is passed in memory."
+ if (layout.Size.Bytes > (2 * 8) &&
+ (aggregateClasses[0] is not Class.SSE || !aggregateClasses.Enumerate().Skip(1).All(x => x is Class.SSEUP or Class.NO_CLASS)))
+ {
+ return MemoryClasses;
+ }
+
+ // "(d) If SSEUP is not preceded by SSE or SSEUP, it is converted to SSE."
+ for (var i = 0; i < aggregateClasses.Length; i++)
+ {
+ if (aggregateClasses[i] is Class.SSEUP && aggregateClasses[i - 1] is not Class.SSE and not Class.SSEUP)
+ {
+ aggregateClasses[i] = Class.SSE;
+ }
+ }
+
+ return aggregateClasses;
+ }
+
+ internal BitSize ClassifyStruct(ref Classes classes, HirStructType type, BitSize offset)
+ {
+ var structLayout = Target.GetStructLayout(type);
+ var sections = structLayout.Sections;
+
+ // "3. If the size of the aggregate exceeds a single eightbyte, each is classified
+ // separately. Each eightbyte gets initialized to class NO_CLASS."
+ // "4. Each field of an object is classified recursively so that always two fields are considered.
+
+ var currentOffset = offset;
+ foreach (var section in sections)
+ {
+ if (section.Kind is StructLayoutSectionKind.Data)
+ {
+ var field = type.Fields[section.FieldIndex];
+ if (field.Type.GetRuntimeKind() is RuntimeTypeKind.Struct)
+ {
+ currentOffset = ClassifyStruct(ref classes, (HirStructType)field.Type, currentOffset);
+ }
+ else
+ {
+ var fieldClasses = Classify(field.Type);
+ for (int slotIndex = currentOffset.Bytes / 8, j = 0; j < fieldClasses.Length; slotIndex++, j++)
+ {
+ Debug.Assert(slotIndex < 8 && slotIndex + j < 8); // eightbytes * 8 slots
+
+ if (fieldClasses[j] is Class.NO_CLASS)
+ break;
+
+ classes[slotIndex] = MergeInSlot(classes[slotIndex], fieldClasses[j]);
+ }
+ }
+ }
+
+ currentOffset += section.Size;
+ }
+
+ return currentOffset;
+ }
+
+ ///
+ /// Merges two classes occupying the same eightbyte slot.
+ ///
+ private Class MergeInSlot(Class a, Class b)
+ {
+ // "4. Each field of an object is classified recursively so that always two fields 18 are considered.
+ // The resulting class is calculated according to the classes of the fields in the eightbyte: [...]"
+ if (a == b) return a;
+ if (a == Class.NO_CLASS) return b;
+ if (b == Class.NO_CLASS) return a;
+ if (a == Class.MEMORY || b == Class.MEMORY) return Class.MEMORY;
+ if (a == Class.INTEGER || b == Class.INTEGER) return Class.INTEGER;
+ if (a == Class.X87 || a == Class.X87UP || a == Class.COMPLEX_X87 || b == Class.X87 || b == Class.X87UP || b == Class.COMPLEX_X87)
+ return Class.MEMORY;
+ return Class.SSE;
+ }
+
+ public override ABIArgInfo ComputeInfo(HirType type, bool isReturnType)
+ {
+ var size = Target.GetLayout(type).Size;
+ if (size.IsZeroSized)
+ {
+ return new IgnoreABIArgInfo();
+ }
+
+ var classes = Classify(type);
+
+ if (classes[0] is Class.MEMORY)
+ {
+ Debug.Assert(classes[1] is Class.NO_CLASS);
+ return new IndirectABIArgInfo();
+ }
+
+ if (classes[0] is Class.SSE && classes[1] is Class.NO_CLASS &&
+ type.GetRuntimeKind() is RuntimeTypeKind.Struct)
+ {
+ return new AsRealABIArgInfo();
+ }
+
+ if (classes[0] is Class.INTEGER && classes[1] is Class.NO_CLASS &&
+ type.GetRuntimeKind() is RuntimeTypeKind.Struct)
+ {
+ return new AsIntegerABIArgInfo();
+ }
+
+ return new DirectABIArgInfo();
+ }
+}
+
+internal sealed class x86_64LinuxGNULLVMTarget(CompilationContext context) : x86_64LinuxLLVMTarget(context)
+{
+ private new x86_64LinuxGNUTarget Target => (x86_64LinuxGNUTarget)Context.Target;
+}
diff --git a/src/compilation/LLVM/LLVMTypeLowering.cs b/src/compilation/LLVM/LLVMTypeLowering.cs
deleted file mode 100644
index c28b1d8..0000000
--- a/src/compilation/LLVM/LLVMTypeLowering.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-namespace Surab.Compilation.LLVM;
-
-///
-/// The kind of an .
-///
-internal enum LLVMTypeLoweringKind
-{
- None,
-
- ///
- /// Zero sized.
- ///
- ZeroSized,
- ///
- /// By value. The arg should be passed as is by its value without any conversion.
- ///
- ByValue,
- ///
- /// By copy ref. The arg is originally passed as a copy but the abi is telling us to pass it by ref.
- /// Mutations shouldn't be observed after the function we're going to call returns to preserve by copy semantics.
- ///
- ByCopyRef,
- ///
- /// As an integer. The arg should be passed as a bitcasted integer of the same bit size.
- ///
- AsInt,
-}
-
-///
-/// Represents a certain conversion imposed by an ABI by which an arg should be passed.
-///
-internal readonly struct LLVMTypeLowering
-{
- public required LLVMTypeLoweringKind Kind { get; init; }
-
- public static readonly LLVMTypeLowering ZeroSized = new() { Kind = LLVMTypeLoweringKind.ZeroSized };
-
- public static readonly LLVMTypeLowering ByValue = new() { Kind = LLVMTypeLoweringKind.ByValue };
-
- public static readonly LLVMTypeLowering ByCopyRef = new() { Kind = LLVMTypeLoweringKind.ByCopyRef };
-
- public static readonly LLVMTypeLowering AsInt = new() { Kind = LLVMTypeLoweringKind.AsInt };
-}
diff --git a/src/compilation/LLVM/LinkerRunner.cs b/src/compilation/LLVM/LinkerRunner.cs
index 5de4d37..6d6e52f 100644
--- a/src/compilation/LLVM/LinkerRunner.cs
+++ b/src/compilation/LLVM/LinkerRunner.cs
@@ -50,7 +50,7 @@ [.. objectFiles.Select(objectFile => Path.Combine(wd, objectFile)), surabRuntime
outputFilePath,
out var error))
{
- throw new Exception("Calling lld failed: " + error);
+ throw new Exception("Calling clang failed: " + error);
}
var exeFileInfo = new FileInfo(outputFilePath);
diff --git a/src/compilation/System/ABIArgInfo.cs b/src/compilation/System/ABIArgInfo.cs
new file mode 100644
index 0000000..161db0e
--- /dev/null
+++ b/src/compilation/System/ABIArgInfo.cs
@@ -0,0 +1,45 @@
+namespace Surab.Compilation;
+
+///
+/// Represents ABI info for a return type or arg type on how to lower it to llvm
+/// while respecting the target ABI.
+///
+public abstract class ABIArgInfo
+{
+}
+
+///
+/// Zero sized.
+///
+public sealed class IgnoreABIArgInfo : ABIArgInfo
+{
+}
+
+///
+/// Direct, by value. The arg should be passed as is by its value without any conversion.
+///
+public sealed class DirectABIArgInfo : ABIArgInfo
+{
+}
+
+///
+/// Indirect, by copy ref. The arg is originally passed as a copy but the abi is telling us to pass it by ref.
+/// Mutations shouldn't be observed after the function we're going to call returns to preserve by copy semantics.
+///
+public sealed class IndirectABIArgInfo : ABIArgInfo
+{
+}
+
+///
+/// As an integer. The arg should be passed as a bitcasted integer of the same bit size.
+///
+public sealed class AsIntegerABIArgInfo : ABIArgInfo
+{
+}
+
+///
+/// As a float. The arg should be passed as a float (usually a transparent struct of a float or double).
+///
+public sealed class AsRealABIArgInfo : ABIArgInfo
+{
+}
diff --git a/src/compilation/System/Abi.cs b/src/compilation/System/Abi.cs
deleted file mode 100644
index cc688ae..0000000
--- a/src/compilation/System/Abi.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Diagnostics;
-
-namespace Surab.Compilation;
-
-internal enum Classification
-{
- // x86_64
- Integer,
- // x86_64
- SSE,
- // x86_64
- SSEUP,
- // x86_64
- Memory,
-}
-
-internal static class Abi
-{
- public static class x86_64
- {
- // https://learn.microsoft.com/en-us/cpp/build/x64-software-conventions
- public static Classification ClassifyTypeForWindows(HirType type, Target target)
- {
- var size = target.GetLayout(type).Size;
- Debug.Assert(!size.IsZeroSized, "Classify shouldn't be called with zero sized types.");
-
- return type.GetRuntimeKind() switch
- {
- RuntimeTypeKind.Bool or
- RuntimeTypeKind.Char or
- RuntimeTypeKind.Ptr or
- RuntimeTypeKind.Struct
- => size.Bytes switch
- {
- 1 or 2 or 4 or 8 => Classification.Integer,
- _ => Classification.Memory,
- },
-
- RuntimeTypeKind.Numeric
- => Classification.SSE,
-
- _ => throw new UnreachableException(),
- };
- }
- }
-}
diff --git a/src/compilation/System/Layout.cs b/src/compilation/System/Layout.cs
index aa3180a..8c88548 100644
--- a/src/compilation/System/Layout.cs
+++ b/src/compilation/System/Layout.cs
@@ -43,7 +43,7 @@ private StructLayoutSection(StructLayoutSectionKind kind)
public required BitSize Size { get; init; }
///
- /// Gets the index of the field in the struct this data section is associated with.
+ /// Gets the index of the in the struct this data section is associated with.
/// This is -1 when Kind is Padding.
///
public required int FieldIndex { get; init; }
diff --git a/src/compilation/System/Target.cs b/src/compilation/System/Target.cs
index 0f049b1..a9563b7 100644
--- a/src/compilation/System/Target.cs
+++ b/src/compilation/System/Target.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+
using Surab.Analysis;
namespace Surab.Compilation;
@@ -11,11 +12,13 @@ public enum TargetArch
public enum TargetOS
{
Windows,
+ Linux,
}
public enum TargetEnv
{
MSVC,
+ GNU,
}
public enum IntegerSign
@@ -34,7 +37,6 @@ public abstract class Target(TargetArch arch, TargetOS os, TargetEnv env)
public BitSize PtrWidth { get; protected set; }
- // Should this be in Abi?
public Layout GetLayout(HirType type)
{
Debug.Assert(type.Kind is not HirTypeKind.GenericParam);
@@ -65,7 +67,6 @@ public Layout GetLayout(HirType type)
throw new UnreachableException();
}
- // Should this be in Abi?
public StructLayout GetStructLayout(HirStructType structType)
{
ArgumentNullException.ThrowIfNull(structType);
@@ -111,9 +112,9 @@ void AddPaddingIfNecessary(BitSize alignment)
}
}
- public BitSize GetAbiSize(HirType type) => GetLayout(type).Size;
+ public BitSize GetSize(HirType type) => GetLayout(type).Size;
- public BitSize GetAbiAlignment(HirType type) => GetLayout(type).Alignment;
+ public BitSize GetAlignment(HirType type) => GetLayout(type).Alignment;
public IntegerSign GetIntegerSign(HirType type)
{
@@ -128,23 +129,39 @@ public IntegerSign GetIntegerSign(HirType type)
}
}
-internal abstract class x86_64WindowsTarget : Target
+internal abstract class x86_64Target : Target
{
- protected x86_64WindowsTarget(TargetEnv env) : base(TargetArch.x86_64, TargetOS.Windows, env)
+ protected x86_64Target(TargetOS os, TargetEnv env) : base(TargetArch.x86_64, os, env)
{
+ PtrWidth = 64.AsBits();
}
+}
- public Classification Classify(HirType type)
+internal abstract class x86_64WindowsTarget : x86_64Target
+{
+ protected x86_64WindowsTarget(TargetEnv env) : base(TargetOS.Windows, env)
{
- return Abi.x86_64.ClassifyTypeForWindows(type, this);
+ PtrWidth = 64.AsBits();
}
}
internal sealed class x86_64WindowsMSVCTarget : x86_64WindowsTarget
{
- public x86_64WindowsMSVCTarget() :
- base(TargetEnv.MSVC)
+ public x86_64WindowsMSVCTarget() : base(TargetEnv.MSVC)
+ {
+ }
+}
+
+internal abstract class x86_64LinuxTarget : x86_64Target
+{
+ protected x86_64LinuxTarget(TargetEnv env) : base(TargetOS.Linux, env)
+ {
+ }
+}
+
+internal sealed class x86_64LinuxGNUTarget : x86_64LinuxTarget
+{
+ public x86_64LinuxGNUTarget() : base(TargetEnv.GNU)
{
- PtrWidth = 64.AsBits();
}
}
diff --git a/src/runner/Program.cs b/src/runner/Program.cs
index af7abef..88f46dc 100644
--- a/src/runner/Program.cs
+++ b/src/runner/Program.cs
@@ -11,7 +11,9 @@
StartInfo =
{
FileName = "cargo",
+ //FileName = "/home/mrahhal/.cargo/bin/cargo", // This is the only way this is working when debugging WSL in VS. Not sure why.
Arguments = "build",
+ WorkingDirectory = runtimeSrcFolderPath,
},
};
diff --git a/src/runner/Properties/launchSettings.json b/src/runner/Properties/launchSettings.json
new file mode 100644
index 0000000..611941d
--- /dev/null
+++ b/src/runner/Properties/launchSettings.json
@@ -0,0 +1,11 @@
+{
+ "profiles": {
+ "runner": {
+ "commandName": "Project"
+ },
+ "WSL": {
+ "commandName": "WSL2",
+ "distributionName": "" // Needs a default WSL distro to be linux with the prerequisites installed.
+ }
+ }
+}