-
Notifications
You must be signed in to change notification settings - Fork 0
Metaprogramming
One can use the insert directive to automatically generate getter and setter functions. This is an elementary example for didactic purposes, but one can extrapolate this example to do much more complex metaprogramming.
generate_getters_and_setters :: ($T: Type) -> string {
builder: String_Builder;
info := type_info(T);
for member: info.members {
print("%\n", type_of(member.type));
print_to_builder(*builder, "get_% :: (s: %) -> type_of(s.%) { return s.%; }\n",
member.name, T, member.name, member.name);
print_to_builder(*builder, "set_% :: (s: *%, val: type_of(s.%)) { s.% = val; }\n",
member.name, T, member.name, member.name);
}
s := builder_to_string(*builder);
return s;
}
#insert -> string {
return generate_getters_and_setters(Person);
}Given the following Person struct as an example:
Person :: struct {
name: string;
age: int;
}The following code generated by the function would result in this:
get_name :: (s: Person) -> type_of(s.name) { return s.name; }
set_name :: (s: *Person, val: type_of(s.name)) { s.name = val; }
get_age :: (s: Person) -> type_of(s.age) { return s.age; }
set_age :: (s: *Person, val: type_of(s.age)) { s.age = val; }Structs of Arrays is a way of rearranging the layout of the data fields of a struct. Specifically, Structs of Arrays (SoA) is a data layout separating elements of a struct into one parallel array per field. Take the following example:
// this is just a normal struct, no SOA
Vec3 :: struct {
x: float;
y: float;
z: float;
}
// this is a SOA Vec3 struct
SOA_Vec3 :: struct {
x: [100] float;
y: [100] float;
z: [100] float;
}Using an #insert directive and generating code at compile time, we can generalize the concept of SOA to any structs using the following:
Vec3 :: struct {
x: float;
y: float;
z: float;
}
Person :: struct {
age: int;
is_cool: bool;
}
SOA :: struct(T: Type, count: int) {
#insert -> string {
t_info := type_info(T);
builder: String_Builder;
defer free_buffers(*builder);
for fields: t_info.members {
print_to_builder(*builder, " %1: [%2] type_of(T.%1);\n", fields.name, count);
}
result := builder_to_string(*builder);
return result;
}
}
#import "Basic";
main :: () {
// create an soa_vec3
soa_vec: SOA(Vec3, 10);
for i: 0..soa_vec.count-1 {
print("soa_vec.x[i]=%, soa_vec.y[i]=%, soa_vec.z[i]=%\n", soa_vec.x[i], soa_vec.y[i], soa_vec.z[i]);
}
// create an soa_person
soa_person: SOA(Person, 10);
for i: 0..soa_person.count-1 {
print("soa_person.age[i]=%, soa_person.is_cool[i]=%\n", soa_person.age[i], soa_person.is_cool[i]);
}
}Applying #no_reset macro allows one to initialize complex data structures at compile time easily without the need of complex assembly language macros like in C++. Here is a simple example of #no_reset directive.
#no_reset array: [5] int;
#run {
array[0] = 1;
array[1] = 10;
array[2] = 3;
array[3] = 5;
array[4] = 700;
}
Applying #no_reset macro allows one to initialize complex data structures at compile time easily without the need of complex assembly language macros like in C++. In this example, one can load a binary NNUE file into memory and populate the data structures at compile time.
#run load_model("orange7_8_24.nnue");
#no_reset nnue: NNUE #align 64;
NNUE :: struct {
feature_weights: [2][9][90][128] s16;
feature_biases: [128] s16;
output_weights: [2][128] s16;
output_bias: s32;
}
load_model :: (filename: string) {
print("loading file %\n", filename);
weights, success := read_entire_file(filename);
assert(success, "File % not found.", filename);
memcpy(*nnue, *weights[0], size_of(NNUE));
free(weights);
}