diff --git a/doc/docs.md b/doc/docs.md index f0bb2ae461..cea192fdef 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -193,7 +193,7 @@ by using any of the following commands in a terminal: * [Trace](#trace) * [Memory-unsafe code](#memory-unsafe-code) * [Structs with reference fields](#structs-with-reference-fields) - * [sizeof and __offsetof](#sizeof-and-__offsetof) + * [sizeof, alignof and __offsetof](#sizeof-alignof-and-__offsetof) * [Limited operator overloading](#limited-operator-overloading) * [Performance tuning](#performance-tuning) * [Atomics](#atomics) @@ -6980,9 +6980,10 @@ println(baz) println(qux) ``` -## sizeof and __offsetof +## sizeof, alignof and __offsetof * `sizeof(Type)` gives the size of a type in bytes. +* `alignof(Type)` gives the alignment of a type in bytes. * `__offsetof(Struct, field_name)` gives the offset in bytes of a struct field. ```v @@ -6992,6 +6993,7 @@ struct Foo { } assert sizeof(Foo) == 8 +assert alignof[Foo]() == 4 assert __offsetof(Foo, a) == 0 assert __offsetof(Foo, b) == 4 ``` diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index b215349290..912bd35e1a 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -33,6 +33,7 @@ pub const int_type_name = $if new_int ? { } pub type Expr = NodeError + | AlignOf | AnonFn | ArrayDecompose | ArrayInit @@ -1956,6 +1957,16 @@ pub mut: scope &Scope = unsafe { nil } } +pub struct AlignOf { +pub: + guessed_type bool // a legacy `alignof( GuessedType )` => a deprecation notice, suggesting `v fmt -w .` => `alignof[ Type ]()` + is_type bool + pos token.Pos +pub mut: + expr Expr // checker uses this to set typ, when !is_type + typ Type +} + pub struct SizeOf { pub: guessed_type bool // a legacy `sizeof( GuessedType )` => a deprecation notice, suggesting `v fmt -w .` => `sizeof[ Type ]()` @@ -2275,11 +2286,11 @@ pub fn (expr Expr) pos() token.Pos { // println('compiler bug, unhandled EmptyExpr pos()') token.Pos{} } - NodeError, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, - CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, ComptimeCall, ComptimeSelector, - EnumVal, DumpExpr, FloatLiteral, GoExpr, SpawnExpr, Ident, IfExpr, IntegerLiteral, - IsRefType, Likely, LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, - PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, + NodeError, AlignOf, ArrayDecompose, ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, + CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, ComptimeCall, + ComptimeSelector, EnumVal, DumpExpr, FloatLiteral, GoExpr, SpawnExpr, Ident, IfExpr, + IntegerLiteral, IsRefType, Likely, LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, + ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, TypeNode, TypeOf, UnsafeExpr, ComptimeType, LambdaExpr, Nil { expr.pos @@ -2817,7 +2828,7 @@ pub fn (expr Expr) is_literal() bool { !expr.has_arg && expr.expr.is_literal() && (expr.typ.is_any_kind_of_pointer() || expr.typ in [i8_type, i16_type, int_type, i64_type, u8_type, u16_type, u32_type, u64_type, f32_type, f64_type, char_type, bool_type, rune_type]) } - SizeOf, IsRefType { + AlignOf, SizeOf, IsRefType { expr.is_type || expr.expr.is_literal() } else { diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index 43c98b0954..9f0c7d4d3e 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -413,6 +413,12 @@ pub fn (x Expr) str() string { stdatomic.sub_i64(&nested_expr_str_calls, 1) } match x { + AlignOf { + if x.is_type { + return 'alignof(${global_table.type_to_str(x.typ)})' + } + return 'alignof(${x.expr.str()})' + } AnonFn { return 'anon_fn' } diff --git a/vlib/v/ast/types.v b/vlib/v/ast/types.v index 31dd3896aa..04caa2b728 100644 --- a/vlib/v/ast/types.v +++ b/vlib/v/ast/types.v @@ -1310,6 +1310,13 @@ pub fn (t &Table) type_size(typ Type) (int, int) { return size, align } +// type_align returns the alignment (in bytes) of `typ`, just like C's alignof(). +pub fn (t &Table) type_align(typ Type) int { + // we only care about the second (alignment) return value + _, align := t.type_size(typ) + return align +} + // round_up rounds the number `n` up to the next multiple `multiple`. // Note: `multiple` must be a power of 2. @[inline] diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 79b6a2cdd9..daa2149ed6 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3288,7 +3288,7 @@ pub fn (mut c Checker) expr(mut node ast.Expr) ast.Type { } return ret_type } - ast.SizeOf { + ast.AlignOf, ast.SizeOf { if !node.is_type { node.typ = c.expr(mut node.expr) } diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index e8d99be0df..57029145c7 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -435,6 +435,10 @@ fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.Comp return val } } + ast.AlignOf { + a := c.table.type_align(expr.typ) + return i64(a) + } ast.SizeOf { s, _ := c.table.type_size(c.unwrap_generic(expr.typ)) return i64(s) diff --git a/vlib/v/eval/expr.c.v b/vlib/v/eval/expr.c.v index 0d124b08e8..f6f0243a69 100644 --- a/vlib/v/eval/expr.c.v +++ b/vlib/v/eval/expr.c.v @@ -491,6 +491,9 @@ pub fn (mut e Eval) expr(expr ast.Expr, expecting ast.Type) Object { // eprintln('unhandled struct init at line $expr.pos.line_nr') return '' } + ast.AlignOf { + return Uint{e.type_to_align(expr.typ), 64} + } ast.SizeOf { return Uint{e.type_to_size(expr.typ), 64} } @@ -635,6 +638,12 @@ fn (e &Eval) type_to_size(typ ast.Type) u64 { } } +// type_to_align returns the natural alignment (in bits) of `typ`. +fn (e &Eval) type_to_align(typ ast.Type) u64 { + // on most ABIs the alignment of a type == its size + return e.type_to_size(typ) +} + fn (e &Eval) get_escape(r rune) rune { res := match r { `\\` { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 03b530141e..0204da80a2 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -572,6 +572,9 @@ pub fn (mut f Fmt) expr(node_ ast.Expr) { match mut node { ast.NodeError {} ast.EmptyExpr {} + ast.AlignOf { + f.align_of(node) + } ast.AnonFn { f.anon_fn(node) } @@ -2996,6 +2999,20 @@ pub fn (mut f Fmt) selector_expr(node ast.SelectorExpr) { f.or_expr(node.or_block) } +pub fn (mut f Fmt) align_of(node ast.AlignOf) { + f.write('alignof') + if node.is_type { + f.write('[') + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + f.write(']()') + return + } else { + f.write('(') + f.expr(node.expr) + f.write(')') + } +} + pub fn (mut f Fmt) size_of(node ast.SizeOf) { f.write('sizeof') if node.is_type && !node.guessed_type { diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index e3931558a3..db9cd97d94 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -3624,6 +3624,9 @@ fn (mut g Gen) expr(node_ ast.Expr) { ast.EmptyExpr { g.error('g.expr(): unhandled EmptyExpr', token.Pos{}) } + ast.AlignOf { + g.align_of(node) + } ast.AnonFn { g.gen_anon_fn(mut node) } @@ -7738,6 +7741,17 @@ fn (mut g Gen) get_type(typ ast.Type) ast.Type { return if typ == g.field_data_type { g.comptime.comptime_for_field_value.typ } else { typ } } +fn (mut g Gen) align_of(node ast.AlignOf) { + typ := g.type_resolver.typeof_type(node.expr, g.get_type(node.typ)) + node_typ := g.unwrap_generic(typ) + sym := g.table.sym(node_typ) + if sym.language == .v && sym.kind in [.placeholder, .any] { + g.error('unknown type `${sym.name}`', node.pos) + } + styp := g.styp(node_typ) + g.write('alignof(${util.no_dots(styp)})') +} + fn (mut g Gen) size_of(node ast.SizeOf) { typ := g.type_resolver.typeof_type(node.expr, g.get_type(node.typ)) node_typ := g.unwrap_generic(typ) diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index 2ca85e0781..64420c21fd 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -339,6 +339,7 @@ typedef int (*qsort_callback_func)(const void*, const void*); #include #include // for va_list +#include // for alignof #ifdef __TERMUX__ #if defined __BIONIC_AVAILABILITY_GUARD && __BIONIC_AVAILABILITY_GUARD(28) diff --git a/vlib/v/gen/golang/golang.v b/vlib/v/gen/golang/golang.v index 2573b04ece..1723da3527 100644 --- a/vlib/v/gen/golang/golang.v +++ b/vlib/v/gen/golang/golang.v @@ -633,6 +633,9 @@ pub fn (mut f Gen) expr(node_ ast.Expr) { ast.SelectorExpr { f.selector_expr(node) } + ast.AlignOf { + f.align_of(node) + } ast.SizeOf { f.size_of(node) } @@ -2147,6 +2150,16 @@ pub fn (mut f Gen) selector_expr(node ast.SelectorExpr) { f.write(node.field_name) } +pub fn (mut f Gen) align_of(node ast.AlignOf) { + f.write('alignof(') + if node.is_type { + f.write(f.table.type_to_str_using_aliases(node.typ, f.mod2alias)) + } else { + f.expr(node.expr) + } + f.write(')') +} + pub fn (mut f Gen) size_of(node ast.SizeOf) { f.write('sizeof(') if node.is_type { diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index e9d358ffd3..315973d865 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1032,7 +1032,7 @@ fn (mut g JsGen) expr(node_ ast.Expr) { ast.SelectorExpr { g.gen_selector_expr(node) } - ast.SizeOf, ast.IsRefType { + ast.AlignOf, ast.SizeOf, ast.IsRefType { // TODO } ast.OffsetOf { diff --git a/vlib/v/markused/walker.v b/vlib/v/markused/walker.v index 4dfc9b69bb..e7fc1e1ea1 100644 --- a/vlib/v/markused/walker.v +++ b/vlib/v/markused/walker.v @@ -782,7 +782,7 @@ fn (mut w Walker) expr(node_ ast.Expr) { w.expr(node.high) } } - ast.SizeOf, ast.IsRefType { + ast.AlignOf, ast.SizeOf, ast.IsRefType { w.expr(node.expr) w.mark_by_type(node.typ) } diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index d6bb02c015..d06698f790 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -332,8 +332,9 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { } } } - .key_sizeof, .key_isreftype { + .key_alignof, .key_sizeof, .key_isreftype { is_reftype := p.tok.kind == .key_isreftype + is_alignof := p.tok.kind == .key_alignof p.next() // sizeof if p.tok.kind == .lsbr { @@ -351,6 +352,12 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { typ: typ pos: type_pos } + } else if is_alignof { + node = ast.AlignOf{ + is_type: true + typ: typ + pos: type_pos + } } else { node = ast.SizeOf{ is_type: true @@ -381,6 +388,12 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { expr: expr pos: pos } + } else if is_alignof { + node = ast.AlignOf{ + is_type: false + expr: expr + pos: pos + } } else { node = ast.SizeOf{ is_type: false @@ -403,6 +416,13 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { typ: arg_type pos: pos } + } else if is_alignof { + node = ast.AlignOf{ + guessed_type: true + is_type: true + typ: arg_type + pos: pos + } } else { node = ast.SizeOf{ guessed_type: true diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 24c7fda1fc..19746ea6c6 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -2670,7 +2670,7 @@ fn (mut p Parser) global_decl() ast.GlobalDecl { ast.FloatLiteral { typ = ast.f64_type } - ast.IntegerLiteral, ast.SizeOf { + ast.AlignOf, ast.IntegerLiteral, ast.SizeOf { typ = ast.int_type } ast.StringLiteral, ast.StringInterLiteral { diff --git a/vlib/v/tests/alignof_test.v b/vlib/v/tests/alignof_test.v new file mode 100644 index 0000000000..6666679def --- /dev/null +++ b/vlib/v/tests/alignof_test.v @@ -0,0 +1,23 @@ +struct S1 { + p voidptr +} + +struct S2 { + i int +} + +fn test_alignof() { + assert alignof[rune]() == 4 + assert alignof[[44]u8]() == 1 + assert alignof(`€`) == 4 + // depends on -m32/64 + assert alignof[S1]() in [u32(4), 8] + s := S2{} + assert alignof(s.i) == 4 + + assert alignof('hello') == $if x64 { + 8 + } $else { + 4 + } +} diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index f8fcad1c47..36ca092f9d 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -121,6 +121,7 @@ pub enum Kind { key_select key_like key_ilike + key_alignof key_sizeof key_isreftype key_likely @@ -305,6 +306,7 @@ fn build_token_str() []string { s[Kind.key_asm] = 'asm' s[Kind.key_return] = 'return' s[Kind.key_module] = 'module' + s[Kind.key_alignof] = 'alignof' s[Kind.key_sizeof] = 'sizeof' s[Kind.key_isreftype] = 'isreftype' s[Kind.key_likely] = '_likely_' @@ -657,6 +659,7 @@ pub fn kind_to_string(k Kind) string { .key_select { 'key_select' } .key_like { 'key_like' } .key_ilike { 'key_ilike' } + .key_alignof { 'key_alignof' } .key_sizeof { 'key_sizeof' } .key_isreftype { 'key_isreftype' } .key_likely { 'key_likely' }