Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
VikingofRock
Aug 24, 2008




RIP Syndrome posted:


Rust seems nice. It's a bigger language, though, and it doesn't support dynamic linking yet.

I thought you could just specify crate-type="cdylib". Is there some issue with doing so? I haven't actually played around with dynamic linking in Rust.

Adbot
ADBOT LOVES YOU

RIP Syndrome
Feb 24, 2016

netcat posted:

Every time I take a look at rust it just seems ugly, verbose and complex and I immediately lose interest.

Not wrong. Trying to overcome it myself. Cf. C++.

VikingofRock posted:

I thought you could just specify crate-type="cdylib". Is there some issue with doing so? I haven't actually played around with dynamic linking in Rust.

I think that's for foreign languages. Looks like my information was a bit out of date, though -- there's dylib too, for Rust consumers. I'm not sure, but I had the impression that the ABI is still unstable and changing between compiler versions, so it's of limited usefulness. Still too hard to find precise info on it :-/

Edit: https://github.com/rust-lang/rfcs/issues/600

quote:

Rust has been committed to a stable language and API since 1.0, but ABI stability has never been claimed. Your code is compatible; your binaries are not.

RIP Syndrome fucked around with this message at 18:55 on Feb 12, 2019

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

The ABI situation would be familiar to anyone doing C++ on the gcc/egcs toolchain in the 90s, when there was a similar lack of ABI guarantee. As with Rust today, it didn’t mean that every release changed the ABI, but it did infrequently happen and it was something you had to keep in mind in terms of how you updated your system and its dependencies. Nonetheless, people built and updated large and successful systems using that C++ toolchain. I wish we’d had the same kind of deployment management tools and versioning sophistication then that we have today.

My first reaction to Rust about a decade ago was similar to what a lot of people experience now: it is weird and verbose and complex. I’ve since come to believe that any successful systems-domain heir to C++’s fortune will pretty much necessarily be those things.

It’s complex because writing correct programs with systems performance and flexibility is a complex problem, especially in the face of concurrency. There are issues of lifetimes and data ownership that we also face in C++, but generally we settle for an incomplete understanding, which is why ~80% of software vulnerabilities are related to memory safety. Having to specify all the dimensions of ownership and lifecycle means there are additional factors that have to be considered thoroughly the time, not just when we know we’re dealing with something tricky. Still, compared to the complexity of manually reasoning about things like exception safety and concurrent data access in C++, it isn’t too onerous a burden.

It’s verbose because when you are building a complex system you want the compiler to tell you when the requirements of correctness are violated. This means that you have to tell the compiler more things about your program and what it intends to do, or what assumptions it relies on. Where these facts lived in comments and tests and post-hoc asserts — if they lived anywhere, let alone were up to date — they now live in the code where the compiler can make sure they’re honoured.

It’s weird for the same reason that template metaprogramming is weird, or continuation-passing style: we aren’t used to thinking — and certainly not talking to the compiler — in terms of data lifetimes and similar. They are largely new concepts for developers coming to Rust, or at least things they aren’t used to being in the code proper and pervasive.

Any credible future language that wants to succeed in this low-level systems domain, as currently basically requires C or C++, will need to attack the same problems as Rust aims to address, and will have to bring (at least!) the same safety reasoning to the foreground. I think this is the case even if that future C++ is C++ itself. Maybe there will be “better” syntax or a more elegant way of expressing certain program elements, but I think this will be a relatively minor difference compared to the major conceptual issues.

One thing I’ve heard from a fair number of people who moved from writing complex systems code in C++ to doing so in Rust is that they feel they are now better C++ programmers because of the reasoning patterns that Rust required them to develop. Also, they pretty much all say that they are more afraid when writing C++ than they were before. :)

E: sorry, that wasn’t as big a post in my head. Not gonna waste that typing though!

Subjunctive fucked around with this message at 04:35 on Feb 13, 2019

nielsm
Jun 1, 2009




These are some good words to explain that hard things are in fact hard. Thanks.

xgalaxy
Jan 27, 2004
i write code
Yea Rust requires more to learn it than just syntax differences... which is much different than any of my experiences with learning other languages that were new to me.
I don't like some of the choices Rust has made syntax wise / naming wise but overall its okay.

I've also found that a lot of the ugliness in Rust code is found in about 20% of your code and the other 80% is actually pretty easy to read and even somewhat elegant.

You also have to take into account that there are some people out there in the Rust community who like to mentally masturbate over their superiority and write overly complex programs just to prove they are smart to people. I find the same is true in the C++ community too.

One Rust project I like to look at is: https://github.com/ggez/ggez
I find it to be a good example of easy to read and reason about Rust code that isn't complex for complexity sake.

xgalaxy fucked around with this message at 16:51 on Feb 13, 2019

Blotto Skorzany
Nov 7, 2008

He's a PSoC, loose and runnin'
came the whisper from each lip
And he's here to do some business with
the bad ADC on his chip
bad ADC on his chiiiiip

xgalaxy posted:

One Rust project I like to look at is: https://github.com/ggez/ggez
I find it to be a good example of easy to read and reason about Rust code that isn't complex for complexity sake.

Looking through the source, I see some functions with names that end in !, which I assume they got from scheme. Did they also pick up the convention of ending functions with ? to indicate that they return a boolean?

xgalaxy
Jan 27, 2004
i write code

Blotto Skorzany posted:

Looking through the source, I see some functions with names that end in !, which I assume they got from scheme. Did they also pick up the convention of ending functions with ? to indicate that they return a boolean?

Functions that end with ! are macros -- which I hate using that name because it has a lot of negative connotations if you are used to thinking of macros in C/C++ terms.
Rust macros are quite a bit different than C/C++ macros. They're hygienic for one.

Here is an example of a Rust macro:
code:
macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

fn main() {
    assert_eq!(25, five_times!(2 + 3));
}
The ? operator in rust is for Option and Result types. Its syntax sugar for getting a value from those things.

toadoftoadhall
Feb 27, 2015
So, I'm working on a Qt-based project that depends on custom configured Qt libraries, which we build from source. In addition to Qt, we must source build a few other libraries which are statically linked in to Qt. At present, all of this is captured in a Python script, a stripped-down, rough approximation of which is:
code:
 build_dependency_1()
 build_dependency_2()
 build_dependency_3()

 build_qt()
Each new developer is required to perform this build, and it (i) takes f*****g ages, and (ii) something is going to error along the way, which contributes to (i). This project has hitherto been developed for a long time by one individual, so on-boarding processes haven't been considered. I think there must be a way "industry" has solved this, because it's so inefficient. Would it be a development VM image with the built Qt libs already installed? Would it be a tarballs of the built Qt libs available on a server for all Linux distributions (all x86_64, so would this even be necessary?) developers are likely to use?

Dren
Jan 5, 2001

Pillbug
step 1 pick a single supported distro and version of that distro
step 2 build the deps into an rpm or a deb or whatever package your distro supports
3 publish that package to a local webserver
4 write some ansible or something to provision a minimally installed machine with the deps needed to build your software including the compiler and everything else

for bonus points wrap this all up and put a bow on it with vagrant

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
Nix with a binary cache is a very powerful solution to this kind of problem, but probably the better solution is to fix your project not to require a patched Qt.

feedmegin
Jul 30, 2008

Ralith posted:

Nix with a binary cache is a very powerful solution to this kind of problem, but probably the better solution is to fix your project not to require a patched Qt.

They said custom configured, not patched. Could be something like https://blog.qt.io/blog/2016/08/18/introducing-the-qt-lite-project-qt-for-any-platform-any-thing-any-size/

(Which is not a new idea, I worked on a previous iteration of this when I was working for Trolltech in about 2001).

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

Use Docker containers, you can separate the qt dependencies vs qt build into their own "layers", and it auto caches stuff if the commands don't change between builds.

Brownie
Jul 21, 2007
The Croatian Sensation
Does anyone have any advice on how to debug broken preprocessor statements? I tried adding tinygltf as a dependency to my rendering project and on Windows I start getting all these syntax errors in other, unrelated dependencies as soon as I include the required preprocessor #defines in my code. I am not even sure how preprocessor stuff gets evaluated -- is it lazily evaluated using import order?

Also I have a feeling this has to do with both tinyglft and bgfx having stb_image as a header only dependency that also requires its own preprocessor definitions... but I really hope not!

RIP Syndrome
Feb 24, 2016

Brownie posted:

Does anyone have any advice on how to debug broken preprocessor statements? I tried adding tinygltf as a dependency to my rendering project and on Windows I start getting all these syntax errors in other, unrelated dependencies as soon as I include the required preprocessor #defines in my code. I am not even sure how preprocessor stuff gets evaluated -- is it lazily evaluated using import order?

Also I have a feeling this has to do with both tinyglft and bgfx having stb_image as a header only dependency that also requires its own preprocessor definitions... but I really hope not!

The preprocessor does just what it says; it preprocesses the text files before they get to the compiler. The compiler never sees the #defines and such, although it can affect compiler operation (see #pragma). It's processed in a single-pass stream fashion, so ordering matters. It's mostly used for inserting text from different files (#include) replacing text with other text (#define STRING other_string or #define STRING(a,b) { hello(a); goodbye(b); } or other constructs of varying degrees of sanity), or conditionally including or excluding text (#if, #ifdef). It's best to think of it as conceptually separate from compilation -- I've seen cpp used as a preprocessor for application configuration files (do not do this) -- and a lot less intelligent.

If you're getting a pile of errors, it's generally best to start with the first one since that's likely to be the closest to whatever's causing the issue. Look at the source line, compare it to what the compiler thinks it's seeing, and think about how macros might play a role in the transformation.

stb_image.h looks fairly unhygienic -- for instance, it defines temporary, non-namespaced macros without undeffing them after use (e.g. COMBO). And if you're defining STB_IMAGE_IMPLEMENTATION, it will inline a bunch of source code every time it is included (possibly multiple times in the same compilation unit if it's included again from dependencies). It also includes a good number of system header files, which should be protected from multiple inclusion and not require you to include them in a specific order, but they may behave differently depending on which other macros are defined prior to their inclusion.

It's hard to say what's biting you without more details, but you could try to put whatever requires stb_image after your other includes, or as late as possible in the list.

You could also #include <stb_image.h> yourself (substitute the correct path as necessary), before the dependencies that require it, and see how that turns out.

Edit: If you're using STB_IMAGE_IMPLEMENTATION, here's a stab in the dark:

code:
/* Your basic includes here */

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#undef STB_IMAGE_IMPLEMENTATION

/* Dependencies that require stb_image here */

RIP Syndrome fucked around with this message at 20:50 on Feb 18, 2019

Brownie
Jul 21, 2007
The Croatian Sensation
Thanks a lot for the explanation. I think I've solved it, at least temporarily, by first trying every dependency one at a time alongside tinygltf. Sure enough, it looks stb_image must be the problem since as soon as I import the BGFX helper lib, bx, *before* tinygltf everything works just fine. Do it after and everything starts breaking, and pointing me to totally unrelated dependencies (it'll actually just start throwing syntax errors in the first following import).

I might try to create a minimal reproduction and submit it to bx or stb_image. Not really sure who's fault it is to be honest. At least tinygltf requires you to #define yourself, while bx does it silently in some secondary dependency <:mad:>

Thanks again, I would never have solved this without your help!

EDIT: Hmmm I may have spoken too soon. It's not bx that has the #define STB_IMAGE_IMPLEMENTATION, it's bimg (also a BGFX dependency) but the problem does go away when I move the bx import... time to do some more digging.

Brownie fucked around with this message at 21:32 on Feb 18, 2019

RIP Syndrome
Feb 24, 2016

Brownie posted:

EDIT: Hmmm I may have spoken too soon. It's not bx that has the #define STB_IMAGE_IMPLEMENTATION, it's bimg (also a BGFX dependency) but the problem does go away when I move the bx import... time to do some more digging.

The way it's set up it's important that STB_IMAGE_IMPLEMENTATION is defined before one and only one instance of #include "stb_image.h". You could potentially #undef it yourself between #includes. There're also some random macros still set after the inline code gets emitted which might hork things up.

I really don't like the practice of making definitions via headers, it causes messes like this. If you have to do it, at least provide a macro that lets you namespace the generated code, i.e. MAKE_FUN_BOILERPLATE(mynamespace) :argh:

Alternately they could've just protected the definitions against multiple inclusion like they did with the prototypes (#ifndef STBI_INCLUDE_STB_IMAGE_H etc).

Also don't exclude the possibility of the problem being in some other part of the included sources :)

Brownie
Jul 21, 2007
The Croatian Sensation

RIP Syndrome posted:

The way it's set up it's important that STB_IMAGE_IMPLEMENTATION is defined before one and only one instance of #include "stb_image.h". You could potentially #undef it yourself between #includes. There're also some random macros still set after the inline code gets emitted which might hork things up.

I really don't like the practice of making definitions via headers, it causes messes like this. If you have to do it, at least provide a macro that lets you namespace the generated code, i.e. MAKE_FUN_BOILERPLATE(mynamespace) :argh:

Alternately they could've just protected the definitions against multiple inclusion like they did with the prototypes (#ifndef STBI_INCLUDE_STB_IMAGE_H etc).

Also don't exclude the possibility of the problem being in some other part of the included sources :)

I created a "minimal" reproduction here, where I'm only include BGFX (and its helper libs) and tinygltf: https://github.com/BruOp/tinygltf_bug_reproduction

So there's definitely something going on between BGFX and tinygltf. Including stb_image on my own (with macros) didn't reproduce the problem, it only happens with the tinygltf include.

I didn't have any luck trying to #undef the associated macros either. I've posted the issue in tinygltf for now, and I guess I'll see what the author says. For now, I'm at least unblocked.

Lime
Jul 20, 2004

Brownie posted:

I created a "minimal" reproduction here, where I'm only include BGFX (and its helper libs) and tinygltf: https://github.com/BruOp/tinygltf_bug_reproduction

Compiling with MSVC, I got these (excerpted) errors:

code:
[...]\bx\include\bx\bx.h(51): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(51): error C2059: syntax error: 'const'
[...]\bx\include\bx\tbx.h(51): error C2059: syntax error: ')'
[...]\bx\include\bx\bx.h(55): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(55): error C2059: syntax error: 'const'
[...]\bx\include\bx\bx.h(55): error C2059: syntax error: ')'
[...]\bx\include\bx\bx.h(59): warning C4002: too many arguments for function-like macro invocation 'min'
[...]\bx\include\bx\bx.h(59): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(59): error C2059: syntax error: 'const'
[...]\bx\include\bx\bx.h(59): error C2059: syntax error: ')'
[...]\bx\include\bx\bx.h(63): warning C4002: too many arguments for function-like macro invocation 'max'
[...]\bx\include\bx\bx.h(63): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(63): error C2059: syntax error: 'const'
[...]\bx\include\bx\bx.h(63): error C2059: syntax error: ')'
What happens on lines 51, 55, 59, and 63? Definitions of min and max functions. This made me immediately suspect somebody was including windows.h in a header somewhere, which infamously #defines a buttload of unhelpful junk and in particular all lowercase min and max macros. Sure enough, tiny_gltf.h itself does.

Two solutions: you can #undef min and max immediately after including tiny_gltf.h, or you can #define NOMINMAX before including tiny_gltf.h (which is really what tiny_gltf ought to do themselves, if they really must include windows.h in a header). Doing either lets me compile main.cpp just fine.

Brownie
Jul 21, 2007
The Croatian Sensation

Lime posted:

Compiling with MSVC, I got these (excerpted) errors:

code:
[...]\bx\include\bx\bx.h(51): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(51): error C2059: syntax error: 'const'
[...]\bx\include\bx\tbx.h(51): error C2059: syntax error: ')'
[...]\bx\include\bx\bx.h(55): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(55): error C2059: syntax error: 'const'
[...]\bx\include\bx\bx.h(55): error C2059: syntax error: ')'
[...]\bx\include\bx\bx.h(59): warning C4002: too many arguments for function-like macro invocation 'min'
[...]\bx\include\bx\bx.h(59): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(59): error C2059: syntax error: 'const'
[...]\bx\include\bx\bx.h(59): error C2059: syntax error: ')'
[...]\bx\include\bx\bx.h(63): warning C4002: too many arguments for function-like macro invocation 'max'
[...]\bx\include\bx\bx.h(63): error C2988: unrecognizable template declaration/definition
[...]\bx\include\bx\bx.h(63): error C2059: syntax error: 'const'
[...]\bx\include\bx\bx.h(63): error C2059: syntax error: ')'
What happens on lines 51, 55, 59, and 63? Definitions of min and max functions. This made me immediately suspect somebody was including windows.h in a header somewhere, which infamously #defines a buttload of unhelpful junk and in particular all lowercase min and max macros. Sure enough, tiny_gltf.h itself does.

Two solutions: you can #undef min and max immediately after including tiny_gltf.h, or you can #define NOMINMAX before including tiny_gltf.h (which is really what tiny_gltf ought to do themselves, if they really must include windows.h in a header). Doing either lets me compile main.cpp just fine.

Wow amazing :woop:. I've already put up an issue in the tinygltf repo, please feel free to post your findings: https://github.com/syoyo/tinygltf/issues/143

hackbunny
Jul 22, 2007

I haven't been on SA for years but the person who gave me my previous av as a joke felt guilty for doing so and decided to get me a non-shitty av
A cute trick that Microsoft uses in their C++ standard library headers is parenthesizing the min and max tokens, like this:

C++ code:
template<> class numeric_limits<char>
	: public _Num_int_base
	{	// limits for type char
public:
	_NODISCARD static constexpr char (min)() noexcept
The preprocessor only matches function-like macros if they're followed by a ( token, so it'll leave those alone

hackbunny fucked around with this message at 00:02 on Feb 21, 2019

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

“Why are those parenthesised?” will be some good nerd bar trivia at some point.

Brownie
Jul 21, 2007
The Croatian Sensation

hackbunny posted:

A cute trick that Microsoft uses in their C++ standard library headers is parenthesizing the min and max tokens, like this:

C++ code:
template<> class numeric_limits<char>
	: public _Num_int_base
	{	// limits for type char
public:
	_NODISCARD static constexpr char (min)() noexcept
The preprocessor only matches function-like macros if they're followed by a ( token, so it'll leave those alone

Didn't realize that this is valid C++, would assume you'd get a syntax error! I... don't know how I feel about this.

Xarn
Jun 26, 2015

hackbunny posted:

A cute trick that Microsoft uses in their C++ standard library headers is parenthesizing the min and max tokens, like this:

C++ code:
template<> class numeric_limits<char>
	: public _Num_int_base
	{	// limits for type char
public:
	_NODISCARD static constexpr char (min)() noexcept
The preprocessor only matches function-like macros if they're followed by a ( token, so it'll leave those alone

This also works with namespaces, which is why Catch contains this beauty
code:
return (std::min) (MaxExitCode, (std::max) (totals.error, static_cast<int>(totals.assertions.failed)));
I still think we should just do this instead though:
code:
#if defined(max) || defined(min)
#error "gently caress off"
#endif
:v:

Mooey Cow
Jan 27, 2018

by Jeffrey of YOSPOS
Pillbug

Brownie posted:

Didn't realize that this is valid C++, would assume you'd get a syntax error! I... don't know how I feel about this.

Consider how you'd normally declare a function pointer:
code:
int (*min)(int, int);

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

Anyone familiar with design of interpreters and/or compilers? I contribute to an open source project that is sort of a compiler (but idk probably acts like an interpreter in some ways too), and I'm currently interested in optimizing how it handles scope and variable lookup. Currently it has a Context class that keeps its own unordered_map<string, Value> for variable lookup by name. It creates a Context object in every scope, and has a pointer to the parent Context, so to look up a variable, it tries to find the variable name string in the current map, and if not found, recurs up the stack to lookup the variable in the parent etc.

This seems really inefficient and I'm thinking about changing it to have a single "Context" that would be an unordered_map<string, vector<Value>>, so every time you go to a new scope you do a push_back of the value assignments onto the appropriate vectors and to find the current value you just read the last element in the vector, then you pop_back when you go out of that scope. Does this sound like a solid approach? I'm wondering if there are any particularly performant interpreters that have other methods of doing such things.

There is also a catch that the language has mostly lexical scope, but there are special types of variables that can also provide dynamic scope, which is being handled in a different way where I don't fully understand the implementation yet. So I'll have to to study that some more.

Also I'm aware that std::unordered_map is generally considered to have poor performance as hash tables go, and might be looking into an alternative to that if I can get this idea worked out.

peepsalot fucked around with this message at 02:25 on Mar 5, 2019

Bruegels Fuckbooks
Sep 14, 2004

Now, listen - I know the two of you are very different from each other in a lot of ways, but you have to understand that as far as Grandpa's concerned, you're both pieces of shit! Yeah. I can prove it mathematically.

peepsalot posted:

Anyone familiar with design of interpreters and/or compilers? I contribute to an open source project that is sort of a compiler (but idk probably acts like an interpreter in some ways too), and I'm currently interested in optimizing how it handles scope and variable lookup. Currently it has a Context class that keeps its own unordered_map<string, Value> for variable lookup by name. It creates a Context object in every scope, and has a pointer to the parent Context, so to look up a variable, it tries to find the variable name string in the current map, and if not found, recurs up the stack to lookup the variable in the parent etc.

This seems really inefficient and I'm thinking about changing it to have a single "Context" that would be an unordered_map<string, vector<Value>>, so every time you go to a new scope you do a push_back of the value assignments onto the appropriate vectors and to find the current value you just read the last element in the vector, then you pop_back when you go out of that scope. Does this sound like a solid approach? I'm wondering if there are any particularly performant interpreters that have other methods of doing such things.

does your language have anonymous functions/lambdas? if scope capturing is in your language, your approach definitely isn't going to work.

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

Bruegels Fuckbooks posted:

does your language have anonymous functions/lambdas? if scope capturing is in your language, your approach definitely isn't going to work.
No but "special variables" (anything prefixed with "$") get passed from current scope into function calls.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Optimizing the representation of the local-variables dictionary shouldn't be particularly important for you because it should be an aim of your implementation to avoid ever doing lookups during actual evaluation. What you can achieve here depends a lot on the semantics of your language, but the right first step is generally to flatten all of your locals into a vector and resolve all locals to an offset into that vector at parse-time. Keep track of how many local variables there are, and whenever you parse a new local variable, store the current count in the dictionary for that name and then increment the count. If locals have sensible lifetime (unlike, say, in JS), you can reset the count whenever you pop a scope. The key thing is that now you can write that index down in the AST instead of the variable name, both for the declaration and for any references to it, so you won't have to do a dictionary lookup during evaluation. In a totally lexically-scoped language, you can actually throw the dictionary away completely after parsing; if you have dynamic-scoping features, you will need to preserve the dictionary to support dynamic lookups, but at least you won't be doing that for every local-variable reference.

The next step beyond that is to recognize that you don't need to allocate a separate vector of locals for every call because you can just use a single vector as a stack. In the parser, you remember the max number of locals that were alive at once, and then on function entry you just need to push space for that many variables, then pop that many variables on exit. Local variable access is then relative to whatever the depth was when you entered the function.

If you do need to optimize dictionary lookups (e.g. because dynamic-scope lookups are hot in typical code), there are a few techniques. The first is that you should intern your identifiers so that you can (1) avoid storing a hundred different copies of the identifier in your AST and (2) do lookups based on the unique string pointer instead of having to do a string hash every time. The second is just dynamic caching: a hot dynamic lookup will probably happen with the same functions on the call stack, so whenever you have to do the lookup, try to remember how you resolved it and then set things up so that you can quickly validate that you have the same caller stack and then immediately jump to the right offset. There are several ways to optimize that further, but don't bother unless it's important.

rjmccall fucked around with this message at 06:24 on Mar 5, 2019

fritz
Jul 26, 2003

peepsalot posted:

This seems

I read your post as suggesting you weren't sure that this is a major part of the runtime, and just wanted to say please be sure to profile before you put in a lot of time doing optimizations.

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

rjmccall posted:

Optimizing the representation of the local-variables dictionary shouldn't be particularly important for you because it should be an aim of your implementation to avoid ever doing lookups during actual evaluation. What you can achieve here depends a lot on the semantics of your language, but the right first step is generally to flatten all of your locals into a vector and resolve all locals to an offset into that vector at parse-time. Keep track of how many local variables there are, and whenever you parse a new local variable, store the current count in the dictionary for that name and then increment the count. If locals have sensible lifetime (unlike, say, in JS), you can reset the count whenever you pop a scope. The key thing is that now you can write that index down in the AST instead of the variable name, both for the declaration and for any references to it, so you won't have to do a dictionary lookup during evaluation. In a totally lexically-scoped language, you can actually throw the dictionary away completely after parsing; if you have dynamic-scoping features, you will need to preserve the dictionary to support dynamic lookups, but at least you won't be doing that for every local-variable reference.

The next step beyond that is to recognize that you don't need to allocate a separate vector of locals for every call because you can just use a single vector as a stack. In the parser, you remember the max number of locals that were alive at once, and then on function entry you just need to push space for that many variables, then pop that many variables on exit. Local variable access is then relative to whatever the depth was when you entered the function.

If you do need to optimize dictionary lookups (e.g. because dynamic-scope lookups are hot in typical code), there are a few techniques. The first is that you should intern your identifiers so that you can (1) avoid storing a hundred different copies of the identifier in your AST and (2) do lookups based on the unique string pointer instead of having to do a string hash every time. The second is just dynamic caching: a hot dynamic lookup will probably happen with the same functions on the call stack, so whenever you have to do the lookup, try to remember how you resolved it and then set things up so that you can quickly validate that you have the same caller stack and then immediately jump to the right offset. There are several ways to optimize that further, but don't bother unless it's important.
Thanks for this, it looks like I have to long way to go to convert this into a proper compiler-sorta-thingy. I'm still thinking about how I could implement this sort of thing in the current code base, but I feel like I have a better general understanding of the way to do it. I guess this indexing you describe would generally be considered a "symbol table"? That seems to be what the project is lacking.

fritz posted:

I read your post as suggesting you weren't sure that this is a major part of the runtime, and just wanted to say please be sure to profile before you put in a lot of time doing optimizations.
No, I'm sure that lookups slow things down considerably in some cases. I have done a lot of profiling. I just meant "it seems like there should be a better way".

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
"Symbol table" is a pretty general term for any sort of lookup table in a language implementation. Your current identifier-to-current-value dictionary is totally a symbol table.

The right way to start is probably to do some silly refactor of the symbol table, like maybe rename the field that holds the dictionary and any obvious methods that access it. You don't have to commit that change, just make sure you know about all the code that accesses the table and (more importantly) why it accesses the table and what it's trying to do. What you're doing at this point is trying to understand the semantics of the language you're working on, and if you're working with some ad hoc, hacked-up scripting language that's going to be a pretty big step because those semantics might be pretty wild, and more importantly they might be pretty different from what you think the semantics are.

The next step beyond that is to just do what I said before, but without the phase distinction: that is, make the dictionary an unordered_map<string, int> that's stored right next to a vector<Value>. That's not an optimization — it's really a pessimization — but if you can get that working, the next step is just to try to build that dictionary "statically" when you parse the code.

Incidentally, it sounds like what you have here is an interpreter, not a compiler. Compilation means translating a programming language into some independently-specified language, often (but not always) a machine language (but possibly a virtual machine like the JVM or CLR). The line between that and an interpreter can get pretty blurry, but usually we call something an interpreter if it only translates the code into some fairly faithful representation of the original source which it can evaluate directly. If you've still got a symbol table and an expression tree when you're executing code, you've got an interpreter.

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

rjmccall posted:

"Symbol table" is a pretty general term for any sort of lookup table in a language implementation. Your current identifier-to-current-value dictionary is totally a symbol table.

The right way to start is probably to do some silly refactor of the symbol table, like maybe rename the field that holds the dictionary and any obvious methods that access it. You don't have to commit that change, just make sure you know about all the code that accesses the table and (more importantly) why it accesses the table and what it's trying to do. What you're doing at this point is trying to understand the semantics of the language you're working on, and if you're working with some ad hoc, hacked-up scripting language that's going to be a pretty big step because those semantics might be pretty wild, and more importantly they might be pretty different from what you think the semantics are.

The next step beyond that is to just do what I said before, but without the phase distinction: that is, make the dictionary an unordered_map<string, int> that's stored right next to a vector<Value>. That's not an optimization — it's really a pessimization — but if you can get that working, the next step is just to try to build that dictionary "statically" when you parse the code.

Incidentally, it sounds like what you have here is an interpreter, not a compiler. Compilation means translating a programming language into some independently-specified language, often (but not always) a machine language (but possibly a virtual machine like the JVM or CLR). The line between that and an interpreter can get pretty blurry, but usually we call something an interpreter if it only translates the code into some fairly faithful representation of the original source which it can evaluate directly. If you've still got a symbol table and an expression tree when you're executing code, you've got an interpreter.
Well I guess I've been wishy washy on the definition because it does "compile" the source script down to what is essentially a data interchange format, but is also a subset of the main language. No control structures or expressions remain, basically just literal values. But I guess it doesn't qualify as "independently-specified" since I think this is currently the only program that uses that format.

General_Failure
Apr 17, 2005
I really need some help. I've been actively avoiding this for many months. It's just a personal project but it has been a de-motivational source.

I'm trying to write a very basic graphics driver module. Just enough to talk to the hardware. What's been hanging me up is a neat way to access the registers. Doing the whole memory mapped IO range and getting the pointer is easy. Having neat way to access the registers is not. At least to me.

Here's a section of one of the smaller register blocks, pulled from the manual to illustrate my issue.

code:
OVL_UI_ATTCTL   0x000 + N*0x20
OVL_UI_MBSIZE   0x004 + N*0x20
OVL_UI_COOR      0x008 + N*0x20
OVL_UI_PITCH     0x00C + N*0x20
OVL_UI_TOP_LADD 0x010 + N*0x20
OVL_UI_BOT_LADD 0x014 + N*0x20
OVL_UI_FILL_COLOR 0x018 + N*0x20
OVL_UI_TOP_HADD 0x080
OVL_UI_BOT_HADD 0x084
OVL_UI_SIZE 0x088
Formatting is probably crap, but it doesn't really matter. N is 0 to 3 on this. Most of the blocks are larger than this, with sections of that PITA arithmetic.
I have tried plopping struct on them, but I just don't know how to deal with the odd gaps and such.
My other options as I see it are good old pointer arithmetic or treating the pointer as an array.

Normally this kind of thing would be done in assembly, but I am actively trying to avoid it. TBQH it would be easier, but I'd rather accessibility.

How should I go about dealing with these registers?

Semi related. there's another part of the video hardware that uses byte addressable registers. This makes me rather cranky because it's on an ARM platform so there's alignment issues to deal with. It's clumsy. In the past I have addressed it with assembly, but I'm thinking treating the pointer as an array of uint8_t and letting the C compiler sort it out. Would that make sense?

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
When you say gaps, are those gaps with other things in them that you don't want to alias, or just unused space? You can used a packed struct with filler bytes there if they're just addresses that nothing uses. You can define macros/inline functions that give you the address of the nth version of this register.

Can you show an example with a gap you're worried about? I don't even necessarily need the whole thing. Fundamentally it's going to be repetitive to type in register addresses one by one but that's life, there's no way to solve this without telling the compiler the addresses or how to compute them, but it will do most of the arithmetic for you if you let it.

General_Failure
Apr 17, 2005

Jeffrey of YOSPOS posted:

When you say gaps, are those gaps with other things in them that you don't want to alias, or just unused space? You can used a packed struct with filler bytes there if they're just addresses that nothing uses. You can define macros/inline functions that give you the address of the nth version of this register.

Can you show an example with a gap you're worried about? I don't even necessarily need the whole thing. Fundamentally it's going to be repetitive to type in register addresses one by one but that's life, there's no way to solve this without telling the compiler the addresses or how to compute them, but it will do most of the arithmetic for you if you let it.

I can show examples, jut not at this moment. There's nothing proprietary about what I'm doing. I already have quite a lot of definitions for the SBC hardware typed out. I'm no stranger to sessions of nothing but making headers etc.

The point I'm at is I have an operating system that boots, supports the GIC, MMU, USB and some other things, so there's a fair bit there already. It's been stagnating for months though because of the stupid video support. It's a hodgepodge.

I'm just trying to find a method that doesn't cause nightmares.

I think the following url is for the PDF download page, not the PDF itself. http://linux-sunxi.org/File:Allwinner_DE2.0_Spec_V1.0.pdf

Around section 5.10.4, page 90 and beyond are where the register definitions start.

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
Something like this seems fine:
code:
struct OVL_UI_LOWER {
	uint32_t ATTCTL;
	uint32_t MBSIZE;
	uint32_t COOR;
	// etc
	uint32_t FILL_COLOR;
	uint32_t unused;
};

struct OVL_UI_LOWER *ovl_ui_lower = regbase + OVL_UI_LOWER_BASE;
Then you can access ovl_ui_lower like a pointer to 4 of them. That doesn't seem so bad to me - you have to come up with names for the sections that don't have great names, and hardcode all the one-off ones, but that's inevitable. Alternatively you can use a function to add the base pointer to a specific offset, and call that function everywhere you access a register. I've seen big piles of macros of stuff like this to too if you always know at compile time which one you're accessing.

Jeffrey of YOSPOS fucked around with this message at 02:04 on Mar 7, 2019

Jeffrey of YOSPOS
Dec 22, 2005

GET LOSE, YOU CAN'T COMPARE WITH MY POWERS
Oh and there are packed bitfields within these - you can do bit-packed structs as well if you need to, though you should verify your compiler is doing it how you expect. I see why that can be a pain in the rear end.

General_Failure
Apr 17, 2005

Jeffrey of YOSPOS posted:

Oh and there are packed bitfields within these - you can do bit-packed structs as well if you need to, though you should verify your compiler is doing it how you expect. I see why that can be a pain in the rear end.

I'm not too concerned about filling unused areas. I have scripts for that. What does concern me is an ARM developer information page I saw http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka3750.html a little over halfway down it warns against using packed structs.
Using structs would make it all nice and readable, although I have to admit I struggle with the concept of how to nest struct pointers. For example the struct which you posted. I actually have similar looking ones in my code for the most part.
For example, we have four OVL channels. It'd be a shame not to recycle the structure to do each of these.
Within them we have the normal registers, and the mathemagical ones with N = 0 - 4 which could also be placed in their own struct and put within the main OVL struct.
Also remember that OVL is just one register block in a larger lot of registers (which is only one of a few subsystems).

My concern is how messy it gets because of the inconsistent grouping of registers.

Qwertycoatl
Dec 31, 2008

If at all possible, get the register definitions as a header supplied by the hardware manufacturer, instead of pasting from the PDF. It's easy to make a very confusing mistake, and also it wouldn't be unusual for the PDF to be wrong too.

However you do it, you have to be reading/writing through something volatile or the compiler might skip out accesses that it shouldn't.

I've normally seen it done something like this:
code:
#define OVL_UI_FILL_COLOR_BASE 0x018 
#define OVL_UI_FILL_COLOR_SKIP 0x20
#define OVL_UI_TOP_HADD 0x080

#define write_reg(reg, val) do { *(volatile uint32_t*)(reg) = value; } while (0)
#define write_reg_idx(reg, idx, val) do { *(volatile uint32_t*)(reg ## _BASE + idx * reg ## _SKIP) = value; } while  (0)
(with all the register address #defines off in their own file, autogenerated from some machine-readable description of the hardware supplied by the manufacturer)

Adbot
ADBOT LOVES YOU

General_Failure
Apr 17, 2005

Qwertycoatl posted:

If at all possible, get the register definitions as a header supplied by the hardware manufacturer, instead of pasting from the PDF. It's easy to make a very confusing mistake, and also it wouldn't be unusual for the PDF to be wrong too.
That would be nice. Not going to happen though.

quote:

However you do it, you have to be reading/writing through something volatile or the compiler might skip out accesses that it shouldn't.
Don't worry. I'm careful with that.

quote:

I've normally seen it done something like this:
code:
#define OVL_UI_FILL_COLOR_BASE 0x018 
#define OVL_UI_FILL_COLOR_SKIP 0x20
#define OVL_UI_TOP_HADD 0x080

#define write_reg(reg, val) do { *(volatile uint32_t*)(reg) = value; } while (0)
#define write_reg_idx(reg, idx, val) do { *(volatile uint32_t*)(reg ## _BASE + idx * reg ## _SKIP) = value; } while  (0)
(with all the register address #defines off in their own file, autogenerated from some machine-readable description of the hardware supplied by the manufacturer)

I have pages and pages of stuff like that, but hand typed and in many cases collated by myself. The collection of various documents I have is the closest thing I have.

For the OS interfacing part of the driver I was making a struct today. Should have read the whole definition first. 15 * 32b integers ...+N pairs of extra params with a -1 terminator. Yeahh. Going to deal with that the old fashioned way. Can't be fancy with that.
Everything was written in assembly and intended to interact with other similarly written things. The vast majority of my code is in assembly too. At least it's all APCS-32 compliant.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply