# Runtime integer sizes?

**URL:** <https://ziggit.dev/t/runtime-integer-sizes/7456>\
**Category:** Help\
**Tags:** language, standard-library, operating-system\
**Created:** [December 21, 2024, 6:40pm UTC](https://ziggit.dev/t/runtime-integer-sizes/7456 "2024-12-21T18:40:34Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![samuel-fiedler](https://ziggit.dev/user_avatar/ziggit.dev/samuel-fiedler/32/2801_2.png) [@samuel-fiedler](https://ziggit.dev/u/samuel-fiedler)\
**Post date:** [December 21, 2024, 6:40pm UTC](https://ziggit.dev/t/runtime-integer-sizes/7456/1 "2024-12-21T18:40:34Z")

</div>

I have been working on devicetree stuff for my kernel recently.  
Currently I am trying to handle the properties of a devicetree memory node.  
For anyone that wants to know more about that, feel free to take a look at my [devicetree branch](https://codeberg.org/loup-os/kernel/src/branch/devicetree) and at the [devicetree spec](https://www.devicetree.org/specifications).

So, there is the `reg` property which is an array consisting of `(address, length)` pairs. `address` and `length` for each item are big-endian integers that have a size specified in the `address-cells` and `size-cells` properties (they specify how many `u32` cells one address or size/length property takes).  
The problem is that those sizes are runtime-known and, as the sizes are a 32-bit unsigned integer, the properties can take very much space (in theory).  
As I am discovering the available memory using the devicetree, I can’t use an allocator safely.

I thought of two approaches to handle this problem:

1. Use an union for integer types and a function that returns matching tags for that union
2. Just reading until a normal `usize` is full (because an address with more bits than `usize` would be unnecessary)

The problem with (1) would be that the union could get very long (a zig integer has up to 65536 bit length possibilities as seen in the [standard library](https://ziglang.org/documentation/master/std/#std.meta.Int) and we can only use each 32nd length, so there are 65536/32=2048 different tags for the enum behind the union).  
The problem with (2) would be that it isn’t fully conformant.

Any further approaches / ideas about my approaches?

---

<div class="post-metadata">

**Author:** ![T0bee](https://ziggit.dev/user_avatar/ziggit.dev/t0bee/32/2037_2.png) [@T0bee](https://ziggit.dev/u/T0bee)\
**Post date:** [December 22, 2024, 12:59pm UTC](https://ziggit.dev/t/runtime-integer-sizes/7456/2 "2024-12-22T12:59:47Z")

</div>

I’m not familiar with kernels or the devicetree spec, but from a glance at the docs I’d go with a pair of slices that can be converted to fixed size integers when the context demands it:

```zig
const std = @import("std");

/// Convert literal to big endian
fn be(T: type, n: comptime_int) T {
    return switch (@import("builtin").cpu.arch.endian()) {
        .big => @as(T, n),
        .little => @byteSwap(@as(T, n)),
    };
}

const Resource = struct {
    address: []const u32,
    length: []const u32,

    /// Read a Resource pair from a buffer
    pub fn init(address_cells: u32, size_cells: u32, buf: []const u32) Resource {
        std.debug.assert(buf.len == address_cells + size_cells);
        return .{
            .address = buf[0..address_cells],
            .length = buf[address_cells .. address_cells + size_cells],
        };
    }

    /// Convert a slice to a fixed size integer
    pub fn asInt(T: type, buf: []const u32) T {
        std.debug.assert(buf.len * 4 == @sizeOf(T));
        return std.mem.readInt(T, @ptrCast(buf.ptr), .big);
    }
};

pub fn main() void {
    // 32-bit address, 32-bit size
    {
        const address_cells = 1;
        const size_cells = 1;
        const resource_cells = address_cells + size_cells;

        // < (0x3000 0x20) (0xFE00 0x100) >
        const reg = [4]u32{ be(u32, 0x3000), be(u32, 0x20), be(u32, 0xFE00), be(u32, 0x100) };
        std.debug.assert(reg.len % resource_cells == 0);

        var cursor: usize = 0;
        while (cursor < reg.len) : (cursor += resource_cells) {
            const res = Resource.init(address_cells, size_cells, reg[cursor .. cursor + resource_cells]);
            std.debug.print("(0x{x} 0x{x})\n", .{ Resource.asInt(u32, res.address), Resource.asInt(u32, res.length) });
        }
    }

    // 64-bit address, 32-bit size
    {
        const address_cells = 2;
        const size_cells = 1;
        const resource_cells = address_cells + size_cells;

        // < (0x0 0x3000 0x20) (0x0 0xFE00 0x100) >
        const reg = @as([2]u32, @bitCast([1]u64{be(u64, 0x3000)})) ++ [1]u32{be(u32, 0x20)} ++ @as([2]u32, @bitCast([1]u64{be(u64, 0xFE00)})) ++ [1]u32{be(u32, 0x100)};
        std.debug.assert(reg.len % resource_cells == 0);

        var cursor: usize = 0;
        while (cursor < reg.len) : (cursor += resource_cells) {
            const res = Resource.init(address_cells, size_cells, reg[cursor .. cursor + resource_cells]);
            std.debug.print("(0x{x} 0x{x})\n", .{ Resource.asInt(u64, res.address), Resource.asInt(u32, res.length) });
        }
    }
}

```

Slices also allow you to compare dynamic sized addresses or lengths without generating code paths for every possible integer size.

For example, in this [example](https://elinux.org/Device_Tree_Usage#Ranges_.28Address_Translation.29), if you wanted to look up the `region` mapping of the slice `[1, 0]` you could just use `std.mem.eq` instead of reading every region child address to a fixed size `u64`

---

<div class="post-metadata">

**Author:** ![samuel-fiedler](https://ziggit.dev/user_avatar/ziggit.dev/samuel-fiedler/32/2801_2.png) [@samuel-fiedler](https://ziggit.dev/u/samuel-fiedler)\
**Post date:** [December 22, 2024, 7:27pm UTC](https://ziggit.dev/t/runtime-integer-sizes/7456/3 "2024-12-22T19:27:12Z")

</div>

I had a similar idea, but we have the `asInt` function here which takes the type argument (you used `Resource.asInt(u64)` and `Resource.asInt(u32)`.  
Those comptime things are my main problem.

I guess I’ll just do something like this (untested!):

```zig
const std = @import("std");

const Address = union(enum) {
    cells1: u32,
    cells2: u64,
};

fn some_fn() void {
    const some_runtime_dependent_size = 1;
    const data: [8]u8 = .{1, 0, 0, 0, 1, 0, 0, 0};
    if (some_runtime_dependent_size == 1) {
        _ = Address{
            .cells1 = std.mem.readInt(u32, data[0..4], .big),
        };
    } else {
        _ = Address{
            .cells2 = std.mem.readInt(u64, data, .big),
        };
    }
}

```

And then panic if not matched here (I guess no address will be longer than 64 bits as of today).

---

<div class="post-metadata">

**Author:** ![andrewrk](https://ziggit.dev/user_avatar/ziggit.dev/andrewrk/32/7011_2.png) [@andrewrk](https://ziggit.dev/u/andrewrk)\
**Post date:** [December 22, 2024, 8:37pm UTC](https://ziggit.dev/t/runtime-integer-sizes/7456/4 "2024-12-22T20:37:32Z")

</div>

Runtime integer sizes are handled by [std.math.big.int](https://ziglang.org/documentation/0.13.0/std/#std.math.big.int)

---

<div class="post-metadata">

**Author:** ![samuel-fiedler](https://ziggit.dev/user_avatar/ziggit.dev/samuel-fiedler/32/2801_2.png) [@samuel-fiedler](https://ziggit.dev/u/samuel-fiedler)\
**Post date:** [December 23, 2024, 7:38am UTC](https://ziggit.dev/t/runtime-integer-sizes/7456/5 "2024-12-23T07:38:56Z")

</div>

I guess I’ll just first “scout for memory” with the union and when I have an allocator, I’ll continue with devicetree parsing with std.math.big.int.
