Jiyu Update - February 2020

Josh Huelsman · February 1, 2020

Progress has been a bit slow for the last few weeks because I’ve personally lost a large amount of time to playing Stardew Valley, but we’ve managed to still fit in a number of important features for this month’s update.

  • There is now support for harnessing LLVM’s JIT engine in user code
#import "Compiler";

// Since metaprograms are just fully JIT-ed LLVM modules,
// we have been internally using the JIT-er to lookup the "main" symbol
// in a program and call that... We no longer do that internally.
// There is a legacy function in Compiler to mimick the way this worked
// previously, but now we allow user code to fully leverage this functionality.

func @metaprogram main() {
    var options: Build_Options;
    options.only_want_obj_file = true;
    var compiler = create_compiler_instance(*options);

    if compiler_load_string(compiler, CODE_TO_COMPILE) != true return;
    if compiler_typecheck_program(compiler) != true return;
    if compiler_generate_llvm_module(compiler) != true return;

    // New function to JIT the entire program.
    if compiler_jit_program(compiler) != true return;

    // Once compiler_jit_program passes, we can now arbitrarily query symbols
    // and call into them.
    var do_a_thing = cast(() -> void) compiler_jit_lookup_symbol(compiler, "do_a_thing");
    do_a_thing();

    var do_a_thing2 = cast((p: string) -> void) compiler_jit_lookup_symbol(compiler, "do_a_thing2");
    do_a_thing2("Howdy :)");
}

let CODE_TO_COMPILE =
"""
#import "LibC";

// We are using the @export tag here for the sake of demonstration, otherwise
// we would need to lookup the proper jiyu-mangled names.
func @export("do_a_thing") do_a_thing() {
    printf("Hello, Sailor\n");
}

func @export("do_a_thing2") do_a_thing2(str: string) {
    printf("Hello, Pilot! %.*s\n", str.length, str.data);
}
""";

Additionally, we now provide a library version of the compiler that is built alongside the compiler driver program. The API is exactly the same whether the user code is running as a metaprogram or as a compiled binary. This allows users to effectively implement custom driver frontends that support arbitrary operations that the official driver does not. This also allows compiled user code to JIT-compile Jiyu code at runtime, enabling games to embed Jiyu as a robust scripting system, or enabling IDEs to embed Jiyu in environments where running external executables and toolchains is not allowed.

  • Support for operator overloading has been added. Currently, only +, -, *, and / can be overloaded, but support for more operators will be added in the future.
#import "LibC";

struct Vector3 {
    var x: float;
    var y: float;
    var z: float;

    func make(x: float, y: float, z: float) -> Vector3 {
        var v: Vector3;
        v.x = x;
        v.y = y;
        v.z = z;
        return v;
    }
}

operator+(b: float, str: string) -> float {
    return b + cast(float) str.length;
}

operator+(a: Vector3, b: Vector3) -> Vector3 {
    return Vector3.make(a.x+b.x, a.y+b.y, a.z+b.z);
}

operator-(a: Vector3, b: Vector3) -> Vector3 {
    return Vector3.make(a.x-b.x, a.y-b.y, a.z-b.z);
}

func print_vector(v: Vector3) {
    printf("vector3: %f, %f, %f\n", v.x, v.y, v.z);
}

func @metaprogram main() {
    var a = Vector3.make(1, 2, 3);
    var b = Vector3.make(4, 5, 6);

    var c = a + b;
    var d = b - a;

    print_vector(a);
    print_vector(b);
    print_vector(c);
    print_vector(d);

    printf("1.0 + \"Hello\": %f\n", 1.0 + "Hello");
}

  • castano has implemented support for typeof() operator that resolves to the type of an input declaration.

  • castano has implemented support for enums:

#import "LibC";
#import "Basic";


enum Day : uint {
    Monday;
    Tuesday = Monday + 1;
    Wednesday = 2;

    Thursday;
    Friday = 1 + Thursday;

    Saturday;
    Sunday;
}

enum WeekendDay {
    Saturday = cast(WeekendDay)Day.Saturday;   // This should require a cast. Do not allow implicit cast from one enum to another.
    Sunday;
}

enum Bool : uint8 { False; True; }

typealias DAY = Day;

var another_day : DAY = Day.Monday;

var monday = Day.Monday;
var tuesday : Day = Day.Tuesday;
var sunday : Day = cast(Day)WeekendDay.Sunday;
var my_day : Day;

func test(day : Day) -> int {
    return cast(int)day + 1;
}

func main() {
    {
        var day : Day;

        let Monday = Day.Monday;
        let Tuesday = Monday + 1;
        assert(typeof(Monday) == Day);

        let MONDAY = cast(int)Day.Monday;   // Type of MONDAY is int
        assert(typeof(MONDAY) == int);
    }
  
    printf("Monday = %d\n", Day.Monday);
    printf("Tuesday = %d\n", Day.Tuesday);
    printf("Wednesday = %d\n", Day.Wednesday);
    printf("Thursday = %d\n", Day.Thursday);
    printf("Friday = %d\n", Day.Friday);
    printf("Saturday = %d\n", Day.Saturday);
    printf("Sunday = %d\n", Day.Sunday);

    printf("test(Day.Monday) = %d\n", test(Day.Monday));

    // Implicit calls from integer to enum:
    // Currently these are only allowed when the expression is a mutable literal.
    {
        var day : Day = 0;
        printf("test(0) = %d\n", test(0));
    }
    

    //printf("Whatever = %d\n", Day.Whatever); // This should trigger an error.
    {
        var day : Day = .Monday;
        test(.Tuesday);
        if day == .Wednesday {
            day = .Sunday;
        }
        if .Wednesday > day {
            // test(1 + .Tuesday); // This produces an error, as expected.        
        }
        
    }
    
}

Additionally, support for enum bit-flags is also available:

#import "LibC";
#import "Basic";

enum @flags Entity_Flags {
    Invisible;
    Solid;
    Cast_Shadows;
}

func main () {
    printf("%d\n", Entity_Flags.Invisible | .Solid | .Cast_Shadows);
}
  • Tuples are now part of the language:
#import "LibC";

func test() -> (a: int, b: float) {
    // arguments in tuple-expressions are automatically inferred
    // according to the fields of the tuple being assigned, returned, or passed to.
    return (1, 4);
}

func test3() -> (c: int, d: float) {
    return (1, 2);
}

func test2(a: (a: int, b: float)) {
    printf("T2: %d\n", a.a);
    printf("T2: %f\n", a.b);
}

func main() {
    var t = test();
    var t3 = test3();

    // t and t3 are the same type as far as the type system is concerned,
    // their tuples will share the same type information in the RTTI system,
    // and internally, they will have the same type table index,
    // however, since the semantic analysis system assigns types in-place,
    // t and t3 have distinct fields.

    // t.c = t3.a; // this fails

    printf("%d\n", t.a);
    printf("%f\n", t.b);

    printf("%d\n", t3.c);
    printf("%f\n", t3.d);

    t = t3;

    t = (2, 4);

    printf("%d\n", t.a);
    printf("%f\n", t.b);

    test2(t);
    test2((6, 9));

    var n: int;
    var m: float;

    // In the event that a tuple-expression is on the left-hand-side
    // of an assignment, the type of the tuple is directly determined by
    // the types of its arguments, regardless of the type of the tuple
    // on the right-hand-side.
    // Each field of the tuple on the right-hand-side is unpacked into
    // the individual arguments of the tuple-expression on the left-hand-
    // side.
    (n, m) = test();

    // Like other types of assignments, you cannot try to use a literal
    // in a tuple expression on the left-hand-side of an assignment.
    // (n, 1) = test(); // does not work.

    printf("n: %d\n", n);
    printf("m: %f\n", m);

    // These do not work yet, but are planned to be available eventually.
    /*
    var (i, j) = test();
    var (i: int, j: float) = test();
    */
}

Additional changes include improved support for C interfaces on various platforms, improved support for trivial-constant-folding, and various fixes.

I want to again thank the contributors for picking up this project and helping to improve it!

The code for the compiler can be found at: jiyu. Pull requests, feature requests, and issue reports are all welcome.

Twitter, Facebook