Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / ASM

Bird Programming Language: Part 2

4.85/5 (10 votes)
1 Jan 2013GPL36 min read 28.7K   344  
A new general purpose language that aims to be fast, high level and simple to use. (I renamed it from Anonymus)

Articles About Bird

Table of Contents 

1st Sample Program: Squares

In this sample I draw squares that are fading gradually. When the mouse is over it, it appears.

I created some constants and an array that contains that how much a square is visible:

C++
using System
using BlitzMax

namespace SquaresSample
    const var SquareSize = 32,
              Width = 24,
              Height = 20

    float[Width, Height] Array

The IsMouseOver function returns true if the the mouse is over the specified area:

C++
bool IsMouseOver(int x, y, w, h)
    return x <= MouseX() < x + w and y <= MouseY() < y + h

The Update function draws the squares and updates the values of the array. It stores the brightness in the Value variable, the position of the square is stored in XPos and YPos:

C++
void Update()
    for var x, y in 0 .. (Width, Height)
        var Value = Array[x, y]
        var XPos = x * SquareSize
        var YPos = y * SquareSize

If the mouse is over the square it sets the Value to 1, which is the maximal brightness, and draws a white rect:

C++
if IsMouseOver(XPos, YPos, SquareSize, SquareSize)
    Value = 1
    SetColor 255, 255, 255
    DrawRect XPos, YPos, SquareSize, SquareSize

If the mouse is not over the square and it's not black, it draws the rect with the appropriate color for its position and brightness:

C++
else if Value > 0
    var Color = 255 * Value
    var Red = (int)(((float)x / Width) * Color)
    var Greed = (int)(((1 - (float)y / Height)) * Color)
    var Blue = (int)(((float)y / Height) * Color)

    SetColor Red, Greed, Blue
    DrawRect XPos, YPos, SquareSize, SquareSize

It decreases the Value variable until 0 and stores it in the array:

C++
Array[x, y] = Math.Max(Value - 0.02, 0)

The program starts with the Main function. It creates the graphics window with Graphics. In a cycle it constantly clears, redraws and updates the screen (60 times in a second by default). It can be interrupted with pressing Escape or closing the window:

C++
void Main()
    Memory.Zero Array, sizeof(Array)
    Graphics SquareSize * Width, SquareSize * Height

    while !KeyHit(Keys.Escape) and !AppTerminate()
        Cls
        Update
        Flip

2nd Sample Program: Circles

This sample calculates the color of each pixels, so it's useable for a small comparison with C++. In order to increase the performance, it computes on an image that is smaller than the window and it scales it up.

I made some helper functions. The ArgbColor function combines the four 8 bit components of a color to a single 32 bit number:

C++
using System
using BlitzMax

namespace CirclesSample
    int ArgbColor(byte a, r, g, b)
        return (a to int) << 24 | (r to int) << 16 |
               (g to int) << 8 | b

The GetDistance calculates the distance of two points:

C++
double GetDistance(double x1, y1, x2, y2)
    var x = x2 - x1, y = y2 - y1
    return Math.Sqrt(x * x + y * y)

The Clamp function creates a byte number that is between 0 and 255 from an integer:

C++
byte Clamp(int x)
    if x < 0: x = 0
    if x > 255: x = 255
    return (byte)x

The main task is done by  UpdateImage. It calls the

LockImage
first to make the image modifiable, and it stores how many seconds elapsed since computer startup:

C++
void UpdateImage(IntPtr Image)
    var Pixmap = LockImage(Image, 0, true, true)
    var Width = ImageWidth(Image)
    var Height = ImageHeight(Image)
    var Time = MilliSecs() / 1000.0

It goes through all pixels with a for loop, calculates its relative position, and its distance to the midpoint of the screen with the time subtracted from it:

C++
for var x, y in 0 .. (Width, Height)
    var RelX = (x to double) / Width
    var RelY = (y to double) / Height
    var Value = GetDistance(RelX, RelY, 0.5, 0.5) * 3 – Time

From the value it calculates a color:

C++
var Light = ((RelY * 100) * Math.Abs(Math.Sin(Value / 1.5)) to int)
var Red = ((RelX * 255) * Math.Abs(Math.Cos(Value)) to int) + Light
var Green = (((1 - RelY) * 255) * Math.Abs(Math.Sin(Value)) to int) + Light
var Blue = ((RelY * 255) * Math.Abs(Math.Cos(Value / 3)) to int) + Light

It stores the pixel and at the end of the cycle it calls the UnlockImage function to make the image drawable again:

C++
    var Color = ArgbColor(255, Clamp(Red), Clamp(Green), Clamp(Blue))
    WritePixel Pixmap, x, y, Color

UnlockImage Image

The Main function only differs from the previous sample that it creates the image that it draws scaled up, and it shows the FPS:

C++
void Main()
    Graphics 1024, 720

    var Image = CreateImage(512, 360)
    while not KeyHit(Keys.Escape) and not AppTerminate()
        Cls
        UpdateImage Image
        DrawImageRect Image, 0, 0, 1024, 720
        DrawFrameStats
        Flip

Here are the performance results (frames per second) on my Intel Core 2 Dou 1.8 Ghz processor:

Image SizeG++ 4.6.2Clang (LLVM 3.1)Visual C++ 11 Bird
320×24017192734

Analyzing the Created Assembly Code

The ArgbColor Function

This function has four parameters that are stored in al,

ah
, dl, dh registers in this order, the return value is stored in eax. The (a to int) << 24 expression is stored in eax too, because the destination variable is the same register. But it overwrites the value of ah, so it has to be copied to cl. The other sub-expression can be stored in
ecx
without overwriting anything.

ASM
_CirclesSample_ArgbColor:
    mov cl, ah
    and eax, 0xFF
    shl eax, 24
    and ecx, 0xFF
    shl ecx, 16
    or eax, ecx
    movzx ecx, dl
    shl ecx, 8
    or eax, ecx
    movzx ecx, dh
    or eax, ecx
    ret

The GetDistance Function

The first three parameter is stored in xmm0, xmm1, xmm2, the 4th is on the stack. The return value is stored in xmm0. The stack pointer is not saved to ebp in all cases for performance increasement:

ASM
_CirclesSample_GetDistance:
    movsd xmm3, xmm0
    movsd xmm0, xmm2
    subsd xmm0, xmm3
    movsd xmm3, qword[esp + 4]
    subsd xmm3, xmm1
    movsd xmm1, xmm0
    mulsd xmm1, xmm0
    movsd xmm2, xmm3
    mulsd xmm2, xmm3
    addsd xmm1, xmm2
    sqrtsd xmm0, xmm1
    ret 8

The Clamp Function

The compiler uses conditional moves in this case to avoid a conditional jump. For the cmov instruction needs the second parameter to be a memory location:

ASM
_CirclesSample_Clamp:
    cmp eax, 0
    cmovl eax, dword[_25]
    cmp eax, 255
    cmovg eax, dword[_27]
    ret

3rd Sample Program: Fire

In this sample I created a float array for pixels. These values are increased if the mouse is close to it while blurring the image too. I store the colors for these values in another array. These are the constants and arrays I created:

C#
using System
using BlitzMax

namespace FireSample
    const var
        WindowSize = (1024, 768),
        ImageSize = (560, 420),
        RectSize = 80,
        ColorNumber = 768

    float[ImageSize.0, ImageSize.1] Array
    int[ColorNumber] Colors

I this sample I use tuples, the type of WindowSize and

ImageSize
is (int, int).
I use two functions from the previous sample:

C#
int ArgbColor(byte a, r, g, b)
    return (a to int) << 24 | (r to int) << 16 | (g to int) << 8 | b

byte Clamp(int x)
    if x < 0: x = 0
    if x > 255: x = 255
    return x to byte

I changed the parameter types of the GetDistance function to tuples:

C#
float GetDistance(float2 P1, P2)
    var x = P1.x - P2.x, y = P1.y - P2.y
    return Math.Sqrt(x * x + y * y) to float

The GetValue function returns with the value of the array at the specified position if they are valid, otherwise it returns zero:

C#
float GetValue(int x, y)
    if 0 <= x < ImageSize.0 and 0 <= y < ImageSize.1
        return Array[x, y]

    return 0f

The UpdateValues function updates the values of Array. First it blurs the image with the weighted average of the nearby pixels. I dived the sum with 7.1 instead of 7 to make it fading:

C#
void UpdateValues()
    for var x, y in 0 .. ImageSize
        var Value = Array[x, y] * 5
        Value += GetValue(x + 1, y + 1) * 0.5
        Value += GetValue(x - 1, y + 1) * 0.75
        Value += GetValue(x - 2, y + 2) * 0.5
        Value += GetValue(x - 3, y + 3) * 0.25
        Array[x, y] = Value / 7.1

It calculates the relative position of the mouse and the square that it will be work inside. The Max functions returns the bigger from two numbers, the Min returns the smaller. If the parameters are not scalar numbers, it does the same operation with all their members. The parameter 0 is automatically converted to (0, 0):

C#
var Mouse = (GetMousePosition() to float2) * ImageSize / WindowSize
var P1 = Math.Max((Mouse to int2) - RectSize, 0)
var P2 = Math.Min((Mouse to int2) + RectSize, ImageSize - 1)

Gets the distance for every point and increases the brightness of the pixel based on the distance. The maximal value is one:

C#
for var x, y in P1 .. P2
    var Dist = 1 - GetDistance(Mouse, (x, y)) / RectSize
    if Dist >= 0f: Array[x, y] = Math.Min(Array[x, y] + Dist / 10f, 1)

The UpdateImage function updates the image with the values stored in Array:

C#
void UpdateImage(IntPtr Image)
    var Pixmap = LockImage(Image)
    for var x, y in 0 .. ImageSize
        var Color = Array[x, y] * (ColorNumber - 1) to int
        WritePixel Pixmap, x, y, Colors[Color]

    UnlockImage Image

This function calculates the colors for the values:

C#
void Initialize()
    for var i in 0 .. ColorNumber
        var Value = (i to float) / (ColorNumber - 1)
        var Red = (Math.Min(Value, 1f / 3)) * 3 * 255 to int
        var Green = (Math.Min(Value, 1.65f / 3) - 0.65f / 3) * 3 * 255 to int
        var Blue = (Math.Min(Value, 1f) - 2f / 3) * 3 * 255 to int

        Colors[i] = ArgbColor(255, Clamp(Red), Clamp(Green), Clamp(Blue))

The program's main function is similar to the ones in the previous samples. It calls the UpdateValues and UpdateImage function:

C#
void Main()
    Initialize
    Graphics WindowSize

    var Image = CreateImage(ImageSize)
    while not KeyHit(Keys.Escape) and not AppTerminate()
        Cls
        Update
        UpdateImage Image
        DrawImageRect Image, (0, 0), WindowSize
        DrawFrameStats
        Flip

The performance is alike for all the compilers, but the C++ version is much longer.

4th Sample Program: Reflection

This sample shows how low-level reflection can be used. I plan to make a higher level layer based on it in the future.

I had made two kind of identifiers. The first one is the declared identifier that are usually classes, functions, variables, etc. They have a name. The other group is that are not declared, the compiler generates them, e.g. when getting the address of the variable or making a tuple.

Writing the members to screen:

The first function outputs five members of the Internals.Reflection.Reflection class. To do this it reads the reflection data made by the compiler for it with the ReadDeclaredId functions that returns a

DeclaredIdData
structure. It contains the name of the identifier, its members, etc. It can be called for the members too to get their name:

C#
using System
using Internals
using Internals.Reflection

namespace ReflectionSamples
    void ListMembers()
        Console.WriteLine "-ListMembers-----------------------------------------"
        var IdData = Reflection.ReadDeclaredId(id_desc_ptr(Reflection))
        var Count = Math.Min(5u, IdData.Members.Length)
        
        for var i in 0 .. Count
            var IdData2 = Reflection.ReadDeclaredId(IdData.Members[i])
            Console.WriteLine IdData2.Name
            IdData2.Free
            
        IdData.Free
        Console.WriteLine

Output:

-ListMembers-------------------------------------------
EntryAssembly
GetAliasBase
GetRealId
IsEquivalentHelper
GetDeclaredEquivalent

Comparing Two Types

The next function outputs the type of an object and compares it with another. The WriteLine calls the object's ToString method if the object is not a string. It returns the name of the type by default:

C#
void TypeCompare()
    object Obj = (0, 1, 2)
    Console.WriteLine "-TypeCompare-----------------------------------------"
    Console.WriteLine Obj
    IDENTIFIER_PTR Type = id_desc_ptr((int, int, int))
    Console.WriteLine Reflection.IsEquivalent(ObjectHelper.GetType(Obj),Type)
    Console.WriteLine

Output:

-TypeCompare-------------------------------------------
(int, int, int)
True

Modifying a variable

This function modifies the value of a variable with the help of DeclaredIdData.Address:

C#
int GlobalVariable
void SettingGlobal()
    Console.WriteLine "-SettingGlobal-----------------------------------------"
    var Id = id_desc_ptr(GlobalVariable)
    var IdData = Reflection.ReadDeclaredId(Id)
    *(int*)IdData.Address = 123123
    IdData.Free

    Console.WriteLine GlobalVariable.ToString()
    Console.WriteLine

Output:

-SettingGlobal-----------------------------------------
123123

Calling a function

The CallingFunction converts the DeclaredIdData.Address member to a function pointer and calls it:

C#
void CallingFunction()
    Console.WriteLine "-CallingFunction-------------------------------------"
    var Id = id_desc_ptr(Function)
    var IdData = Reflection.ReadDeclaredId(Id)
    var FuncPtr = IdData.Address to (static string -> void)
    IdData.Free

    FuncPtr "Function called"
    Console.WriteLine

void Function(string Str)
    Console.WriteLine "Str = " + Str

Output:

-CallingFunction---------------------------------------
Str = Function called

Function Parameters

The type of all variables are undeclared identifiers. They are described by the UndeclaredIdData structure. The Type member specifies what kind of identifier it is. e.g. it could be UndeclaredIdType.Pointer. All declared identifiers can be referenced with UndeclaredIdType.Unknown value, the UndeclaredIdData.DeclaredId member stores its pointer.

The FunctionData first makes sure that the given identifier is a function:

C#
void FunctionData(IDENTIFIER_PTR Id)
    Console.WriteLine "-FunctionData----------------------------------------"
    var IdData = Reflection.ReadDeclaredId(Id)
    if IdData.Type != DeclaredIdType.Function
        Console.WriteLine "Not a function"
        IdData.Free

If it's a function, then it reads the identifier's type data. The type of a functions contains the return value, parameters, etc. It uses the GetUndeclIdName function to determine the name of their type that are undeclared identifiers too:

C#
else
    UNDECLARED_ID_PTR FuncType = IdData.BaseUndeclaredId
    IdData.Free

    var TypeData = Reflection.ReadUndeclaredId(FuncType)
    Console.WriteLine "Function: " + Reflection.GetFullName(Id)
    Console.WriteLine "Return type: " + GetUndeclIdName(TypeData.BaseUndeclaredId)
    Console.WriteLine "Parameters:"
    for var i in 0 .. TypeData.Parameters.Length
        var ParamType = GetUndeclIdName(TypeData.Parameters[i].UndeclaredType)
        var ParamName = TypeData.Parameters[i].Name
        Console.WriteLine ParamType + " " + ParamName

    TypeData.Free

Determining the name of an undeclared identifier is not simple, so it only returns its name, if it refers to a declared one or it has a declared equivalent like UndeclaredIdType.String:

C#
string GetUndeclIdName(UNDECLARED_ID_PTR UndeclId)
    var UndeclData = Reflection.ReadUndeclaredId(UndeclId)
    var UndeclType = UndeclData.Type

    if UndeclType == UndeclaredIdType.Unknown
        var DeclId = UndeclData.DeclaredId
        UndeclData.Free
        return Reflection.GetFullName(DeclId)
    else
        UndeclData.Free

        var DeclId = Reflection.GetDeclaredEquivalent(UndeclType)
        if DeclId != null: return Reflection.GetFullName(DeclId)
        return "???"

Output:

-FunctionData------------------------------------------
Function: ReflectionSamples.GetUndeclIdName
Return type: System.String
Parameters:
Internals.Reflection.UNDECLARED_ID_PTR UndeclId

The Main function

It calls the previous sample functions:

C#
void Main()
    ListMembers
    TypeCompare
    SettingGlobal
    CallingFunction
    FunctionData id_desc_ptr(GetUndeclIdName)

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)