On news.ycombinator I've found an article about the Jai language being designed by Jonathan Blow:
The purpose of Jai is to write high performance video games, and it's not a safe language, so if you look at it with Rust eyes you will see a soup of strange dangerous things. Yet Rust is not meant just to write browsers and very low-level code, you should be able to use Rust to write games too (see Piston).
Below I list two Jai features that I think could be interesting for Rust too.
An interesting feature of Jai is SoA/AoS:
Modern processors and memory models are much faster when spatial locality is adhered to. This means that grouping together data that is modified at the same time is advantageous for performance. So changing a struct from an array of structures (AoS) style:
struct Entity { Vector3 position; Quaternion orientation; // ... many other members here };
Entity all_entities[1024]; // An array of structures
for (int k = 0; k < 1024; k++) update_position(&all_entities[k].position);
for (int k = 0; k < 1024; k++) update_orientation(&all_entities[k].orientation);
to a structure of arrays (SoA) style:
struct Entity { Vector3 positions[1024]; Quaternion orientations[1024]; // ... many other members here };
Entity all_entities; // A structure of arrays
for (int k = 0; k < 1024; k++) update_position(&all_entities.positions[k]);
for (int k = 0; k < 1024; k++) update_orientation(&all_entities.orientations[k]);
can improve performance a great deal because of fewer cache misses.
However, as programs get larger, it becomes much more difficult to reorganize the data. Testing whether a single, simple change has any effect on performance can take the developer a long time, because once the data structures must change, all of the code that acts on that data structure breaks. So Jai provides mechanisms for automatically transitioning between SoA and AoS without breaking the supporting code. For example:
Vector3 :: struct { x: float = 1; y: float = 4; z: float = 9; }
v1 : [4] Vector3; // Memory will contain: 1 4 9 1 4 9 1 4 9 1 4 9
Vector3SOA :: struct SOA { x: float = 1; y: float = 4; z: float = 9; }
v2 : [4] Vector3SOA; // Memory will contain: 1 1 1 1 4 4 4 4 9 9 9 9
Getting back to our previous example, in Jai:
Entity :: struct SOA { position : Vector3; orientation : Quaternion // .. many other members here }
all_entities : [4] Entity;
for k : 0..all_entities.count-1 update_position(&all_entities[k].position);
for k : 0..all_entities.count-1 update_orientation(&all_entities[k].orientation);
Now the only thing that needs to be changed to convert between SoA and AoS is to insert or remove the SOA keyword at the struct definition site, and Jai will work behind the scenes to make everything else work as expected.
Normal AoS Rust struct:
struct Foo {
a: f32,
b: f32,
c: f32,
d: f32,
}
A SoA struct:
#[SOA]
struct Foo {
a: f32,
b: f32,
c: f32,
d: f32,
}
This could be an intermediate point:
struct Foo {
#[hot] a: f32,
b: f32,
c: f32,
d: f32,
}
It's useful when the 'a' field is hot while the others are cold, so you want the 'a' fields in a memory zone separated from the other fields, to increase cache coherence when you access the a fields often.
A related idea is to group fields:
struct Foo {
#[SOA(1)] a: f32,
#[SOA(1)] b: f32,
#[SOA(2)] c: f32,
#[SOA(2)] d: f32,
}
It's useful if your code uses the "a" and "b" fields together often, and the "c" and "d" together often. But the other combinations of accesses (like a and c) are uncommonly done.
Jai allows inlining annotations at the function definition and also at the calling point:
test_a :: () { /* ... */ }
test_b :: () inline { /* ... */ }
test_c :: () no_inline { /* ... */ }
test_a(); // Compiler decides whether to inline this
test_b(); // Always inlined due to "inline" above
test_c(); // Never inlined due to "no_inline" above
inline test_a(); // Always inlined
inline test_b(); // Always inlined
inline test_c(); // Always inlined
no_inline test_a(); // Never inlined
no_inline test_b(); // Never inlined
no_inline test_c(); // Never inlined
Allowing #[inline]/#[noinline] at the calling point in Rust code sounds nice (I guess the annotation at the calling point overrides the attribute at the function definition point).
Currently the syntax is allowed:
#![feature(stmt_expr_attributes)]
#[inline(never)] pub fn foo(x: u32) -> u32 { x * 2 }
fn main() {
use std::env::args;
let x = args().nth(1).unwrap().parse::<u32>().unwrap();
#[inline(always)] let y = foo(x);
println!("{}", y);
}
But apparently the inline(always) is ignored, or it loses against the inline(never):
...
je .LBB1_32
cmpq %r15, %r14
je .LBB1_32
movl $1, %r8d
movq %rdi, %rcx
movq %r14, %rdx
callq __rust_deallocate
.LBB1_32:
movzbl 64(%rsp), %eax
cmpl $212, %eax
jne .LBB1_34
.Ltmp16:
leaq 40(%rsp), %rcx
callq _ZN3sys2os9Args.Drop4drop20hfcd1409b1c697392sDxE
.Ltmp17:
.LBB1_34:
shrq $32, %rbx
movl %ebx, %ecx
callq _ZN3foo20h6a4eb31decab0346eaaE
movl %eax, 92(%rsp)
leaq _ZN3fmt3num16u32.fmt..Display3fmt20hbb2431870effd2eaxLVE(%rip), %rax
movq %rax, 104(%rsp)
leaq 92(%rsp), %rax
movq %rax, 96(%rsp)
leaq ref4347(%rip), %rax
movq %rax, 40(%rsp)
movq $2, 48(%rsp)
xorps %xmm0, %xmm0
movups %xmm0, 56(%rsp)
...