On 22/05/2019 02:07, Bart wrote:
> On 21/05/2019 23:13, David Brown wrote:
>> On 21/05/2019 20:56, Bart wrote:
>
>>> Just a mismatch of languages, even though C in this case
>>> superficially works the same way.
>>>
>>
>> In other words, bugs in the auto-translator. The flaws are in the
>> design and specification, rather than the implementation, but they are
>> bugs nonetheless.
>
> You can call a unwillingness to expend a huge, disproportionate effort
> in overcoming C's many shortcomings for this purpose a bug if you like.
You are happy to classify your wilful and determined ignorance of C as a
bug in yourself? Okay, I suppose.
Certainly the idea that this is all a "huge, disproportionate effort" is
your own personal problem. Undefined behaviours in C are mostly quite
clear and obvious, you rarely meet them in practice, and they are mostly
straightforward to handle. For a language generator, they are peanuts
to deal with. These have been explained to you countless times.
Of course, dealing with them nicely and efficiently involves macros and
the C preprocessor. But it is apparently far better to whine and moan
about deficiencies in C than to use the features of C to get what you need.
>
> The source language has a simple, orthogonal type model, 64-bit-based,
> which is very easy to superimpose on the simple hardware model of the
> 64-bit target you want to use.
>
> But now introduce C between the two, which has a more complex, unwieldy,
> not-quite-so-unorthogonal type system, which is 32-bit-based even when
> the final target is 64-bit, with its million and one quirks, and which
> doesn't quite match that of the target language.
C is not based on any hardware model - it is more abstract. Yes,
putting that in between the two layers that have matching models will
cause complications, and you will have to be careful to get it right.
But as abstract models go, C's is not difficult to comprehend.
>
>> It is fine as a target language, but you need to generate correct C code.
>
> Which means what? So that there are 0 errors and 0 warnings no matter
> what options somebody will apply?
No. It means that there are no errors in the code, based on whatever
restrictions you might want to place on how it is used. If you want to
generate fully portable C code (matching a particular standard), then do
so. If you want to generate code that has limitations on the compiler
or flags needed, then do so - but make sure that you document the
restrictions. Far and away the best choice here is to use conditional
compilation and compiler detection. For example, if you want to allow
casting between different pointer types to work for punning, and you
want wrapping overflow behaviour to match your source language, then try
something like this:
#ifdef __GNUC__
/* Set options needed by gcc and clang for desired C variant */
#pragma GCC optimize "-fno-strict-aliasing"
#pragma GCC optimize "-fwrapv"
#pragma GCC diagnostic ignored "-Wformat"
#elif defined(_MSC_VER)
/* Set options needed by MSVC for desired C variant */
#elif defined(_BART_C)
/* Bart's C compiler already supports Bart C */
#else
#error Untested compiler - remove this and compile at your own risk
#endif
Simple and safe.
Add whatever other tests and compiler-specific options you need to suit
the compilers you have tested - forcing compiler options with pragmas
and disabling any warnings you might want to disable. For example, if
you are confident that your code generator gets all printf formats
correct, then you might like to disable gcc's "-Wformat" warnings. Then
you can use "%dll" for int64_t, without warning, whether the target
typedef's int64_t to "long int", "long long int", or a compiler
extension type.
You can expand this for other assumptions made by your code. For
example, you can include <limits.h> and confirm that INT_MIN is
-
2147483648 and that CHAR_BIT is 8.
I've given such suggestions before. But as usual, you'd prefer to
complain and get things wrong than learn from others. After all, it is
/so/ much better to write thousands of lines of Usenet posts whining
than to put a dozen lines of fixed text into your C generator.
>
> How can something be judged correct or not when that measure depends on
> which options - which can be out of your control - are applied?
Easy - put it under your control. Stop saying "this is C code" unless
it is standards conforming C code that works with any standards
conforming C compiler. If you want to write code that is only correct
in certain circumstances, then say that the code is only useable in
those circumstances. As shown above, the best way to do this is with
compile-time checks.
>
>> Other people who write code generators or translators that produce C
>> manage it. And when their generated code has flaws, they blame their
>> generators - not the language.
>
> The fault is very largely with the unsuitably of C for the role. Except
> that there isn't really anything else that is as ubiquitous.
First, it is completely unreasonable to blame C for its suitability for
a role /you/ choose for it. That is like buying a Ford Transit and then
blaming Ford when it doesn't fit in your garage.
Secondly, C works perfectly well for this usage. It is no coincidence
that there aren't any serious alternatives - C does a fine job for this
kind of purpose, and there is no demand for anything else. The fact
that you are having trouble is a reflection on you, not on C.
>
>> You understand how assembly works. You are willing to use features of
>> assemblers. You don't understand how C works. You are unwilling to
>> use many features of the language. It is not surprising that you find
>> generating assembly easier than generating C code.
>
> ASM doesn't stop me storing a 64-bit function pointer into a 64-bit
> memory location. Source language:
>
> import clib
> ref void fnptr
> fnptr := puts
>
> Generates native code (could be a one-liner but never mind):
>
> lea D0, [`puts*]
> mov [t.fnptr], D0
>
> No problem. Now I get it to generate C:
>
> static void * t_fnptr;
> t_fnptr = (void *)(&puts);
>
> gcc (no options):
>
> (Nothing)
It is entirely reasonable to generate no code, because you aren't doing
anything. If t_fnptr is static, and the compiler can see it is never
read, then it will not bother storing anything there (if there is
optimisation enabled). I recommend using "static" at the file-scope
level whenever possible, as it generates more efficient code, reduces
errors, and improves modularity. But it doesn't help your testing in
cases like this.
If you make t_fnptr a non-static file-scope variable, you get the code
you expect:
void * t_fnptr;
void foo(void) {
t_fnptr = (void*) (&puts);
}
gcc -O2 -x c -std=gnu11:
foo:
mov QWORD PTR t_fnptr[rip], OFFSET FLAT:puts
ret
>
> gcc (with recommended bunch of options):
>
> t.c:66:5: warning: ISO C forbids initialization between function
> pointer and 'void *' [-Wpedantic]
"Recommended bunch of options" is meaningless unless you say what why
they are recommended, and they are only recommended for particular
cases. "-Wpedantic" is recommended when you want to try to ensure that
your code is portable standards compliant C. But what you are writing
here is /not/ standard C - it is C that is supported by many compilers,
but not all. So "-Wpedantic" would not be recommended for this code.
Please try to apply a little common sense here - it is almost as though
you /want/ to give yourself problems rather than to solve your problems.
>
> g++ (no options):
>
> t.c:66:5: error: invalid conversion from 'void (*)()' to 'void*'
> [-fpermissive]
I don't get that at all, with my quick
godbolt.org testing.
>
> Three categories of message; which one was right? Because either my
> source code is fine, or it isn't.
You are driving yourself bananas with your nonsensical attitude here.
No, it is /not/ that case that your source code is either fine or it is
not fine. You have to ask about the circumstances and conditions. As
standard C, it is /not/ fine. As C suitable for many practical
compilers, it /is/ fine - depending on the options.
You have three choices here, as far as I can see.
1. Make the effort to generate good, portable C code to the greatest
possible extent. This would mean using "void (*)()" as your general
function pointer type, rather than "void *". It would also mean losing
some of the simplicity in a direct translation from your language to C,
as the semantics are different.
2. Adapt the target C variant to suit. The way to do that is given
earlier in this post. This will restrict the compilers and targets that
can use your code, but that is probably absolutely fine.
3. Whine, moan, complain, and blame C, blame me, blame the C committee,
blame the gcc developers, blame the dog across the road and everyone
else for your continued stubborn, wilful ignorance.
Options 1 and 2 work fine for other people, but my money's on your
picking number 3.
>
>>> Are there are any others I'm likely to be able to program in consumer
>>> equipment?
>>
>> Since your languages and tools are for you alone, it is up to you to
>> answer that one.
>
> I meant: which ones am I likely to come across? What can I buy from PC
> World that I can program, that will have some fancy processor inside
> where function pointers are bigger than 64 bits, or float64 has a
> different byte ordering from int64.
If your restrictions are computers from PC World, then it is likely that
they will be ARM or x86. The great majority of processors are /not/ ARM
or x86, but you will not be programming them. (And to be fair, the
majority of these other processors have function pointers of the same
size as data pointers, and consistent endianness.)
>
> If you want to generate code that
>> only works on platforms where you can store a function pointer in a
>> void* pointer (though I can't imagine why it would be useful),
>
> I explained why: to produce a list of pointers to disparate functions.
> (As to why /that/ might be useful, I'd have post a link to a longer
> explanation.)
But why do you want to store them in "void*" pointers? Why not a "void
(*)(void)" pointer? That is the most sensible type here, and the
standards guarantee it will work. (You need to convert back to the
correct function type pointer before calling it, of course.)
>
>> you can tune your options to suit.
>
> The compiler will already know where that will work and where it won't,
> because it presumably knows what the target is, and can either report an
> error if not, or arrange for it to work.
>
>> Perhaps try with "-fpermissive" ?
>
> How does that magically make it alright? I thought such an option just
> suppressed the warning? (For g++, it just changed an error to a warning.)
I have not needed to try the "-fpermissive" option - I don't write code
like that in the first place. I merely repeated gcc's suggestion.
>
> Is there an actual practical problem in doing such a conversion or not?
Such conversions can be implemented, of course, but that does not mean
that they are allowed by the language.
>
> And if not, why is it bothering me with it?
The compiler gives a warning when it thinks you have made a mistake in
your code.