harec

[hare] Hare compiler, written in C11 for POSIX OSs
Log | Files | Refs | README | LICENSE

commit 269edfdb7d1fa78663cc7d09591fad90bd15d54a
parent b271d374207b7d13aa27b9cb079613f37a2954e0
Author: Bor Grošelj Simić <bgs@turminal.net>
Date:   Wed,  8 Feb 2023 00:49:27 +0100

implement align() builtin

Signed-off-by: Bor Grošelj Simić <bgs@turminal.net>

Diffstat:
Minclude/expr.h | 1+
Minclude/lex.h | 1+
Msrc/check.c | 14++++++++++++++
Msrc/eval.c | 3+++
Msrc/gen.c | 6++++++
Msrc/lex.c | 1+
Msrc/parse.c | 5+++++
Mtests/01-arrays.ha | 21+++++++++++++--------
Mtests/02-integers.ha | 52++++++++++++++++------------------------------------
Mtests/03-pointers.ha | 3+++
Mtests/04-strings.ha | 4++--
Mtests/06-structs.ha | 3+++
Mtests/07-aliases.ha | 19+++++++++++++++++++
Mtests/08-slices.ha | 4+++-
Mtests/09-funcs.ha | 3+++
Mtests/13-tagged.ha | 9+++++----
Mtests/15-enums.ha | 7++++++-
Mtests/21-tuples.ha | 3+++
Mtests/23-errors.ha | 24++++++++++++++++++++++++
Mtests/34-declarations.ha | 36++++++++++++++++++++++++++++++++++--
Mtests/35-floats.ha | 6++++++
21 files changed, 171 insertions(+), 54 deletions(-)

diff --git a/include/expr.h b/include/expr.h @@ -265,6 +265,7 @@ struct expression_match { }; enum measure_operator { + M_ALIGN, M_LEN, M_SIZE, M_OFFSET, diff --git a/include/lex.h b/include/lex.h @@ -19,6 +19,7 @@ enum lexical_token { T_ATTR_THREADLOCAL, T_UNDERSCORE, T_ABORT, + T_ALIGN, T_ALLOC, T_APPEND, T_AS, diff --git a/src/check.c b/src/check.c @@ -2236,6 +2236,20 @@ check_expr_measure(struct context *ctx, return; } break; + case M_ALIGN: + expr->measure.dimensions = type_store_lookup_dimensions( + ctx->store, aexpr->measure.type); + if (expr->measure.dimensions.align == ALIGN_UNDEFINED) { + error(ctx, aexpr->measure.value->loc, expr, + "Cannot take alignment of a type with undefined alignment"); + return; + } + if (expr->measure.dimensions.size == 0) { + error(ctx, aexpr->measure.value->loc, expr, + "Cannot take alignment of a type of size 0"); + return; + } + break; case M_SIZE: expr->measure.dimensions = type_store_lookup_dimensions( ctx->store, aexpr->measure.type); diff --git a/src/eval.c b/src/eval.c @@ -712,6 +712,9 @@ eval_measurement(struct context *ctx, struct expression *in, struct expression * } out->constant.uval = len; return EVAL_OK; + case M_ALIGN: + out->constant.uval = in->measure.dimensions.align; + return EVAL_OK; case M_SIZE: out->constant.uval = in->measure.dimensions.size; return EVAL_OK; diff --git a/src/gen.c b/src/gen.c @@ -2779,6 +2779,12 @@ gen_expr_measure(struct gen_context *ctx, const struct expression *expr) abort(); // Invariant } break; + case M_ALIGN: + return (struct gen_value){ + .kind = GV_CONST, + .type = &builtin_type_size, + .lval = expr->measure.dimensions.align, + }; case M_SIZE: return (struct gen_value){ .kind = GV_CONST, diff --git a/src/lex.c b/src/lex.c @@ -26,6 +26,7 @@ static const char *tokens[] = { [T_ATTR_THREADLOCAL] = "@threadlocal", [T_UNDERSCORE] = "_", [T_ABORT] = "abort", + [T_ALIGN] = "align", [T_ALLOC] = "alloc", [T_APPEND] = "append", [T_AS] = "as", diff --git a/src/parse.c b/src/parse.c @@ -1153,6 +1153,10 @@ parse_measurement_expression(struct lexer *lexer) want(lexer, T_LPAREN, NULL); switch (tok.token) { + case T_ALIGN: + exp->measure.op = M_ALIGN; + exp->measure.type = parse_type(lexer); + break; case T_SIZE: exp->measure.op = M_SIZE; exp->measure.type = parse_type(lexer); @@ -1561,6 +1565,7 @@ parse_builtin_expression(struct lexer *lexer) case T_ASSERT: unlex(lexer, &tok); return parse_assertion_expression(lexer, false); + case T_ALIGN: case T_SIZE: case T_LEN: case T_OFFSET: diff --git a/tests/01-arrays.ha b/tests/01-arrays.ha @@ -32,6 +32,19 @@ fn measurements() void = { assert(size([3]int) == size(int) * 3); static assert(len([1, 2, 3]) == 3); + + assert(align([_]i8) == 1); + assert(align([_]i16) == 2); + assert(align([_]i32) == 4); + assert(align([_]i64) == 8); + assert(align([*]i8) == 1); + assert(align([*]i16) == 2); + assert(align([*]i32) == 4); + assert(align([*]i64) == 8); + assert(align([2]i8) == 1); + assert(align([2]i16) == 2); + assert(align([2]i32) == 4); + assert(align([2]i64) == 8); }; fn storage() void = { @@ -42,13 +55,6 @@ fn storage() void = { assert(*((y + (size(int) * 2): uintptr): *int) == 3); }; -fn alignment() void = { - let x: [_]i32 = [1, 2, 3]; - assert(&x: uintptr: size % 4 == 0); - let y: [_]i64 = [1, 2, 3]; - assert(&y: uintptr: size % 8 == 0); -}; - fn assignment() void = { let x = [1, 2, 3]; let y = x; @@ -152,7 +158,6 @@ export fn main() void = { indexing(); measurements(); storage(); - alignment(); assignment(); param([1, 2, 3]); nested(); diff --git a/tests/02-integers.ha b/tests/02-integers.ha @@ -1,43 +1,23 @@ -fn sizes() void = { +fn dimensions() void = { // Fixed precision - assert(size(i8) == 1); - assert(size(i16) == 2); - assert(size(i32) == 4); - assert(size(i64) == 8); - assert(size(u8) == 1); - assert(size(u16) == 2); - assert(size(u32) == 4); - assert(size(u64) == 8); + assert(size(i8) == 1 && align(i8) == 1 && size(i8) % align(i8) == 0); + assert(size(i16) == 2 && align(i16) == 2 && size(i16) % align(i16) == 0); + assert(size(i32) == 4 && align(i32) == 4 && size(i32) % align(i32) == 0); + assert(size(i64) == 8 && align(i64) == 8 && size(i64) % align(i64) == 0); + assert(size(u8) == 1 && align(u8) == 1 && size(u8) % align(u8) == 0); + assert(size(u16) == 2 && align(u16) == 2 && size(u16) % align(u16) == 0); + assert(size(u32) == 4 && align(u32) == 4 && size(u32) % align(u32) == 0); + assert(size(u64) == 8 && align(u64) == 8 && size(u64) % align(u64) == 0); // Implementation-defined (test meets spec limits) - assert(size(int) >= 4); - assert(size(uint) >= 4); + assert(size(int) >= 4 && align(int) >= 4 && size(int) % align(int) == 0); + assert(size(uint) >= 4 && align(uint) >= 4 && size(uint) % align(uint) == 0); + assert(size(size) % align(size) == 0); + assert(size(uintptr) % align(uintptr) == 0); // etc - assert(size(char) == 1); -}; - -fn alignment() void = { - // Fixed alignment - let _i8 = 0i8, _u8 = 0u8; - assert(&_i8: uintptr: size % 1 == 0); - assert(&_u8: uintptr: size % 1 == 0); - let _i16 = 0i16, _u16 = 0u16; - assert(&_i16: uintptr: size % 2 == 0); - assert(&_u16: uintptr: size % 2 == 0); - let _i32 = 0i32, _u32 = 0u32; - assert(&_i32: uintptr: size % 4 == 0); - assert(&_u32: uintptr: size % 4 == 0); - let _i64 = 0i64, _u64 = 0u64; - assert(&_i64: uintptr: size % 8 == 0); - assert(&_u64: uintptr: size % 8 == 0); - // Implementation-defined alignment - let i = 0i, u = 0u, z = 0z, uptr = &i: uintptr; - assert(&i: uintptr: size % size(int) == 0); - assert(&u: uintptr: size % size(uint) == 0); - assert(&z: uintptr: size % size(size) == 0); - assert(&uptr: uintptr: size % size(uintptr) == 0); + assert(size(char) == 1 && align(char) == 1 && size(char) % align(char) == 0); + assert(size(rune) == 4 && align(rune) == 4 && size(rune) % align(rune) == 0); }; export fn main() void = { - sizes(); - alignment(); + dimensions(); }; diff --git a/tests/03-pointers.ha b/tests/03-pointers.ha @@ -8,6 +8,9 @@ fn basics() void = { assert(*y == 42); *y = 1337; assert(x == 1337); + + assert(size(*int) == size(uintptr)); + assert(align(*int) == align(uintptr)); }; fn _nullable() void = { diff --git a/tests/04-strings.ha b/tests/04-strings.ha @@ -5,10 +5,10 @@ fn measurements() void = { assert(len("Hello!\0") == 7); assert(len("He\0llo!") == 7); assert(size(str) == size(*u8) + size(size) * 2); - const align: size = + const alignment: size = if (size(*u8) > size(size)) size(*u8) else size(size); - assert(&x: uintptr: size % align == 0); + assert(align(str) % alignment == 0); static assert(len("Hello!") == 6); }; diff --git a/tests/06-structs.ha b/tests/06-structs.ha @@ -4,6 +4,9 @@ fn padding() void = { assert(size(struct { x: i32, y: i32 }) == 8); assert(size(struct { x: i32, y: i64 }) == 16); assert(size(union { x: i8, y: i16, z: i32 }) == 4); + assert(align(struct { x: i32, y: i32 }) == 4); + assert(align(struct { x: i32, y: i64 }) == 8); + assert(align(union { x: i8, y: i16, z: i32 }) == 4); const s = struct { x: i8 = 10, diff --git a/tests/07-aliases.ha b/tests/07-aliases.ha @@ -104,6 +104,24 @@ fn recursive() void = { assert(x.self == x.self.self); }; +fn measurement() void = { + assert(size(my_int) == size(int) && size(my_my_int) == size(int)); + assert(size(my_array) == size([3]int)); + assert(size(my_array_ptr) == size(*[3]int)); + assert(size(my_slice) == size([]int)); + assert(size(my_struct) == size(struct { x: int, y: int })); + assert(size(my_struct_ptr) == size(*struct { x: int, y: int })); + assert(size(my_tagged) == size((int | void))); + + assert(align(my_int) == align(int) && align(my_my_int) == align(int)); + assert(align(my_array) == align([3]int)); + assert(align(my_array_ptr) == align(*[3]int)); + assert(align(my_slice) == align([]int)); + assert(align(my_struct) == align(struct { x: int, y: int })); + assert(align(my_struct_ptr) == align(*struct { x: int, y: int })); + assert(align(my_tagged) == align((int | void))); +}; + export fn main() void = { alias_builtin(); alias_array(); @@ -112,4 +130,5 @@ export fn main() void = { alias_tagged(); alias_alias(); recursive(); + measurement(); }; diff --git a/tests/08-slices.ha b/tests/08-slices.ha @@ -51,8 +51,10 @@ fn casting() void = { fn measurements() void = { let x: []int = [1, 2, 3, 4, 5]; assert(size([]int) == size(*[*]int) + size(size) * 2); + assert(align([]int) == (if (align(*[*]int) > align(size)) align(*[*]int) + else align(size))); assert(len(x) == 5); - assert(&x: uintptr: size % size(int) == 0); + assert(&x[0]: uintptr: size % size(int) == 0); }; fn indexing() void = { diff --git a/tests/09-funcs.ha b/tests/09-funcs.ha @@ -77,6 +77,9 @@ fn reject() void = { "fn f(x: [*]int) void = void;", // return value of undefined size "fn f(x: int) [*]int = void;", + + "let x: size = size(fn(x: int) int);", + "let x: size = align(fn(x: int) int);", ]; for (let i = 0z; i < len(failures); i += 1) { diff --git a/tests/13-tagged.ha b/tests/13-tagged.ha @@ -2,16 +2,17 @@ use rt::{compile, exited, EXIT_SUCCESS}; fn measurements() void = { const x: (u8 | u16 | u32 | u64) = 1337u16; // With padding - const align: size = + const alignment: size = if (size(u64) < size(uint)) size(uint) else size(u64); - assert(size((u8 | u16 | u32 | u64)) == align * 2); + assert(align((u8 | u16 | u32 | u64)) == alignment); + assert(size((u8 | u16 | u32 | u64)) == alignment * 2); assert(&x: uintptr: size % size(uint) == 0); assert(&x: uintptr: size % size(u64) == 0); const y: (u8 | u16) = 1337u16; // No padding - assert(&y: uintptr: size % size(uint) == 0); - assert(&y: uintptr: size % size(u16) == 0); + assert(align((u8 | u16)) == align(uint)); + assert(align(((u8 | u16) | (i8 | i16))) == align(uint)); }; fn storage() void = { diff --git a/tests/15-enums.ha b/tests/15-enums.ha @@ -55,7 +55,12 @@ let global_uintptr: uintptr_storage = uintptr_storage::FOO; fn storage() void = { assert(size(explicit_values) == size(int)); - assert(size(with_storage) == 2); + assert(size(with_storage) == size(u16)); + assert(size(uintptr_storage) == size(uintptr)); + assert(align(explicit_values) == align(int)); + assert(align(with_storage) == align(u16)); + assert(align(uintptr_storage) == align(uintptr)); + const val = 0xBEEFu16; const is_little = (&val: *[2]u8)[0] == 0xEF; assert(with_storage::CAFE: u8 == (if (is_little) 0xFEu8 else 0xCAu8)); diff --git a/tests/21-tuples.ha b/tests/21-tuples.ha @@ -5,6 +5,9 @@ def CONST: (int, str) = (15, "foo"); fn storage() void = { let x: (int, size) = (42, 1337); assert(size((int, size)) == size(size) * 2); + assert(size((int, (u8, size))) == size(size) * 3); + assert(align((int, size)) == align(size)); + assert(align((int, (u8, size))) == align(size)); let ptr = &x: *struct { i: int, z: size }; assert(ptr.i == 42 && ptr.z == 1337); }; diff --git a/tests/23-errors.ha b/tests/23-errors.ha @@ -68,9 +68,33 @@ fn void_assignability() void = { `) as exited != EXIT_SUCCESS); // non-void types cannot be assigned to void }; +fn measurements() void = { + assert(size(!int) == size(int)); + assert(size(!f64) == size(f64)); + assert(size(!(int | void)) == size((int | void))); + assert(size(!(i8, rune)) == size((i8, rune))); + assert(size(!struct { x: int, y: str }) == size(struct { x: int, y: str })); + assert(size(!union { x: int, y: str }) == size(union { x: int, y: str })); + assert(size(![2]int) == size([2]int)); + assert(size(![]int) == size([]int)); + assert(size(!*size) == size(*size)); + + assert(align(!int) == align(int)); + assert(align(!f64) == align(f64)); + assert(align(!(int | void)) == align((int | void))); + assert(align(!(i8, rune)) == align((i8, rune))); + assert(align(!struct { x: int, y: str }) == align(struct { x: int, y: str })); + assert(align(!union { x: int, y: str }) == align(union { x: int, y: str })); + assert(align(![2]int) == align([2]int)); + assert(align(![*]int) == align([*]int)); + assert(align(![]int) == align([]int)); + assert(align(!*size) == align(*size)); +}; + export fn main() void = { assignability(); propagate(); cannotignore(); void_assignability(); + measurements(); }; diff --git a/tests/34-declarations.ha b/tests/34-declarations.ha @@ -162,13 +162,19 @@ type mut_ptuple_A1 = *(mut_ptuple_A2, u8), mut_ptuple_A2 = (mut_ptuple_A1, u8); type mut_ptuple_B2 = (mut_ptuple_B1, u8), mut_ptuple_B1 = *(mut_ptuple_B2, u8); // reverse // type with a type dimension dependency -type arri8_A = [size(arrintptr)]u8, arrintptr = [8]*int; -type arrintptr_B = [8]*int, arri8_B = [size(arrintptr)]u8; // reverse +type arri8_A = [size(arrintptr_A)]u8, arrintptr_A = [8]*int; +type arrintptr_B = [8]*int, arri8_B = [size(arrintptr_B)]u8; // reverse + +type arri8_al_A = [align(arrintptr_al_A)]u8, arrintptr_al_A = [8]*int; +type arrintptr_al_B = [8]*int, arri8_al_B = [align(arrintptr_al_B)]u8; // reverse // mutually recursive types with a dimension dependency type arru8_A = [size(arru8ptr_A)]u8, arru8ptr_A = [8]*arru8_A; type arru8ptr_B = [8]*arru8_B, arru8_B = [size(arru8ptr_B)]u8; // reverse +type arru8_al_A = [align(arru8ptr_al_A)]u8, arru8ptr_al_A = [8]*arru8_al_A; +type arru8ptr_al_B = [8]*arru8_al_B, arru8_al_B = [align(arru8ptr_al_B)]u8; // reverse + // unwrapped aliases to tagged unions type unwrap_A1 = ([32]u8 | void | str), unwrap_A2 = (i64 | ...unwrap_A1), @@ -182,6 +188,7 @@ type unwrap_alias_B3 = ...unwrap_alias_B2, // reverse unwrap_B1 = ([32]u8 | void | str); fn sz() void = { + // size static assert(size(mut_struct_A3) == 2 * size(*void)); static assert(size(mut_struct_B3) == 2 * size(*void)); @@ -205,6 +212,31 @@ fn sz() void = { static assert(size(unwrap_A1) == size(unwrap_alias_A3)); static assert(size(unwrap_B1) == size(unwrap_alias_B3)); + + //align + static assert(align(mut_struct_A3) == align(*void)); + static assert(align(mut_struct_B3) == align(*void)); + + static assert(align(mut_tagged_A1) == align(*void)); + static assert(align(mut_tagged_B1) == align(*void)); + + static assert(align(mut_tagged_A3) == align(*void)); + static assert(align(mut_tagged_B3) == align(*void)); + + static assert(align(mut_tagged_A4) == align(*void)); + static assert(align(mut_tagged_B4) == align(*void)); + + static assert(align(mut_tuple_A1) == align(*void)); + static assert(align(mut_tuple_B1) == align(*void)); + + static assert(align(arru8_A) == align(u8)); + static assert(align(arru8_B) == align(u8)); + + static assert(align(arru8ptr_A) == align(*void)); + static assert(align(arru8ptr_B) == align(*void)); + + static assert(align(unwrap_A1) == align(unwrap_alias_A3)); + static assert(align(unwrap_B1) == align(unwrap_alias_B3)); }; fn reject() void = { diff --git a/tests/35-floats.ha b/tests/35-floats.ha @@ -1,3 +1,8 @@ +fn measurements() void = { + assert(size(f32) == align(f32)); + assert(size(f32) == align(f32)); +}; + fn signed_casts() void = { let a = 20f64; let f = a: f32; @@ -109,6 +114,7 @@ fn unsigned_casts() void = { export fn main() void = { // TODO: test parsing, compile-time evaluation + measurements(); signed_casts(); unsigned_casts(); };