Re: 2-D array initialization
On Saturday, 1 August 2020 at 22:00:43 UTC, Ali Çehreli wrote: On 8/1/20 12:57 PM, Andy Balba wrote: > On Saturday, 1 August 2020 at 00:08:33 UTC, MoonlightSentinel wrote: >> On Friday, 31 July 2020 at 23:42:45 UTC, Andy Balba wrote: >>> How does one initialize c in D ? >> >> ubyte[3][4] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; > I'm a D newbie. moving over from C/C++, and I'm really finding it hard > to adjusting to D syntax, which I find somewhat cryptic compared to C/C++. That's surprising to me. I came from C++03 year ago but everything in D was much better for me. :) I wanted to respond to your question yesterday and started typing some code but then I decided to ask first: Do you really need a static array? Otherwise, the following is a quite usable 2D array: ubyte[][] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; However, that's quite different from a ubyte[3][4] static array because 'c' above can be represented like the following graph in memory. (Sorry if this is already known to you.) c.ptr --> | .ptr | .ptr | .ptr | .ptr | | | | | . . | --> | 35 | 35 | 35 | 35 | etc. etc. --> | 25 | 25 | 25 | 25 | In other words, each element is reached through 2 dereferences in memory. On the other hand, a static array consists of nothing but the elements in memory. So, a ubyte[3][4] would be the following elements in memory: | 5 | 5 | ... | 35 | 35 | One big difference is that static arrays are value types, meaning that all elements are copied e.g. as arguments during function calls. On the other hand, slices are copied just as fat pointers (ptr+length pair), hence have reference semantics. Here are some ways of initializing a static array. This one is the most natural one: ubyte[3][4] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; Yes, that works! :) Why did you need to cast to begin with? One reason may be you had a value that could not fit in a ubyte so the compiler did not agree. (?) This one casts a 1D array as the desired type: ubyte[3][4] c = *cast(ubyte[3][4]*)(cast(ubyte[])[ 5, 5, 5, 15, 15, 15, 25, 25, 25, 35, 35, 35 ]).ptr; The inner cast is required because 5 etc. are ints by-default. There is std.array.staticArray as well but I haven't used it. Ali Although not detailed in my original question, in my actual app I have array ubyte [1000][3] Big which consists of research data I obtained, and from which I want to randomly select 4 observations to construct ubyte c[ ][ ]. i.e. construct c= [ Big[r1][3], Big[r2][3], Big[r3][3], Big[r4][3] ] where r1, r2, r3 and r4 are 4 random integers in 0..1001 Being a D newbie, my naive way of doing this was to declare c using: ubyte[3][4] c= [ Big[r1][3], Big[r2][3], Big[r3][3], Big[r4][3] ] Obviously, I want to learn how to this the smart D way, but I not smart enough at this point.
Re: D on lm32-CPU: string argument on stack instead of register
On Saturday, 1 August 2020 at 08:58:03 UTC, Michael Reese wrote: [...] So the compiler knows how to use r1 and r2 for arguments. I checked again in the lm32 manual (https://www.latticesemi.com/view_document?document_id=52077), and it says: "As illustrated in Table 3 on page 8, the first eight function arguments are passed in registers. Any remaining arguments are passed on the stack, as illustrated in Figure 12." So strings and structs should be passed on the stack and this seems to be more an issue of the gcc lm32 backend than a D issue. Nice find! Though if the compiler is allowed to split a single uint64_t into two registers, I would expect it to split struct/string into two registers as well. At least, the manual doesn't seem to explicitly mention higher-level constructs like structs. It does suggest a one-to-one relationship between arguments and registers (up to a point), but GCC seems to have decided otherwise for certain uint64_t's. (Looking at Table 3...) It even gives you two registers for a return value: enough for a string or an array. And if the backend/ABI weren't up for it, it would be theoretically possible to have the frontend to lower strings (dynamic arrays) and small structs into their components before function calls and then also insert code on the other side to cast them back into their original form. I'm not sure if anyone would want to write it, though. o.O But I just found a workaround using a wrapper function. void write_to_host(in string msg) { write_to_hostC(msg.ptr, msg.length); } I checked the assembly code on the caller side, and the call write_to host("Hello, World!\n") is inlined. There is only one call to write_to_hostC. This is still not nice, but I think I can live with that for now. Now I have to figure out how make the cast back from from pointer-length pair into a string. I'm sure I read that somewhere before, but forgot it and was unable to find it now on a quick google search... That's pretty clever. I like it. Getting from pointer-length to string might be pretty easy: string foo = ptr[0 .. len]; D allows pointers to be indexed, like in C. But unlike C, D has slices, and pointers can be "sliced". The result of a slice operation, at least for primitive arrays+pointers, is always an array (the "dynamic" ptr+length kind). Hope that helps. And since this is D: is there maybe some CTFE magic that allows to create these wrappers automatically? Somthing like fix_stack!write_to_host("Hello, World!\n"); It ended up being a little more complicated than I thought it would be. Hope I didn't ruin the fun. ;) https://pastebin.com/y6e9mxre Also, that part where you mentioned a 64-bit integer being passed as a pair of registers made me start to wonder if unions could be (ab)used to juke the ABI: https://pastebin.com/eGfZN0SL Good luck with your lm32/FPGA coding. That sounds like cool stuff! I'm doing this mainly to improve my understanding of how embedded processors work, and how to write linker scripts for a given environment. Although I do have actual hardware where I can see if everything runs in the real world, I mainly use simulations. The coolest thing in my opinion is, nowadays it can be done using only open source tools (mainly ghdl and verilator, the lm32 source code is open source, too). The complete system is simulated and you can look at every logic signal in the cpu or in the ram while the program executes. Thanks for the insights; I've done just a little hobby electrical stuff here-and-there, and having some frame of reference for tool and component choice makes me feel good, even if I don't plan on buying any lm32s or FPGAs anytime soon :) Maybe I can Google some of that later and geek out at images of other people's debugging sessions or something. I'm curious how they manage the complexity that happens when circuits and massive swarms of logic gates do their, uh, complexity thing. o.O
Re: 2-D array initialization
On 8/1/20 12:57 PM, Andy Balba wrote: > On Saturday, 1 August 2020 at 00:08:33 UTC, MoonlightSentinel wrote: >> On Friday, 31 July 2020 at 23:42:45 UTC, Andy Balba wrote: >>> How does one initialize c in D ? >> >> ubyte[3][4] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; > I'm a D newbie. moving over from C/C++, and I'm really finding it hard > to adjusting to D syntax, which I find somewhat cryptic compared to C/C++. That's surprising to me. I came from C++03 year ago but everything in D was much better for me. :) I wanted to respond to your question yesterday and started typing some code but then I decided to ask first: Do you really need a static array? Otherwise, the following is a quite usable 2D array: ubyte[][] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; However, that's quite different from a ubyte[3][4] static array because 'c' above can be represented like the following graph in memory. (Sorry if this is already known to you.) c.ptr --> | .ptr | .ptr | .ptr | .ptr | | | | | . . | --> | 35 | 35 | 35 | 35 | etc. etc. --> | 25 | 25 | 25 | 25 | In other words, each element is reached through 2 dereferences in memory. On the other hand, a static array consists of nothing but the elements in memory. So, a ubyte[3][4] would be the following elements in memory: | 5 | 5 | ... | 35 | 35 | One big difference is that static arrays are value types, meaning that all elements are copied e.g. as arguments during function calls. On the other hand, slices are copied just as fat pointers (ptr+length pair), hence have reference semantics. Here are some ways of initializing a static array. This one is the most natural one: ubyte[3][4] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; Yes, that works! :) Why did you need to cast to begin with? One reason may be you had a value that could not fit in a ubyte so the compiler did not agree. (?) This one casts a 1D array as the desired type: ubyte[3][4] c = *cast(ubyte[3][4]*)(cast(ubyte[])[ 5, 5, 5, 15, 15, 15, 25, 25, 25, 35, 35, 35 ]).ptr; The inner cast is required because 5 etc. are ints by-default. There is std.array.staticArray as well but I haven't used it. Ali
Re: 2-D array initialization
On Saturday, 1 August 2020 at 00:08:33 UTC, MoonlightSentinel wrote: On Friday, 31 July 2020 at 23:42:45 UTC, Andy Balba wrote: How does one initialize c in D ? ubyte[3][4] c = [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; none of the statements below works c = cast(ubyte) [ [5, 5, 5], [15, 15,15], [25, 25,25], [35, 35,35] ]; This is an invalid cast because it tries to coerce the entire literal into an ubyte. Also it would be an assignment instead of an initialization because this is independent of c's declaration. c[0] = ubyte[3] [5, 5, 5] ; c[1] = ubyte[3] [15, 15,15] ; c[2] = ubyte[3] [25, 25,25] ; c[3] = ubyte[3] [35, 35,35] ; A cast is usually specified as `cast(TargetType) value` but not necesseray in this example. Use this instead: c[0] = [5, 5, 5] ; c[1] = [15, 15,15] ; c[2] = [25, 25,25] ; c[3] = [35, 35,35] ; for (int i= 0; i<3; i++) for (int j= 0; i<4; j++) c[i][j]= cast(ubyte)(10*i +j) ; The array indices and the inner loop condition are wrong for (int i= 0; i<3; i++) for (int j= 0; j<4; j++) c[j][i]= cast(ubyte)(10*i +j) ; You could also use foreach-loops, e.g. foreach (j, ref line; c) foreach (i, ref cell; line) cell = cast(ubyte) (10 * i + j); @ MoonlightSentinel: sorry about the typo in for (int i= 0; i<3; i++) for (int j= 0; j<4; j++) I'm a D newbie. moving over from C/C++, and I'm really finding it hard to adjusting to D syntax, which I find somewhat cryptic compared to C/C++. After going thru a D on-line tutorial, I decided D was much better than C/C++, so I began by journey into D by converting one of my less complicated C++ apps. I'm surprised at the enormous amount of time this D-conversion has taken compared to GO conversion of the very same app. Obviously, I need a faster way of climbing the D learning curve, so I would appreciate any suggestions.
Re: Lack of asm volatile qualifier (explicitly) again.
On Saturday, 1 August 2020 at 02:36:41 UTC, Cecil Ward wrote: On Thursday, 30 July 2020 at 07:05:39 UTC, Iain Buclaw wrote: [...] Ah. I wasn’t thinking about pure, although I do use it everywhere I can as a matter of course. The absence of something doesn’t hit you in the eye as an expression of the programmer’s intent I suppose, absence of pure just could mean the author forgot to put it in. I see your point though. The value of volatile I saw as in documentation. When the baseline for asm is volatile, I don't think it's entirely surprising to consider pure as a cancellation of that - afterall, if it truly is side-effect free, then it's fine for the compiler to remove the statement block.
Re: dynamic array .length vs .reserve - what's the difference?
On 7/31/20 12:32 PM, wjoe wrote: On Friday, 31 July 2020 at 04:28:57 UTC, Ali Çehreli wrote: Another option, which is curiously said to be more performant in memory allocation than native arrays, is std.array.Appender. I've used function-local static Appenders to cut down on memory allocation. Here is an uncompiled pseudo code: [...] This looks like an even better way to do it. Thanks, Ali :) Just FYI, the reason this is faster is because there is no need to go through the opaque calls into druntime to figure out if appending-in-place is possible. The reserved length is stored directly in the struct. -Steve
Re: Overload comparison operators for "nullable" types
On Thursday, 30 July 2020 at 14:52:17 UTC, H. S. Teoh wrote: On Thu, Jul 30, 2020 at 01:41:05PM +, Oleg B via Digitalmars-d-learn wrote: [...] Logically we can compare versions, but what must return `opCmp` if one of versions has 'not comparible' state? [...] opCmp is allowed to return float; so you could return float.nan in this case. T Thank you! Simple answer but not in documentation...
Re: D on lm32-CPU: string argument on stack instead of register
On Friday, 31 July 2020 at 15:13:29 UTC, Chad Joan wrote: THAT SAID, I think there are things to try and I hope we can get you what you want. If you're willing to entertain more experimentation, here are my thoughts: Thanks a lot for the suggestions and explanations. I tried them all but the only time I got different assembly code was your suggestion (3) which produced the following. (3) Try a different type of while-loop in the D-style version: // ideomatic D version void write_to_host(in string msg) { // a fixed address to get bytes to the host via usb char *usb_slave = cast(char*)BaseAdr.ft232_slave; size_t i = 0; while(i < msg.length) { *usb_slave = msg[i++]; } } _D10firmware_d13write_to_hostFAyaZv: addi sp, sp, -8 addi r4, r0, 4096 sw (sp+8), r2 sw (sp+4), r1 addi r2, r0, 0 .L3: or r5, r2, r0 be r2,r1,.L1 lw r3, (sp+8) addi r2, r2, 1 add r3, r3, r5 lbu r3, (r3+0) sb (r4+0), r3 bi .L3 .L1: addi sp, sp, 8 bra At any rate, I don't think your code is larger or less efficient due to utf-8 decoding, because I don't see the utf-8 decoding. Agreed, I think there's no code for autodecoding being generated. I did some more experiments: Trying to put pointer and length into a struct and pass that to the function. Same result, the argument ended up on the stack. Then, I wrote the function in C and compiled it with the C-compiler of lm32-elf-gcc. It also puts the 64-bit POD structure on the stack. It seems the only arguments passed in registers are primitive data types. However, if I pass a uint64_t argument, it is registered using registers r1 and r2. So the compiler knows how to use r1 and r2 for arguments. I checked again in the lm32 manual (https://www.latticesemi.com/view_document?document_id=52077), and it says: "As illustrated in Table 3 on page 8, the first eight function arguments are passed in registers. Any remaining arguments are passed on the stack, as illustrated in Figure 12." So strings and structs should be passed on the stack and this seems to be more an issue of the gcc lm32 backend than a D issue. But I just found a workaround using a wrapper function. void write_to_host(in string msg) { write_to_hostC(msg.ptr, msg.length); } I checked the assembly code on the caller side, and the call write_to host("Hello, World!\n") is inlined. There is only one call to write_to_hostC. This is still not nice, but I think I can live with that for now. Now I have to figure out how make the cast back from from pointer-length pair into a string. I'm sure I read that somewhere before, but forgot it and was unable to find it now on a quick google search... And since this is D: is there maybe some CTFE magic that allows to create these wrappers automatically? Somthing like fix_stack!write_to_host("Hello, World!\n"); Good luck with your lm32/FPGA coding. That sounds like cool stuff! I'm doing this mainly to improve my understanding of how embedded processors work, and how to write linker scripts for a given environment. Although I do have actual hardware where I can see if everything runs in the real world, I mainly use simulations. The coolest thing in my opinion is, nowadays it can be done using only open source tools (mainly ghdl and verilator, the lm32 source code is open source, too). The complete system is simulated and you can look at every logic signal in the cpu or in the ram while the program executes.