Live data from Hacker News

Subroutine calls in the ancient world, before computers had stacks or heaps

devblogs.microsoft.com

131–140 of 241 posts

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#131

In the @let feature in Enhanced GNU Awk, for those @let blocks that are outside of a function, like in BEGIN or END blocks, I have the compiler allocate secret global variables. They are reused as much as possible between blocks. $ ./gawk --dump-variables 'BEGIN { @let (a, b, c = 1) { } }' $ cat awkvars.out $let0001: untyped variable $let0002: untyped variable $let0003: 1 ARGC: 1 ARGIND: 0 ARGV: array, 1 elements BIN…

That website doesn't work from my ISP. Can't even ping it or nc -z 104.37.63.7 443.

Edit update: Your security infrastructure is broken because I don't know what that is and don't use Twitter. If you check the AS, it's Google Fiber. And I'd appreciate it if you wouldn't dox me.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#132
post #71

Earlier quoted context omitted.

Recursion in production code is bad news, because you can't control the depth of your call tree. At some point you will crash because the runtime won't be able to allocate any more stack. And you can't preflight it because if you could preflight it you wouldn't be doing recursion. Recursion is a nice toy, but in real life it's a ticking time bomb.

> Recursion in production code is bad news, because you can't control the depth of your call tree. Of course you can, if you wanted to , just like you can control the iteration count of a loop. It's not even hard. This is simply a non-issue. Some algorithms are much more naturally expressed recursively and writing the imperative equivalent with manual stack handling is just annoying. Stack growth is just something yo…

From my perspective, the main issue it causes (besides flood risks) is an overlapping state dependency that can process-bind large areas of fragmented memory.

So, if you try to unroll naive recursive code, than one wins n-many issues instead of 1 predictably separable one. Sure one could pass in a mutex etc., but it is back to using a global again and a single-core bounded context.

Best of luck, =)

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#133

Earlier quoted context omitted.

"Recursion is fine [for your use-case]." In general it is naive, often dangerous, and an inefficient space/time trade-off. I have been writing software for several decades... does that make one less insightful or more biased? https://youtu.be/pmu5sRIizdw?feature=shared&t=31 =)

For a fun time, make a nested json blob about 1000 layers deep[1] and in python: with open('lulz.json') as ayy_lmao: oops = json.load(ayy_lmao) If you parse arbitrary json bytes from the Internet (for example, if you have some public Python API with no auth) then you have given the world a fun little stacktrace generator, and a way to lessen your server room's heating bill. EDIT: btw you can do the same thing in rust…

Parsing untrusted input on a machine where failure could affect someone other than the person who provided the input is definitely a known case to be mindful of.

You'll want to cap the maximum allowed nesting depth. Even if not using recursion, you probably don't want untrusted input to be able to make your stack data structure allocate arbitrary amounts of memory.

If you do put a nesting limit in, you can do that equally well using an actual stack or recursion. (For example, I believe v8 still uses recursive descent for parsing JSON and JavaScript. It detects and handles stack overflows to ensure that untrusted input can't blow the stack and crash. I'm not sure if it's using a hardcoded nesting limit or actually trapping the stack/heap collision somehow.)

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#134
post #71

Earlier quoted context omitted.

Recursion in production code is bad news, because you can't control the depth of your call tree. At some point you will crash because the runtime won't be able to allocate any more stack. And you can't preflight it because if you could preflight it you wouldn't be doing recursion. Recursion is a nice toy, but in real life it's a ticking time bomb.

> Recursion in production code is bad news, because you can't control the depth of your call tree. Of course you can, if you wanted to , just like you can control the iteration count of a loop. It's not even hard. This is simply a non-issue. Some algorithms are much more naturally expressed recursively and writing the imperative equivalent with manual stack handling is just annoying. Stack growth is just something yo…

> This is simply a non-issue.

Well, it's about the tradeoffs right? If I have a recursive algorithm that's growing the stack (assuming no TCO, because few languages people actually use in production support it) I'm trading execution time, space, and reliability for economy of expression. In reverse order:

- reliability: if, as you suggest, I implement some hard depth limit (which is necessary because all the processes which are running concurrently need to not exceed my max stack depth), and assuming generally that things grow over time (more users, more concurrent processes, more recursive calls needed to get the job done) we face two issues. (1) theres a complicated relationship between the maximum number of concurrent processes and the maximum allowable recursion depth. Getting it wrong could crash the entire program! If this is a web server that's means we just killed a whole bunch of connections all at once. (2) eventually, over time, we'll need to raise the recursion limit, which entails rebalancing the concurrency limit. Hard walls like this are bad news in systems. If instead this was implemented iteratively the system would degrade softly as the iteration count grows--each process would take longer to complete, but they'd still all complete. Assuming I'm monitoring process execution time I can predict and respond to this proactively instead of being faced with an emergency where the system is just completely broken.

- space: this is obvious I guess, more stack frames == more memory. The problem is worse in some languages than others.

- time: this may be less obvious, and there may be optimizations which render it false, but generally in my experience iterative code gets pipelined better and runs quicker.

> Stack growth is just something you don't have to worry about for almost all scenarios you're likely to encounter.

I guess that depends on the situation. I've encountered hard walls and performance issues from recursion enough times in my career thus far that I make the extra effort to avoid it. I could totally see the value, though, in areas where you know ahead of time how the recursion depth will scale over time. More often than not, though, that's unknowable at implementation time so better err on the side of caution.

EDIT: upon re-reading this I think it might have been clearer if I insted wrote "task" every time I wrote "process"--I'm not talking specifically about any OS feature.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#135

Earlier quoted context omitted.

For a fun time, make a nested json blob about 1000 layers deep[1] and in python: with open('lulz.json') as ayy_lmao: oops = json.load(ayy_lmao) If you parse arbitrary json bytes from the Internet (for example, if you have some public Python API with no auth) then you have given the world a fun little stacktrace generator, and a way to lessen your server room's heating bill. EDIT: btw you can do the same thing in rust…

Parsing untrusted input on a machine where failure could affect someone other than the person who provided the input is definitely a known case to be mindful of. You'll want to cap the maximum allowed nesting depth. Even if not using recursion, you probably don't want untrusted input to be able to make your stack data structure allocate arbitrary amounts of memory. If you do put a nesting limit in, you can do that eq…

You mean like the reply depth limit in forums like YC.

Shouldn't you stick to a depth-first branch reply limit until moving to the next fork.

I want to respect your beliefs. =)

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#136

Earlier quoted context omitted.

For a fun time, make a nested json blob about 1000 layers deep[1] and in python: with open('lulz.json') as ayy_lmao: oops = json.load(ayy_lmao) If you parse arbitrary json bytes from the Internet (for example, if you have some public Python API with no auth) then you have given the world a fun little stacktrace generator, and a way to lessen your server room's heating bill. EDIT: btw you can do the same thing in rust…

Parsing untrusted input on a machine where failure could affect someone other than the person who provided the input is definitely a known case to be mindful of. You'll want to cap the maximum allowed nesting depth. Even if not using recursion, you probably don't want untrusted input to be able to make your stack data structure allocate arbitrary amounts of memory. If you do put a nesting limit in, you can do that eq…

Yes, unfortunately in the python case the standard library's json parser has no way to configure a reasonable limit. It'll just consume stack frames until it hits the interpeter's recursion limit. And, as has been mentioned elsewhere, python stack frames are expensive for a variety of reasons.

I've run into similar issues with recursive code in java. The story gets better in C or rust, but there wre still sharp edges. I guess there are sharp edges everywhere though...

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#137
post #125

This post happens to describe the world of the famous “Goto considered harmful” paper. Most people just know the title and simply fetishistically refuse to use a goto (most of the time they are terrible, but not always). But really the paper argues for giving subroutines a single entry point. Sometimes I write assembly code that falls through into another subroutine, but I don’t want all my assembly to be spaghetti l…

While those are of the same era, the goto issue was its own whole issue, generally referring to goto statements in a give block of code, and not talking about subroutines.

While this, as you note, often led to mass refusal to use a goto, the effect of the paper led to much discussion and presaged the practice of much better control flow constructs in languages.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#138

> As I recall, some processors stored the return address at the word before the first instruction of the subroutine. Yep, that's what the PDP-8 did. The evolution of the PDP-8 is arguably a journey in hardware support for recursion. Initially the JMS instruction stuck the return address in the first word of the function (as an aside, a lot of time caller would put it's arguments after the JMS instruction, and the cal…

As did the IBM 1800 and IBM 1130 and many machines of that era. Machines such as the Xerox Sigma series had enough registers to avoid this practice.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#139

I've been doing functional programming so long that I genuinely have a hard time thinking about how I would write things without recursion. Like, I technically know how to convert a recursive algorithm to an iterative one, I've done it before on more resource-constrained stuff, but I don't like it. I think the recursive stuff is generally prettier and for 99% of things it's fast enough (and 100% if your compiler supp…

Yes, instruction sets these days are significantly more useful.

To do recursion in those old machines, one would need to build your own stack mechanism, but there would still be issues to take care of, as there was no native way to use anything but global storage.

Having lived through those times, I don't wish them on anyone.

Re: Subroutine calls in the ancient world, before computers had stacks or heaps

#140

"There was just one catch: You can't do recursion." You could do tail recursion, because only the return address of the first call would be needed to be stored. `branch_with_link` would be used for the initial call, but the recursive calls would have to be regular branches.

Yes, but you do need to handle local storage.
Post reply on HN