Need help type casting

I am trying to convert the code below to ZIg. I have left C-style casts in the code to illustrate what I am trying to achieve.

SInce I am not using standard types, but rather Zig “typedefs” declared at the top of the file, I can’t get my head around how to cast from one user defined type directly to another user defined type in a simple way. Any recommendations on how to structure this?

const Real_t = f32;       // global datatype definitions for large source file
const Index_t = usize;

pub fn main() !void
{
   // ...
   // lots of stuff above this line
   var tz: Real_t = ZERO;
   var plane: Index_t = 0;
   while ( plane < edgeNodes ) : ( plane += 1 ) {
      var ty: Real_t = ZERO;
      var row: Index_t = 0;
      while ( row < edgeNodes ) : ( row += 1 ) {
         var tx: Real_t = ZERO;
         var col: Index_t = 0;
         while ( col < edgeNodes ) : ( col += 1 ) {
               // computational stuff happens in here on (tx, ty, tz) tuples
           tx = 1.125*(Real_t)(col+1)/(Real_t)(edgeElems);
         }
         ty = 1.125*(Real_t)(row+1)/(Real_t)(edgeElems);
      }
      tz = 1.125*(Real_t)(plane+1)/(Real_t)(edgeElems);
   }
   // lots of stuff below this line
   //  ...
}

Check if @floatFromInt() works in the places where you’re casting to Real_t. E.g.: 1.125*@floatFromInt(col+1)/@floatFromInt(edgeElems)

In general, mixing types in expressions can get unreadable quickly because of the ‘@-noise’, sometimes it helps moving the casts out of the expression and into a separate assignment or into a small helper function.

PS: in general, check the casting builtins @floatFromInt, @intFromFloat, @floatCast and @intCast.

3 Likes

The problem is that I don’t know what the source and destinations types will be, a priori.

In my example above, it is straightforward. On the other hand, users could potentially create “typedefs” in one line at the top of a Zig file that someone could plug many different types into (int, float, u8, bool) just to trade off compiler performance for the different cases. The C-style cast handles this case extremely efficiently and unambiguously in terms of source code representation.

I don’t see a “clean” way to handle user-defined type casting in Zig unless it is very ugly, and requires a rewrite of lots of source code with any type change. I can accept that, but being new to the language, I am looking for ways to structure/encapsulate my source code with the least possible pain.

Creating small helper functions might indeed be the best solution. I am just gathering input to make sure.

Hello,
since you can use types in Zig as first class citizens, it should be possible to do it cleanly in a generic way.

Can you share more of the code, or tell us what kind of types is it supposed to support? Just so we know what we are dealing with, current description is a bit too apaque.

Would love to help
Robert :blush:

@Bobvan,

Thank you! I am using helper functions for now as @floooh suggested, because they are very clean for my use case.

My plan is to put a first cut of my 3000 line science app on GitHub on July 4 along with a showcase post on Ziggit.dev . Hopefully, people will make suggestions in that post how the first cut should be improved, and I will do my best to make some improvements.

Even better, people should feel free to submit pull requests highlighting their particular slant on how the ZIg language should be applied to achieve the best balance of Zig language features and performance.

Thanks again!

Ok,
till then, if you are interested take a look at:

  1. Compile-Time-Parameters for function level generics
  2. Generic-Data-Structures for structutre level generics
  3. std.math does a lot of type genericism at function level
  4. Ziglings extracted usefull information about the language overall into small excersises (target are newcomers)

This shows ways you can do type generic code that for example accepts any float / signed / unsigned numerical type and safely works on that, if you try to do something that is not allowed for a type, you get mostly nice compiler error.

This is one of the harder parts of the Zig, especially if you want to accept really different kinds of types, like pointers, structs, enums, arrays, etc. and have specific code paths for them. (if that interests you look at @typeInfo() and std.builtin.Type)

1 Like

Thanks. 3000 line app compiles, but I’m struggling to get it to run. In the meantime, I’ve put a 1D app in the repo GitHub - HPCguy/ZigShock: 1D and 3D Zig shock physics examples .

Fingers crossed that I will be done tomorrow before going out.

You can use gnuplot to see the results, by redirecting ouput to afile and then typing > plot “file” on the gnuplot command line.

After some more tests, I believe that Zig has some compiler bugs related to my test problem. Building Debug and ReleaseFast is producing different answers. I have added the 3000 line science app to this github location: ZigShock/3D at main · HPCguy/ZigShock · GitHub .

If anyone wants to take a look, that would be great. I believe the Debug build of Zig should be working no matter what, so I will try to determine exactly where things are going off the rails, and try to create a narrow reproducer. As I said, I know there is a Zig problem, because the debug and releasefast builds produce dramatically different results.

This is usually caused by usage of something that is undefined.

Translated into English, undefined means “Not a meaningful value. Using this value would be a bug. The value will be unused, or overwritten before being used.”

In Debug and ReleaseSafe mode, Zig writes 0xaa bytes to undefined memory. This is to catch bugs early, and to help detect use of undefined memory in a debugger. However, this behavior is only an implementation feature, not a language semantic, so it is not guaranteed to be observable to code.

EDIT: May also want to try -fllvm if you’re on x86_64 Linux and a recent master version of Zig; you might be hitting a bug in the new self-hosted backend.

1 Like

Ok. Thanks. The behavior is closer to Debug with ReleaseSafe, so I must have an undefined somewhere. The odd thing is that the first 10 timesteps of the simulation seem to be the same as the reference C code, and every timestep is supposed to touch every byte of memory that has been allocated. That said, if I am writing to a local somewhere, when I should be writing back to main memory that could be the problem. I will do data dumps after every function for every timestep tomorrow, and try to isolate the problem.

Running with valgrind (after using -fllvm) confirms that undefined is playing a part:

$ zig build-exe lulesh.zig -fllvm
$ valgrind ./lulesh
==175935== Memcheck, a memory error detector
==175935== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==175935== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info
==175935== Command: ./lulesh-llvm
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10EC3AC: lulesh.CalcVelocityForNodes (lulesh.zig:1143)
==175935==    by 0x10EB904: lulesh.LagrangeNodal (lulesh.zig:1194)
==175935==    by 0x10EA398: lulesh.LagrangeLeapFrog (lulesh.zig:2384)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10EC437: lulesh.CalcVelocityForNodes (lulesh.zig:1148)
==175935==    by 0x10EB904: lulesh.LagrangeNodal (lulesh.zig:1194)
==175935==    by 0x10EA398: lulesh.LagrangeLeapFrog (lulesh.zig:2384)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10EC4A6: lulesh.CalcVelocityForNodes (lulesh.zig:1153)
==175935==    by 0x10EB904: lulesh.LagrangeNodal (lulesh.zig:1194)
==175935==    by 0x10EA398: lulesh.LagrangeLeapFrog (lulesh.zig:2384)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
1.0000000e-7 1.0000000e-7
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10F77DF: lulesh.CalcElemCharacteristicLength (lulesh.zig:1340)
==175935==    by 0x10EEEF5: lulesh.CalcKinematicsForElems (lulesh.zig:1485)
==175935==    by 0x10EC730: lulesh.CalcLagrangeElements (lulesh.zig:1542)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10F7861: lulesh.CalcElemCharacteristicLength (lulesh.zig:1345)
==175935==    by 0x10EEEF5: lulesh.CalcKinematicsForElems (lulesh.zig:1485)
==175935==    by 0x10EC730: lulesh.CalcLagrangeElements (lulesh.zig:1542)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10F78F4: lulesh.CalcElemCharacteristicLength (lulesh.zig:1350)
==175935==    by 0x10EEEF5: lulesh.CalcKinematicsForElems (lulesh.zig:1485)
==175935==    by 0x10EC730: lulesh.CalcLagrangeElements (lulesh.zig:1542)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10F798A: lulesh.CalcElemCharacteristicLength (lulesh.zig:1355)
==175935==    by 0x10EEEF5: lulesh.CalcKinematicsForElems (lulesh.zig:1485)
==175935==    by 0x10EC730: lulesh.CalcLagrangeElements (lulesh.zig:1542)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10F7A20: lulesh.CalcElemCharacteristicLength (lulesh.zig:1360)
==175935==    by 0x10EEEF5: lulesh.CalcKinematicsForElems (lulesh.zig:1485)
==175935==    by 0x10EC730: lulesh.CalcLagrangeElements (lulesh.zig:1542)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10F7AB3: lulesh.CalcElemCharacteristicLength (lulesh.zig:1365)
==175935==    by 0x10EEEF5: lulesh.CalcKinematicsForElems (lulesh.zig:1485)
==175935==    by 0x10EC730: lulesh.CalcLagrangeElements (lulesh.zig:1542)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)
==175935==
==175935== Conditional jump or move depends on uninitialised value(s)
==175935==    at 0x10EF203: lulesh.VolErr2 (lulesh.zig:1528)
==175935==    by 0x10EC741: lulesh.CalcLagrangeElements (lulesh.zig:1551)
==175935==    by 0x10EB980: lulesh.LagrangeElements (lulesh.zig:2287)
==175935==    by 0x10EA3AC: lulesh.LagrangeLeapFrog (lulesh.zig:2388)
==175935==    by 0x10E991A: lulesh.main (lulesh.zig:2766)
==175935==    by 0x10E5473: callMain (start.zig:681)
==175935==    by 0x10E5473: callMainWithArgs (start.zig:641)
==175935==    by 0x10E5473: start.posixCallMainAndExit (start.zig:596)
==175935==    by 0x10E501D: (below main) (start.zig:286)

(truncated, it continues hitting similar errors)

1 Like

Thanks! I’ll try to track it down before I have to leave the house tomorrow.

I’ll also change this:

     var ydtmp =yt;
     if( @abs(yt) < u_cut ) ydtmp = ZERO;
     yd[k] = ydtmp;

to this:

   yd[k] = if ( @abs(yt) < u_cut ) ZERO else yt;

I had so much to change and so many bugs (not to mention a trip to AZ), that I haven’t had a chance to clean up.

1 Like

Turns out that many of the Valgrind messages are false positives.

For example:

         var delvxxi: Real_t   = delv_xi[idx]   * delx_xi[idx];
         var delvxeta: Real_t  = delv_eta[idx]  * delx_eta[idx];
         var delvxzeta: Real_t = delv_zeta[idx] * delx_zeta[idx];

         if ( delvxxi   > ZERO ) delvxxi   = ZERO;
         if ( delvxeta  > ZERO ) delvxeta  = ZERO;
         if ( delvxzeta > ZERO ) delvxzeta = ZERO;

has the following impossible Valgrind message:

==2479== Conditional jump or move depends on uninitialised value(s)
==2479==    at 0x10F7170: lulesh.CalcMonotonicQRegionForElems (lulesh.zig:1816)
==2479==    by 0x10F0317: lulesh.CalcMonotonicQForElems (lulesh.zig:1850)
==2479==    by 0x10ED76F: lulesh.CalcQForElems (lulesh.zig:1902)
==2479==    by 0x10EC95B: lulesh.LagrangeElements (lulesh.zig:2284)
==2479==    by 0x10EB77B: lulesh.LagrangeLeapFrog (lulesh.zig:2382)
==2479==    by 0x10EAE1B: lulesh.main (lulesh.zig:2760)
==2479==    by 0x10E76B3: callMain (start.zig:675)
==2479==    by 0x10E76B3: callMainWithArgs (start.zig:635)
==2479==    by 0x10E76B3: start.posixCallMainAndExit (start.zig:590)
==2479==    by 0xFFFFFFFFFFFFFFFF: ???
==2479==
==2479== Conditional jump or move depends on uninitialised value(s)
==2479==    at 0x10F7180: lulesh.CalcMonotonicQRegionForElems (lulesh.zig:1817)
==2479==    by 0x10F0317: lulesh.CalcMonotonicQForElems (lulesh.zig:1850)
==2479==    by 0x10ED76F: lulesh.CalcQForElems (lulesh.zig:1902)
==2479==    by 0x10EC95B: lulesh.LagrangeElements (lulesh.zig:2284)
==2479==    by 0x10EB77B: lulesh.LagrangeLeapFrog (lulesh.zig:2382)
==2479==    by 0x10EAE1B: lulesh.main (lulesh.zig:2760)
==2479==    by 0x10E76B3: callMain (start.zig:675)
==2479==    by 0x10E76B3: callMainWithArgs (start.zig:635)
==2479==    by 0x10E76B3: start.posixCallMainAndExit (start.zig:590)
==2479==    by 0xFFFFFFFFFFFFFFFF: ???
==2479==
==2479== Conditional jump or move depends on uninitialised value(s)
==2479==    at 0x10F71A0: lulesh.CalcMonotonicQRegionForElems (lulesh.zig:1818)
==2479==    by 0x10F0317: lulesh.CalcMonotonicQForElems (lulesh.zig:1850)
==2479==    by 0x10ED76F: lulesh.CalcQForElems (lulesh.zig:1902)
==2479==    by 0x10EC95B: lulesh.LagrangeElements (lulesh.zig:2284)
==2479==    by 0x10EB77B: lulesh.LagrangeLeapFrog (lulesh.zig:2382)
==2479==    by 0x10EAE1B: lulesh.main (lulesh.zig:2760)
==2479==    by 0x10E76B3: callMain (start.zig:675)
==2479==    by 0x10E76B3: callMainWithArgs (start.zig:635)
==2479==    by 0x10E76B3: start.posixCallMainAndExit (start.zig:590)
==2479==    by 0xFFFFFFFFFFFFFFFF: ???

I likely messed up somewhere in my coding though, and I’ll narrow down a compact reproducer in the event I find an actual compiler problem.

Are you sure it’s impossible? undefined * undefined still leaves the result as undefined AFAIK.

1 Like