diff --git a/doc/docs.md b/doc/docs.md index 41c7a955f2..26cadbf487 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -112,6 +112,7 @@ For more details and troubleshooting, please visit the [vab GitHub repository](h * [Profiling](#profiling) * [Advanced Topics](#advanced-topics) * [Memory-unsafe code](#memory-unsafe-code) + * [sizeof and __offsetof](#sizeof-and-__offsetof) * [Calling C functions from V](#calling-c-functions-from-v) * [Debugging generated C code](#debugging-generated-c-code) * [Conditional compilation](#conditional-compilation) @@ -2991,6 +2992,22 @@ println(baz) println(qux) ``` +## sizeof and __offsetof + +V supports the usage of `sizeof` to calculate sizes of structs and +`__offsetof` to calculate struct field offsets. + +```v +struct Foo { + a int + b int +} + +println(sizeof(Foo)) +println(__offsetof(Foo, a)) +println(__offsetof(Foo, b)) +``` + ## Calling C functions from V ```v @@ -3734,6 +3751,7 @@ type typeof union unsafe +__offsetof ``` See also [Types](#types). diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index ca500b0afa..be6145939e 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -13,9 +13,9 @@ pub type Expr = AnonFn | ArrayDecompose | ArrayInit | AsCast | Assoc | AtExpr | CTempVar | CallExpr | CastExpr | ChanInit | CharLiteral | Comment | ComptimeCall | ComptimeSelector | ConcatExpr | EnumVal | FloatLiteral | GoExpr | Ident | IfExpr | IfGuardExpr | IndexExpr | InfixExpr | IntegerLiteral | Likely | LockExpr | MapInit | - MatchExpr | None | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | SelectExpr | - SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | - Type | TypeOf | UnsafeExpr + MatchExpr | None | OffsetOf | OrExpr | ParExpr | PostfixExpr | PrefixExpr | RangeExpr | + SelectExpr | SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | + StructInit | Type | TypeOf | UnsafeExpr pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt | @@ -1068,6 +1068,13 @@ pub mut: typ table.Type } +pub struct OffsetOf { +pub: + struct_type table.Type + field string + pos token.Position +} + pub struct Likely { pub: expr Expr @@ -1197,7 +1204,7 @@ pub fn (expr Expr) position() token.Position { } ArrayInit, AsCast, Assoc, AtExpr, BoolLiteral, CallExpr, CastExpr, ChanInit, CharLiteral, ConcatExpr, Comment, EnumVal, FloatLiteral, GoExpr, Ident, IfExpr, IndexExpr, IntegerLiteral, - Likely, LockExpr, MapInit, MatchExpr, None, OrExpr, ParExpr, PostfixExpr, PrefixExpr, + Likely, LockExpr, MapInit, MatchExpr, None, OffsetOf, OrExpr, ParExpr, PostfixExpr, PrefixExpr, RangeExpr, SelectExpr, SelectorExpr, SizeOf, SqlExpr, StringInterLiteral, StringLiteral, StructInit, Type, TypeOf, UnsafeExpr { return expr.pos diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index d8f3d4e618..cbcd726621 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -290,6 +290,9 @@ pub fn (x Expr) str() string { SizeOf { return 'sizeof($x.expr)' } + OffsetOf { + return '__offsetof($x.struct_type, $x.field)' + } StringInterLiteral { mut res := []string{} res << "'" diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index e87f97b4e1..ea0790a804 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -3505,6 +3505,9 @@ pub fn (mut c Checker) expr(node ast.Expr) table.Type { } return table.u32_type } + ast.OffsetOf { + return c.offset_of(node) + } ast.SqlExpr { return c.sql_expr(mut node) } @@ -5058,6 +5061,19 @@ pub fn (mut c Checker) chan_init(mut node ast.ChanInit) table.Type { } } +pub fn (mut c Checker) offset_of(node ast.OffsetOf) table.Type { + sym := c.table.get_final_type_symbol(node.struct_type) + if sym.kind != .struct_ { + c.error('first argument of __offsetof must be struct', node.pos) + return table.u32_type + } + + if !c.table.struct_has_field(node.struct_type, node.field) { + c.error('struct `$sym.name` has no field called `$node.field`', node.pos) + } + return table.u32_type +} + pub fn (mut c Checker) check_dup_keys(node &ast.MapInit, i int) { key_i := node.keys[i] if key_i is ast.StringLiteral { diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 1b3d71def1..9a8fa43315 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -947,6 +947,9 @@ pub fn (mut f Fmt) expr(node ast.Expr) { ast.SizeOf { f.size_of(node) } + ast.OffsetOf { + f.write('__offsetof(${f.table.type_to_str(node.struct_type)}, $node.field)') + } ast.SqlExpr { f.sql_expr(node) } diff --git a/vlib/v/fmt/tests/offset_keep.vv b/vlib/v/fmt/tests/offset_keep.vv new file mode 100644 index 0000000000..df391b9337 --- /dev/null +++ b/vlib/v/fmt/tests/offset_keep.vv @@ -0,0 +1,8 @@ +struct Animal { + breed string + age u64 +} + +fn main() { + println(__offsetof(Animal, breed) + __offsetof(Animal, age)) +} diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index 06e05a8963..40c287bf4b 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -2821,6 +2821,10 @@ fn (mut g Gen) expr(node ast.Expr) { styp := g.typ(node_typ) g.write('/*SizeOf*/ sizeof(${util.no_dots(styp)})') } + ast.OffsetOf { + styp := g.typ(node.struct_type) + g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, $node.field))') + } ast.SqlExpr { g.sql_select_expr(node) } diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 391f75d9bd..7166d3f9ee 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -541,6 +541,9 @@ fn (mut g JsGen) expr(node ast.Expr) { ast.SizeOf { // TODO } + ast.OffsetOf { + // TODO + } ast.SqlExpr { // TODO } diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index 2df9ad22f4..f3d2f57935 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -204,6 +204,25 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { pos: spos.extend(p.tok.position()) } } + .key_offsetof { + pos := p.tok.position() + p.next() // __offsetof + p.check(.lpar) + st := p.parse_type() + p.check(.comma) + if p.tok.kind != .name { + p.error_with_pos('unexpected `$p.tok.lit`, expecting struct field', p.tok.position()) + return ast.Expr{} + } + field := p.tok.lit + p.next() + p.check(.rpar) + node = ast.OffsetOf{ + struct_type: st + field: field + pos: pos + } + } .key_likely, .key_unlikely { is_likely := p.tok.kind == .key_likely p.next() diff --git a/vlib/v/tests/offsetof_test.v b/vlib/v/tests/offsetof_test.v new file mode 100644 index 0000000000..ac90bcda59 --- /dev/null +++ b/vlib/v/tests/offsetof_test.v @@ -0,0 +1,35 @@ +import math.complex + +struct Cat { + name string + breed string + age int +} + +type Feline = Cat + +fn test_offsetof() { + cat := Cat{name: 'Cthulhu' breed: 'Great Old One' age: 2147483647} + unsafe { + assert *(&string(byteptr(&cat) + __offsetof(Cat, name))) == 'Cthulhu' + assert *(&string(byteptr(&cat) + __offsetof(Cat, breed))) == 'Great Old One' + assert *(&int(byteptr(&cat) + __offsetof(Cat, age))) == 2147483647 + } +} + +fn test_offsetof_struct_from_another_module() { + num := complex.Complex{1.0, 1.0} + unsafe { + assert *(&f64(byteptr(&num) + __offsetof(complex.Complex, re))) == 1.0 + assert *(&f64(byteptr(&num) + __offsetof(complex.Complex, im))) == 1.0 + } +} + +fn test_offsetof_alias() { + fel := Feline{name: 'Cthulhu' breed: 'Great Old One' age: 2147483647} + unsafe { + assert *(&string(byteptr(&fel) + __offsetof(Feline, name))) == 'Cthulhu' + assert *(&string(byteptr(&fel) + __offsetof(Feline, breed))) == 'Great Old One' + assert *(&int(byteptr(&fel) + __offsetof(Feline, age))) == 2147483647 + } +}