Blazar
View as PDFOctober, 2025
Overview
    Blazar is a programming language implemented in C#. Its front end is based on a fork of Roslyn, extended with additional keywords and custom language semantics. The compiler middle end takes Roslyn’s bound syntax nodes and lowers them to a custom stack-based intermediate language (BIL), which is then translated into LLVM IR using the LLVMSharp C# wrapper.
The language and compiler support structs, heap-allocated objects, arrays, object fields, instance methods, virtual functions with method overriding, conditional branching, loops, native C interoperability, console I/O, file streams, exception handling with try/catch/finally, and additional core language features.
The project system is built on MSBuild and supports project references. A custom Visual Studio extension provides integrated build, debugging, and editor support, including syntax highlighting, code formatting, code completion, and syntactic analysis. The compiler also supports Linux, enabling cross-platform development and execution.
Language
Blazar source code closely resembles C#, as its front end is based on a fork of the Roslyn compiler. This means it inherits many of C#’s familiar language constructs, such as classes, interfaces, inheritance, and virtual methods. The compiler pipeline begins by parsing the source code into a syntax tree, which is then processed by the binder to perform semantic analysis. After resolving symbols and types, a graph of bound nodes is produced, which serve as the input for Blazar’s middle end compilation stages, where the code is lowered into an intermediate representation.
Source Code
The following Blazar source code calculates the sum of squares from 1 to 9 and outputs the intermediate and final results to the console.
public class Program
{
    public static unsafe int Main()
    {
        var sum = 0;
 
        for (var i = 1; i < 10; i++)
        {
            sum = sum + i * i;
            Console.Write("i = ");
            Console.Write(i);
            Console.Write("  sum = ");
            Console.WriteLine(sum);
        }
 
        Console.Write("Final sum of squares: ");
        Console.WriteLine(sum);
 
        return 0;
    }
}
        
    Intermediate Language
Blazar Intermediate Language (BIL) is a stack-based intermediate representation used by the compiler for optimization and code generation. Each function is divided into blocks of linear sequences of instructions that must begin at the first instruction in the block and end with a control flow terminator such as a branch or return. This structure simplifies optimization and validation passes by providing well-defined control flow. The choice to use an intermediate language also enables future support for additional front ends while reusing the same optimization and code generation infrastructure.
    In the following example, the Main function from the sum of squares program is shown after being lowered into Blazar’s intermediate representation.
    The variable LoweringTemp0 is a temporary variable introduced during the lowering process to hold an intermediate computation.
    However, since it is written to at instruction 0019 but never subsequently read, it represents dead storage that could be safely removed by a dead variable elimination optimization pass.
public static int [Blazar.Test]Program.Main()
{
   int sum
   int i
   int LoweringTemp0
 
   0000: LoadConstantInt32 0
   0001: StoreVariable sum
   0002: LoadConstantInt32 1
   0003: StoreVariable i
   0004: BranchAlways 0005
 
   0005: LoadVariable i
   0006: LoadConstantInt32 10
   0007: CompareLessThan
   0008: BranchConditional 0009, 001F
 
   0009: LoadVariable sum
   000A: LoadVariable i
   000B: LoadVariable i
   000C: Multiply
   000D: Add
   000E: StoreVariable sum
   000F: LoadConstantString "i = "
   0010: Call void [Blazar.System.Console]System.Console.Write(constr)
   0011: LoadVariable i
   0012: Call void [Blazar.System.Console]System.Console.Write(int)
   0013: LoadConstantString "  sum = "
   0014: Call void [Blazar.System.Console]System.Console.Write(constr)
   0015: LoadVariable sum
   0016: Call void [Blazar.System.Console]System.Console.WriteLine(int)
   0017: BranchAlways 0018
 
   0018: LoadVariable i
   0019: StoreVariable LoweringTemp0
   001A: LoadVariable i
   001B: LoadConstantInt32 1
   001C: Add
   001D: StoreVariable i
   001E: BranchAlways 0005
 
   001F: LoadConstantString "Final sum of squares: "
   0020: Call void [Blazar.System.Console]System.Console.Write(constr)
   0021: LoadVariable sum
   0022: Call void [Blazar.System.Console]System.Console.WriteLine(int)
   0023: LoadConstantInt32 0
   0024: ReturnValue
}
        
    LLVM
The back end uses LLVMSharp to translate BIL into LLVM IR, enabling Blazar to target multiple architectures. Blazar can also emit debug information alongside the generated IR, allowing inspection of variables during execution. Integration with LLVM further provides access to its advanced optimization pipeline and facilitates the generation of portable binaries across different platforms.
In the following example, the Main function from the sum of squares example is lowered into LLVM IR, then optimized and linked with core runtime assemblies to produce the final executable.
define i32 @"[Blazar.Test]Program.Main.2841951496"() {
entry:
  %sum = alloca i32, align 4
  %i = alloca i32, align 4
  %LoweringTemp0 = alloca i32, align 4
  store i32 0, ptr %sum, align 4
  store i32 1, ptr %i, align 4
  br label %b0
  
b0:            
                     
            ; preds = %b2, %entry
  %0 = load i32, ptr %i, align 4
  %cmp = icmp slt i32 %0, 10
  br i1 %cmp, label %b1, label %b3
  
b1:            
                     
            ; preds = %b0
  %1 = load i32, ptr %sum, align 4
  %2 = load i32, ptr %i, align 4
  %3 = load i32, ptr %i, align 4
  %mul = mul nsw i32 %2, %3
  %add = add nsw i32 %1, %mul
  store i32 %add, ptr %sum, align 4
  %4 = load %"[Blazar.Core]System.StringConstant", ptr @"[Blazar.Test].strc.2", align 1
  call void @"[Blazar.System.Console]System.Console.Write.2896451628"(%"[Blazar.Core]System.StringConstant" %4)
  %5 = load i32, ptr %i, align 4
  call void @"[Blazar.System.Console]System.Console.Write.1741313647"(i32 %5)
  %6 = load %"[Blazar.Core]System.StringConstant", ptr @"[Blazar.Test].strc.4", align 1
  call void @"[Blazar.System.Console]System.Console.Write.2896451628"(%"[Blazar.Core]System.StringConstant" %6)
  %7 = load i32, ptr %sum, align 4
  call void @"[Blazar.System.Console]System.Console.WriteLine.1741313647"(i32 %7)
  br label %b2
  
b2:            
                     
            ; preds = %b1
  %8 = load i32, ptr %i, align 4
  store i32 %8, ptr %LoweringTemp0, align 4
  %9 = load i32, ptr %i, align 4
  %add1 = add nsw i32 %9, 1
  store i32 %add1, ptr %i, align 4
  br label %b0
  
b3:            
                     
            ; preds = %b0
  %10 = load %"[Blazar.Core]System.StringConstant", ptr @"[Blazar.Test].strc.6", align 1
  call void @"[Blazar.System.Console]System.Console.Write.2896451628"(%"[Blazar.Core]System.StringConstant" %10)
  %11 = load i32, ptr %sum, align 4
  call void @"[Blazar.System.Console]System.Console.WriteLine.1741313647"(i32 %11)
  ret i32 0
}
        
    Linking
The final stage in Blazar uses the LLVM toolchain to link all project reference libraries and the compiled LLVM IR into a final library or executable.
    The sum of squares example program displays the intermediate values of i and the running total sum at each iteration, then prints the final sum of squares to the terminal.
    
    Native Interoperability
Blazar supports integration with native C libraries by allowing managed functions to override their target symbol names using attributes. This enables direct calls to operating system and C runtime functions without additional wrappers or bindings.
    For example, writing a character to a file is implemented using the standard C library function fputc, which is defined in Blazar with a MethodName attribute and overrides the default method name once lowered to LLVM.
[System.Runtime.MethodName("fputc")]
private static extern int WriteCharacter(int @char, nint stream);
        
    
            declare i32 @fputc(i32, i64)
        
    During the linking stage, the function reference is resolved to the native implementation, enabling seamless interoperation with system-level libraries.
Objects
    Blazar uses single inheritance, so each class can have at most one base class.
    Every object contains a pointer to its TypeInfo followed by the object’s fields.
    Base class fields appear first, followed by derived class fields.
    The TypeInfo contains references to the virtual method table (VMT), ancestor tables, and optional interface descriptors, enabling runtime type checks and virtual dispatch.
    
    
    Consider the following example, which defines an abstract Shape base class with three derived types: Circle, Square, and Sphere.
    Each subclass overrides the virtual Area method to provide a specialized implementation based on its geometry.
    Circle and Square both implement the IArea interface, while Sphere additionally implements IVolume to provide volume calculations.
public interface IArea
{
    public double Area();
}
 
public interface IVolume
{
    public double Volume();
}
 
public abstract class Shape
{
    public int TypeId { get; }
 
    protected Shape(int typeId)
    {
        TypeId = typeId;
    }
 
    public virtual double Area() => 0.0;
}
 
public class Circle : Shape, IArea
{
    public double Radius { get; }
 
    public Circle(double radius) : base(1)
    {
        Radius = radius;
    }
 
    public override double Area() => Math.PI * Radius * Radius;
}
 
public class Square : Shape, IArea
{
    public double Size { get; }
 
    public Square(double size) : base(2)
    {
        Size = size;
    }
 
    public override double Area() => Size * Size;
}
 
public class Sphere : Shape, IArea, IVolume
{
    public double Radius { get; }
 
    public Sphere(double radius) : base(3)
    {
        Radius = radius;
    }
 
    public override double Area() => Math.PI * Radius * Radius;
    public double Volume() => (4.0 / 3.0) * Math.PI * Radius * Radius * Radius;
}
        
    
    The simplified TypeInfo definitions for Shape, Circle, Square, and Sphere describe how each type is represented in memory.
    
    
    The main program creates a Circle and Square, then prints its information through a helper function that accepts a Shape.
    This involves an implicit upcast from the derived type to Shape, and the call to Area() is dispatched virtually at runtime based on the actual object type.
public class Program
{
    public static int Main()
    {
        var circle = new Circle(2);
        var square = new Square(2);
        var sphere = new Sphere(2);
 
        WriteType(circle);
        WriteArea(circle);
 
        WriteType(square);
        WriteArea(square);
 
        WriteType(sphere);
        WriteArea(sphere);
        WriteVolume(sphere);
 
        return 0;
    }
 
    private static void WriteType(Shape shape)
    {
        Console.Write("TypeId = ");
        Console.WriteLine(shape.TypeId);
    }
 
    private static void WriteArea(IArea area)
    {
        Console.Write("Area = ");
        Console.WriteLine(area.Area());
    }
 
    private static void WriteVolume(IVolume volume)
    {
        Console.Write("Volume = ");
        Console.WriteLine(volume.Volume());
    }
}
        
    When the program is executed, the following output is produced.
TypeId = 1
Area = 12.566370614359172
TypeId = 2
Area = 4
TypeId = 3
Area = 12.566370614359172
Volume = 33.510321638291124
        
    
    The following diagram illustrates how Circle, Square, and Sphere instances are laid out in memory.
    Each object begins with a pointer to its corresponding TypeInfo, followed by its instance fields.
    
    Casting
Casting from a derived class to its base class incurs no runtime cost due to Blazar’s single inheritance model. The validity of such a cast can be fully determined at compile time. Additionally, field offsets remain fixed because derived classes append their own fields after the base class’s fields. This layout allows a base class function implementation to operate uniformly on all derived class instances.
    Casting from a base class to a derived class, however, requires a runtime check to ensure the object is actually of the target type.
    This is accomplished by accessing the TypeInfo pointer, which is always the first field in every object, and verifying that the target type exists in the object’s ancestor table.
    A simple comparison of type IDs is insufficient, as the object could be an instance of a further derived type.
Because of single inheritance, if the ancestor table is ordered from base to derived, the cast can be optimized by using the target type’s ancestor depth to directly index into the table and verify the type ID with a single comparison.
    The following diagram shows the TypeInfo and AncestorTable structures used to determine whether an object instance can be safely cast to a Circle.
    
    
    The following code snippet demonstrates the implementation of the casting function, which accepts an object pointer and a pointer to the target TypeInfo.
internal static unsafe void* CastDynamic(void* instance, in TypeInfo castTypeInfo)
{
    if (instance == null)
        return null;
 
    var typeInfo = *(TypeInfo**)instance;
 
    if (castTypeInfo.AncestorDepth >= typeInfo->AncestorTable.Count ||
        typeInfo->AncestorTable.Ids[castTypeInfo.AncestorDepth] != castTypeInfo.Id)
        return null;
 
    return instance;
}
        
    Virtual Methods
    Virtual methods are dispatched by first accessing the object’s TypeInfo and then indexing into its virtual method table (VMT).
    Because Blazar uses single inheritance, the order of virtual methods is consistent across all derived types, allowing the table index to be determined at compile time.
    This enables the same lowered code to be used for all derived types when calling virtual methods.
    The following diagram illustrates the relationship between an object’s TypeInfo and its virtual method table.
    
    Interfaces
    Interfaces differ from classes because a single class can implement multiple interfaces.
    Each class contains an InterfaceDescriptorTable, which holds a list of InterfaceDescriptors representing the interfaces it implements.
    Each InterfaceDescriptor stores the interface ID along with the corresponding virtual method table for that interface.
    This structure allows interface method calls to be correctly dispatched to the appropriate implementation for each object instance.
    
    
    To avoid repeatedly retrieving the virtual method table for an interface at every call site, the lookup is performed once when the object is cast to an interface.
    At that point, an InterfacePointer is created, which contains both a pointer to the object instance and a pointer to the corresponding virtual method table.
    This allows subsequent interface method calls to dispatch directly through the InterfacePointer without additional type lookups.
internal unsafe struct InterfacePointer
{
    public void* Pointer;
    public void** VirtualMethods;
}
        
    
    The following diagram illustrates the relationship between an object’s TypeInfo and its associated InterfaceDescriptors, showing how each implemented interface links to its corresponding virtual method table.
    
    
    Casting an object to an interface requires iterating over the object’s InterfaceDescriptorTable to locate the descriptor with the matching interface ID.
    Once found, an InterfacePointer can be constructed, containing a pointer to the object and a pointer to the interface’s virtual method table.
internal static unsafe InterfacePointer CastInterface(void* instance, in TypeInfo castTypeInfo)
{
    if (instance != null)
    {
        var typeInfo = *(TypeInfo**)instance;
 
        var interfaceTable = typeInfo->InterfaceDescriptorTable;
 
        for (var i = 0; i < interfaceTable.Count; i++)
        {
            var descriptor = interfaceTable.Descriptors[i];
 
            if (descriptor.Id == castTypeInfo.Id)
                return new InterfacePointer
                {
                    Pointer = instance,
                    VirtualMethods = descriptor.VirtualMethodTable.VirtualMethods,
                };
        }
    }
 
    return default;
}
        
    Visual Studio
    Supports a custom project type built on MSBuild with full editor tooling, including syntax highlighting, code formatting, code completion, code outlining, signature helper, go to definition, and code refactoring extensions.
Solution Explorer
Custom project node in Solution Explorer with in-place project editing, allowing project files to be modified without unloading. Includes custom icons for Blazar source files.
    
    MSBuild
    Project system built on MSBuild with support for ProjectReference and PackageReference, providing full compatibility with standard project file features used in MSBuild projects.
<Project Sdk="Mapromar.Blazar.Sdk">
  <PropertyGroup>
    <AssemblyName>BlazarTester</AssemblyName>
    <RootNamespace>BlazarTester</RootNamespace>
    <OutputType>Exe</OutputType>
    <TargetFramework>Blazar,Version=v1.0</TargetFramework>
  </PropertyGroup>
  <PropertyGroup>
    <Product>BlazarTester</Product>
    <AssemblyTitle>$(Product)</AssemblyTitle>
    <Description>BlazarTester</Description>
    <Company>Mapromar</Company>
    <Authors>$(Company)</Authors>
    <Copyright>Copyright © 2005-2019 Mapromar</Copyright>
    <NeutralLanguage>en-CA</NeutralLanguage>
    <Version>0.0.0</Version>
    <FileVersion>0.0.0</FileVersion>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Blazar.Core\Blazar.Core.bproj" />
    <ProjectReference Include="..\Blazar.System.Memory\Blazar.System.Memory.bproj" />
    <ProjectReference Include="..\Blazar.System.Console\Blazar.System.Console.bproj" />
    <ProjectReference Include="..\Blazar.System.Runtime\Blazar.System.Runtime.bproj" />
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Mapromar.Build.ProjectDependency" Version="1.*">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>
</Project>
        
    Debugger
Support is provided for debugging within Visual Studio, including breakpoints and inspection of variables and runtime state.
    
    Syntax Highlighting
Highlights all references to a variable under the cursor in the source code editor.
    
    Code Formatting
Formats whitespace and indentation in the source code editor to maintain consistent, standard coding practices.
    
    Code Outlining
Allows code blocks in the source editor to be expanded or collapsed.
    
    Signature Helper
Displays method modifiers, return type, and parameter names and types in a popup within the source editor.
    
    Go To Definition
Navigates to the location where an element is defined and positions the cursor at its declaration in the code file.
    
    Error Diagnostics
Error diagnostics display popups in the editor to indicate syntax errors in the source code.