....is love, sweet love. That's the only thing that there's just too little of.
At least according to Hal David, and Burt Bacharach.
My original Concertina architecture was originally intended just to illustrate how computers work, and then I decided to illustrate
almost every feature some computer somewhere ever had by throwing them
all in.
Concertina II was intended to be a bit more practical. But it wasn't
really designed primarily for commercial success.
I designed it to show what I would like to see - so it is still going
to have lots of features, if not _quite_ as many as the original
Concertina. IBM-format Decimal Floating Point. Character string
instructions. Cray-like vector instructions.
I threw in a block structure to allow pseudo-immediates, since I
agreed with Mitch that using immediates for constants is a good
idea with memory access being so slow these days. Even if the
way that made sense for me to implement them was one Mitch
quite reasonably found really awful.
And I also put in VLIW - based on the arguments in favor of the
Mill architecture, I felt that if being lightweight and avoiding
OoO but still being efficient is a good thing, then offering
this capability in a more conventional architecture, less
radically innovative than the Mill, might serve a purpose.
But none of this stuff is what people really want to see from
a new computer architecture. (In fact, they don't _want_ a
new computer architecture, they want to run their old Windows
programs in peace!)
What new improved capability _would_ have people beating
down the doors to get their hands on processors with a novel
architecture? To me, it's obvious. If somebody could
design a processor that would power computers that were
*much more secure* than today's computers, *that* is what
would be extremely attractive.
Is the problem really in the processor, though? Maybe the
problem is mostly in the software. Perhaps hardware outside
the processor - like having one hard disk read-only unless
a physical switch was turned to allow software to be installed
on drive C: - is what we need.
Mitch Alsup noted that on his design, there is no bit in the
status word that gets turned on to allow supervisor or privileged
or kernel mode; instead, program code allowed to run privileged
instructions is specified as such within the memory management
unit.
To me, it seemed like this approach had a fundamental problem,
although I'm sure Mitch figured out a way to deal with it.
Because you can always turn privileged mode off, but never on,
except through an interrupt or an interrupt-like mechanism
such as a supervisor call, when the computer is turned on, it
has to start from privileged mode. And memory management is
complicated, and needs to be set up before it can be used.
A workaround is to make 'raw' memory capable of running
privileged instructions, but then that means people who
don't want to bother with memory management don't have a
security mechanism.
So, while I felt his idea was useful, I would be tempted
to do things differently: have a privilege bit, but then
also have a feature that can be enabled that only allows
privileged code to run in code segments designated by the
memory descriptors given to the MMU.
Since the most insidious attack on a computer is to corrupt
its BIOS, or the boot sectors of its hard drive, a security
mechanism that could work when a computer is started up
would be attractive.
Modern microprocessors include the feature of executing
encrypted programs; the purpose of this is to allow
copy-protection schemes for things like movies and music
to work; the code is hidden from being disassembled, and
can't be tampered with or have its functionality duplicated.
This doesn't help security, though; a virus writer could
use the same public keys of the microprocessor maker
to hide parts of a virus from analysis. (But when something
is known to be a virus, and not an implementation of HDMI
encryption, the microprocessor maker will gladly decrypt
it for antivirus developers...)
In any case, this has inspired me to think of a feature to
add to Concertina II. Add a new kind of header block, which
must be the very first one, which contains an encrypted
checksum of the rest of the block - which must be valid for
the block to be allowed to execute. A mode exists where
only blocks with such a checksum can be executed.
The idea is to provide the following facilities:
Supply the computer with an encryption key.
Set the computer so that on bootup it is in the
mode only allowing execution of this checksummed code.
Quadibloc wrote:
In any case, this has inspired me to think of a feature to
add to Concertina II. Add a new kind of header block, which
must be the very first one, which contains an encrypted
checksum of the rest of the block - which must be valid for
the block to be allowed to execute. A mode exists where
only blocks with such a checksum can be executed.
This is one reason you cannot allow writable and executable
in the same PTE. My 66000 HW checks this.
This is one reason you cannot allow writable and executable in the same
PTE. My 66000 HW checks this.
On Thu, 14 Dec 2023 18:34:10 +0000, MitchAlsup wrote:
This is one reason you cannot allow writable and executable in the same
PTE. My 66000 HW checks this.
This is a very sound principle.
On Thu, 14 Dec 2023 18:34:10 +0000, MitchAlsup wrote:
This is one reason you cannot allow writable and executable in the same
PTE. My 66000 HW checks this.
This is a very sound principle.
Of course, computers must allow executable programs to be loaded into
memory from the disk, but that just means that, now, loader programs
need to include a call to the operating system to change the status of
the memory into which a program has been loaded.
So the effect of this elementary precaution is to break any legacy
programs that do JIT compilation, for example. So this interferes with
making the x86/x86-64 platform secure.
Which helps to remind me of a very important fact: while I mentioned
adding a very _minor_ security feature to Concertina II here, which
happens to make use of the block format of program code, to permit
making certain types of fine-grained security a bit more convenient...
what is the really _important_ security enhancement that computers need?
In my opinion, it's better, more effective, sandboxing of Web browsers, E-mail clients, and other Internet-facing applications. And when I've
been previously thinking about this, I noticed that one class of
applications - which is not connected with most of the security risk -
stands in the way of using some possible mechanisms for this purpose.
A lot of computer games - a class of applications which typically
require access to the full computational power of the computer - also
are connected to the Internet, so that players can interact and can't
cheat because actual gameplay takes place on servers, while the game
clients take care of game graphics.
If it's just browsers and E-mail clients, having those run in a
"sandbox" that also has a lot less performance than the real computer
is not a problem.
Is the CPU even the place for sandboxing? A genuinely effective
sandbox would involve a physical separation between the protected
computer and the one connected to the Internet, after all. But that
isn't convenient...
John Savard
Quadibloc wrote:
Is the CPU even the place for sandboxing? A genuinely effective sandbox
would involve a physical separation between the protected computer and
the one connected to the Internet, after all. But that isn't
convenient...
With 10-cycle context switches, you can run the sandbox where those
cores are only provided indirect access to the internet through an
IPI-like mechanism (also 10-cycles).
When a real hard context switch remains 10-cycless, you an run the
secure sandbox under a different HyperVisor that provides no illusion of internet access. Still 10-cycles.
If using a *virtual machine* won't give you a secure sandbox, then I
despair of anything else giving you a secure sandbox on a single
computer.
Not supporting RWE as a valid PTE permission won't stop anything. It
just means that on My66k an OS has to use trap and emulate to a
accomplish the same end result. All that does is make certain things
more expensive on that platform.
Whether to support RWE is a decision that should be left to the OS, implemented through its privileging mechanisms and authorized on a
per-user or per-app basis for individual memory sections.
On Fri, 15 Dec 2023 09:53:14 -0500, EricP wrote:
Not supporting RWE as a valid PTE permission won't stop anything. It
just means that on My66k an OS has to use trap and emulate to a
accomplish the same end result. All that does is make certain things
more expensive on that platform.
Whether to support RWE is a decision that should be left to the OS,
implemented through its privileging mechanisms and authorized on a
per-user or per-app basis for individual memory sections.
Although I tend to agree with you, I also think that allowing write permission and execute permission at the same time is highly
dangerous. So I would both deprecate that option, and put extra
security safeguards around it so that if malware found a vulnerability
in the operating system, it still wouldn't be able to turn that mode
on.
Here, making it so that once access to write plus execute is turned off,
it can't be turned on again without a reboot seems appropriate. (Or "potentially on", because forcing the system to boot with this enabled
isn't a good idea either. So there would be three states: RWE available,
but off; RWE on; RWE turned permanently off until reboot. From the third state, there is no return to the first two.)
John Savard
Although I tend to agree with you, I also think that allowing write >permission and execute permission at the same time is highly
dangerous.
Quadibloc wrote:
Here, making it so that once access to write plus execute is turned
off,
it can't be turned on again without a reboot seems appropriate. (Or
"potentially on", because forcing the system to boot with this enabled
isn't a good idea either. So there would be three states: RWE
available,
but off; RWE on; RWE turned permanently off until reboot. From the
third state, there is no return to the first two.)
Did you take a course in customer abusive design or something?
Who is it you think you are going to sell this feature to?
Quadibloc <quadibloc@servername.invalid> writes:
Although I tend to agree with you, I also think that allowing write >>permission and execute permission at the same time is highly dangerous.
Security theatre!
Gforth uses mmap(... RWX ...) for the area where it generates native
code. Now on MacOS on Apple Silicon (MacOS on Intel works fine) that
mmap() fails, which means that currently the development version of
Gforth does not work. When I work around this misfeature of MacOS on
Apple Silicon (by disabling native code generation), Gforth will run
around a factor of 2 slower on MacOS than on Linux.
On Sat, 16 Dec 2023 11:54:40 -0500, EricP wrote:
Quadibloc wrote:
Here, making it so that once access to write plus execute is turned
off,
it can't be turned on again without a reboot seems appropriate. (Or
"potentially on", because forcing the system to boot with this enabled
isn't a good idea either. So there would be three states: RWE
available,
but off; RWE on; RWE turned permanently off until reboot. From the
third state, there is no return to the first two.)
Did you take a course in customer abusive design or something?
Who is it you think you are going to sell this feature to?
Well, Mitch proposes making it completely and absolutely impossible
for memory to both contain code that can currently be executed, and
for it to be writeable, at the same time.
I recognized the logic behind this. Yet, there are cases when this
would be a problem. So I felt that an option might be to dial the
security just a tiny notch down:
Make it _possible_ to have an operating system that did allow
read and execute at the same time, but _also_ make it possible for
an operating system not to allow it - in a way that can't be
subverted. The only way to change that being disabled is to reboot
into a different operating system that does allow it.
John Savard
So perhaps we need to dedicate _another_ pin on the CPU to mandating
another security mode. If the user wishes to only run operating systems
where write plus execute is hard disabled, then... ground that pin or whatever, and it tells the CPU:
do not allow exiting the checksummed blocks only mode unless write plus execute has been disabled!
At the same time, there might be a demand for having both write and
execute in some cases - such as GForth. So I decided on allowing write/execute to be disabled - with re-enebling it requiring a reboot.
Those who fear write/execute because of security then have a situation
that's almost as good as the processor not supporting it at all, but
those who want it have the option of using an operating system that runs
with it allowed.
But hackers have been able to get around all sorts of security features
in computers.
Context switching in 10-cycles is the cure to the perceived problems.
On Sat, 16 Dec 2023 17:33:48 +0000, Anton Ertl wrote:
Quadibloc <quadibloc@servername.invalid> writes:
Although I tend to agree with you, I also think that allowing write >>>permission and execute permission at the same time is highly dangerous.
Security theatre!
Gforth uses mmap(... RWX ...) for the area where it generates native
code. Now on MacOS on Apple Silicon (MacOS on Intel works fine) that
mmap() fails, which means that currently the development version of
Gforth does not work. When I work around this misfeature of MacOS on
Apple Silicon (by disabling native code generation), Gforth will run
around a factor of 2 slower on MacOS than on Linux.
This is an argument you need to have with Mitch, rather than
with me.
I lack the expertise to really assess how valuable not allowing
write and execute at the same time is; it certainly seems potentially >dangerous, so I went along with Mitch.
The situation on older computers, where being able to write and
execute together was just the norm, makes things easier for malware
to tamper with code.
If, instead, you have to
request that new blocks of memory be created with write/execute, then
JIT programs can run, but this can't be used to tamper with existing code
in memory - maybe that's good enough, and anything more is just
security theatre.
Well, I've _already_ noted that *a pin on the CPU* might be used to
cause it to start up in a mode where it will refuse to execute ANY >instructions unless they're in blocks protected by encrypted
checksums!
Quadibloc <quadibloc@servername.invalid> writes:
The situation on older computers, where being able to write and
execute together was just the norm, makes things easier for malware
to tamper with code.
It is the norm.
How does it make things easier for attackers to tamper with code? For
15-20 years, malloc() has allocated everything RW (no X), data and BSS segments are RW, and code segments are RX. So any plain C program
(and stuff based on that) never sees RWX memory.
An example: executable stacks are the norm on Linux, for realizing trampolines (gcc parlance for pointers to nested functions).
See https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html .
The point I was making is that in the case of Gforth, it does not add
any danger that is not already there.
On Sun, 17 Dec 2023 08:16:23 +0000, Anton Ertl wrote:
The point I was making is that in the case of Gforth, it does not add
any danger that is not already there.
I'm not sure of the relevance of that objection.
Nobody is accusing GForth of having bugs in it. The problem is that
the feature of the CPU that is needed for GForth to run efficiently -
having memory to which both write and execute access is permitted
at the same time - is dangerous.
It means the
computer is more open to attacks which don't involve the application
GForth at all.
So, an open question: How does one create a pointer to a nested
function in architectures which disallow rwx pages?
An example: executable stacks are the norm on Linux, for realizing >trampolines (gcc parlance for pointers to nested functions).
See https://gcc.gnu.org/onlinedocs/gccint/Trampolines.html .
I believe that gcc now uses heap-allocated rwx pages on Darwin,
which I believe is allowed (but I would have to check details
to be sure).
So, an open question: How does one create a pointer to a nested
function in architectures which disallow rwx pages? Standard C
does not allow this, but languages like Fortran do, as does a
gcc extension (which clang does not support, AFAIK).
According to Thomas Koenig <tkoenig@netcologne.de>:
So, an open question: How does one create a pointer to a nested
function in architectures which disallow rwx pages?
You make pointers two words, one for the code, one that points to the
display for the dynamic data. This has been a standard compiler
technique since the late 1960s, and indeed the GCC page you pointed to
calls them descriptors. In the fine tradition of GCC overimplmenting everything, they set bits in descriptors to deliberately misalign them
and indirect calls check to see how much indirection to do.
For a particularly painful example of nested functions, see Knuth's Man or Boy test
https://rosettacode.org/wiki/Man_or_boy_test
On Sat, 16 Dec 2023 11:54:40 -0500, EricP wrote:
Quadibloc wrote:
Here, making it so that once access to write plus execute is turned
off,
it can't be turned on again without a reboot seems appropriate. (Or
"potentially on", because forcing the system to boot with this enabled
isn't a good idea either. So there would be three states: RWE
available,
but off; RWE on; RWE turned permanently off until reboot. From the
third state, there is no return to the first two.)
Did you take a course in customer abusive design or something?
Who is it you think you are going to sell this feature to?
Well, Mitch proposes making it completely and absolutely impossible
for memory to both contain code that can currently be executed, and
for it to be writeable, at the same time.
I recognized the logic behind this. Yet, there are cases when this
would be a problem. So I felt that an option might be to dial the
security just a tiny notch down:
Make it _possible_ to have an operating system that did allow
read and execute at the same time, but _also_ make it possible for
an operating system not to allow it - in a way that can't be
subverted. The only way to change that being disabled is to reboot
into a different operating system that does allow it.
John Savard
I'm objecting to the notion of forcing a processor to reboot
for any reason, let alone in order to change something that is
under control of the operating system. There was a time before PC's
when computers normally ran for a year without reboot and never crashed.
As for Mitch's RW-no-E restriction, I am confident it will not survive
its first encounter with actual customers (where philosophy collides
with income).
EricP <ThatWouldBeTelling@thevillage.com> schrieb:
I'm objecting to the notion of forcing a processor to reboot
for any reason, let alone in order to change something that is
under control of the operating system. There was a time before PC's
when computers normally ran for a year without reboot and never crashed.
Quadibloc <quadibloc@servername.invalid> writes:
On Sat, 16 Dec 2023 17:33:48 +0000, Anton Ertl wrote:
Quadibloc <quadibloc@servername.invalid> writes:
Although I tend to agree with you, I also think that allowing write >>>>permission and execute permission at the same time is highly dangerous.
Security theatre!
Gforth uses mmap(... RWX ...) for the area where it generates native
code. Now on MacOS on Apple Silicon (MacOS on Intel works fine) that
mmap() fails, which means that currently the development version of
Gforth does not work. When I work around this misfeature of MacOS on
Apple Silicon (by disabling native code generation), Gforth will run
around a factor of 2 slower on MacOS than on Linux.
This is an argument you need to have with Mitch, rather than
with me.
This is a newsgroup, he can read and react on my posting as well as
you and others.
I lack the expertise to really assess how valuable not allowing
write and execute at the same time is; it certainly seems potentially >>dangerous, so I went along with Mitch.
The point I was making is that in the case of Gforth, it does not add
any danger that is not already there. If you let an untrusted user
run Gforth, it's like allowing that user to run non-suid binaries that
that user supplied.
EricP <ThatWouldBeTelling@thevillage.com> schrieb:
I'm objecting to the notion of forcing a processor to reboot
for any reason, let alone in order to change something that is
under control of the operating system. There was a time before PC's
when computers normally ran for a year without reboot and never crashed.
[tkoenig@cfarm135 ~]$ uptime
18:03:12 up 1537 days, 19:33, 1 user, load average: 12,31, 9,59, 8,58
[tkoenig@cfarm135 ~]$ uname -a
Linux cfarm135 4.18.0-80.7.2.el7.ppc64le #1 SMP Thu Sep 12 15:45:05 UTC 2019 ppc64le ppc64le ppc64le GNU/Linux
In any case, this has inspired me to think of a feature to add to
Concertina II. Add a new kind of header block, which must be the very
first one, which contains an encrypted checksum of the rest of the block
- which must be valid for the block to be allowed to execute. A mode
exists where only blocks with such a checksum can be executed.
You make pointers two words, one for the code, one that points to the
display for the dynamic data. This has been a standard compiler
technique since the late 1960s, ...
... which is yields ABI problems if the ABI is modeled on C,
and on function pointers being convertible to void* and back),
and needs requires conditional code for _each_ call through a
function pointer, because it needs to check if it is a vanilla
call or a call to a nested function.
And then, *duh*, the benefit of these checksums over no write-execute
hits me. Write execute bans protect the copy of code that's in the
memory. Checksums even protect the copy *that's residing on the
computer's disk storage*, which should be of some value.
The installer program, read in from the DVD-ROM, asks for a password -
which has to be the same one you used for installing the BIOS -
On 12/17/2023 10:38 AM, EricP wrote:
As for Mitch's RW-no-E restriction, I am confident it
will not survive its first encounter with actual customers
(where philosophy collides with income).
Yeah.
Imposing such a restriction at the ISA level is likely a bit too much of
an ask.
Better IMO to have flags to encode various modes, say:
Read Only
Read/Write
Read/Execute
Read/Write/Execute.
For these, one might have, say, a No-Write and No-Execute flag.
Write-Only and Execute-Only are not really common modes.
However, in my case, the MMU has a No-Read flag as well, so these are technically possible.
For the main page flags, one might also have, say:
No-User (Supervisor Only)
No-Cache
Shared/Global
Valid/Present
...
If one has super and User mode, and R,W,E enables, that's 6 PTE bits.
The PTE is very cramped so there is a big incentive to save any bits.
We can get that down to 3 PTE bits by throwing out the silly combinations, Write-only, Execute-only, Super:NoAccess+User:RWE,...
Assuming you're the klapatious who posted the question on stack
overflow,
According to Thomas Koenig <tkoenig@netcologne.de>:
You make pointers two words, one for the code, one that points to the
display for the dynamic data. This has been a standard compiler
technique since the late 1960s, ...
... which is yields ABI problems if the ABI is modeled on C,
and on function pointers being convertible to void* and back),
and needs requires conditional code for _each_ call through a
function pointer, because it needs to check if it is a vanilla
call or a call to a nested function.
Yup, after almost 50 years C still suffers from "everything is really
an int". You can use C, or you can use nested functions, but you'll be
sad if you try to do both at the same time.
I never found the lack of nested functions in C to be much of a
problem. In the rare cases where I want to do something like that,
it's not hard to pass what would have been in the display as
explicit arguments.
I never found the lack of nested functions in C to be much of a
problem. In the rare cases where I want to do something like that,
it's not hard to pass what would have been in the display as
explicit arguments.
Some languages have it in their specification (Fortran and, I
believe, Ada), so they need to be supported, and should
be supported well.
According to Thomas Koenig <tkoenig@netcologne.de>:
I never found the lack of nested functions in C to be much of a
problem. In the rare cases where I want to do something like that,
it's not hard to pass what would have been in the display as
explicit arguments.
Some languages have it in their specification (Fortran and, I
believe, Ada), so they need to be supported, and should
be supported well.
If you want to support nested functions, it's not hard, you make
function pointers two words, the code pointer and the static chain.
John Levine <johnl@taugh.com> schrieb:
According to Thomas Koenig <tkoenig@netcologne.de>:
You make pointers two words, one for the code, one that points to the
display for the dynamic data. This has been a standard compiler
technique since the late 1960s, ...
... which is yields ABI problems if the ABI is modeled on C,
and on function pointers being convertible to void* and back),
and needs requires conditional code for _each_ call through a
function pointer, because it needs to check if it is a vanilla
call or a call to a nested function.
Yup, after almost 50 years C still suffers from "everything is really
an int". You can use C, or you can use nested functions, but you'll be
sad if you try to do both at the same time.
It becomes more interesting if you want to design an ABI (or an
ABI together with an ISA) which caters well to them, or have a
C-based ABI and try to fit them in.
I never found the lack of nested functions in C to be much of a
problem. In the rare cases where I want to do something like that,
it's not hard to pass what would have been in the display as
explicit arguments.
Some languages have it in their specification (Fortran and, I
believe, Ada), so they need to be supported, and should
be supported well.
In my own (non-critical) Ada programs, small nested subprograms are not >unusual, but pointers to them are very rare (I don't recall a single
case of it).
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
In my own (non-critical) Ada programs, small nested subprograms are not
unusual, but pointers to them are very rare (I don't recall a single
case of it).
That's interesting, because a major reason for using nested functions
(or, more generally, closures), is if you need to pass a function with
a given interface, but have to pass additional data to that function.
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
That is the canonical example, yes. But it happens I have never needed
to do numerical integration in my Ada programs. I do have some cases
where I have used locally instantiated generics with generic function parameters in the way I described above. In Ada the integration example
would be declared thusly:
generic
with function F (X : Float) return Float;
function Integrate (Lo, Hi : Float) return Float;
although one would usually not limit it to a specific number type such
as Float, but would make this type also a generic (type) parameter.
IIRC, the ability to define numerical integration as a generic
subprogram, with the function to be integrated as a generic parameter,
was one of the reasons presented for omitting function pointers entirely
from original Ada (Ada 83).
If you want to support nested functions, it's not hard, you make
function pointers two words, the code pointer and the static chain.
Why? Nested functions need access to containing function local _data_,
not code. The actual generated code is independent of the containing >function.
Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
That is the canonical example, yes. But it happens I have never needed
to do numerical integration in my Ada programs. I do have some cases
where I have used locally instantiated generics with generic function
parameters in the way I described above. In Ada the integration example
would be declared thusly:
generic
with function F (X : Float) return Float;
function Integrate (Lo, Hi : Float) return Float;
although one would usually not limit it to a specific number type such
as Float, but would make this type also a generic (type) parameter.
IIRC, the ability to define numerical integration as a generic
subprogram, with the function to be integrated as a generic parameter,
was one of the reasons presented for omitting function pointers entirely
from original Ada (Ada 83).
Interesting, thanks!
Just one remark: Making numerical routines generic can be a bit
problematic if constants depend on the accuracy of a type; things
like tolerances for checks for convergence,
number of coefficients
in a polynomial (or Chebyshev) evaluation, quantities to divide by
for numeric differentiation etc.
According to Scott Lurndal <slp53@pacbell.net>:
If you want to support nested functions, it's not hard, you make
function pointers two words, the code pointer and the static chain.
Why? Nested functions need access to containing function local _data_,
not code. The actual generated code is independent of the containing >>function.
Sorry, I meant pointers to nested functions. You are quite right that
if you can't take the address of an internal function, the code is straightforward.
Thomas Koenig wrote:
Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
That is the canonical example, yes. But it happens I have never
needed to do numerical integration in my Ada programs. I do have some
cases where I have used locally instantiated generics with generic
function parameters in the way I described above. In Ada the
integration example would be declared thusly:
generic
with function F (X : Float) return Float;
function Integrate (Lo, Hi : Float) return Float;
although one would usually not limit it to a specific number type
such as Float, but would make this type also a generic (type)
parameter. IIRC, the ability to define numerical integration as a
generic subprogram, with the function to be integrated as a generic
parameter, was one of the reasons presented for omitting function
pointers entirely from original Ada (Ada 83).
Interesting, thanks!
Just one remark: Making numerical routines generic can be a bit
problematic if constants depend on the accuracy of a type; things
like tolerances for checks for convergence,
whether compiler constant arithmetic has the same numeric properties as compiled code instruction sequences,
Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
That is the canonical example, yes. But it happens I have never needed
to do numerical integration in my Ada programs. I do have some cases
where I have used locally instantiated generics with generic function
parameters in the way I described above. In Ada the integration example
would be declared thusly:
generic
with function F (X : Float) return Float;
function Integrate (Lo, Hi : Float) return Float;
although one would usually not limit it to a specific number type such
as Float, but would make this type also a generic (type) parameter.
IIRC, the ability to define numerical integration as a generic
subprogram, with the function to be integrated as a generic parameter,
was one of the reasons presented for omitting function pointers entirely
from original Ada (Ada 83).
Interesting, thanks!
Just one remark: Making numerical routines generic can be a bit
problematic if constants depend on the accuracy of a type; things
like tolerances for checks for convergence, number of coefficients
in a polynomial (or Chebyshev) evaluation, quantities to divide by
for numeric differentiation etc.
On 2023-12-19 20:12, Anton Ertl wrote:
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
In my own (non-critical) Ada programs, small nested subprograms are not
unusual, but pointers to them are very rare (I don't recall a single
case of it).
That's interesting, because a major reason for using nested functions
(or, more generally, closures), is if you need to pass a function with
a given interface, but have to pass additional data to that function.
In Ada, instead of passing a (nested) function pointer to some service
that needs such a parameter, one can implement the service as a generic program unit, where the function to be passed is given as a generic parameter, and instantiate that generic locally in the nested context,
thus binding the instance with the nested function.
Depending on the way the compiler implements such generics (as macros,
or with shared code) this may or may not be implemented by passing a
function pointer, but even if it is, the compiler can use a "fat
pointer" for this generic case, without being forced to use a fat
pointer for all function pointers.
EricP wrote:
If one has super and User mode, and R,W,E enables, that's 6 PTE bits.
The PTE is very cramped so there is a big incentive to save any bits.
We can get that down to 3 PTE bits by throwing out the silly
combinations,
Write-only, Execute-only, Super:NoAccess+User:RWE,...
If one uses a different page table for user and supervisor, you only
need those 3-bits. In my case Guest OS has its own table and its own
ASID completely separate from application. ASID plays the part of G
(global).
MitchAlsup wrote:
EricP wrote:
If one has super and User mode, and R,W,E enables, that's 6 PTE bits.
The PTE is very cramped so there is a big incentive to save any bits.
We can get that down to 3 PTE bits by throwing out the silly
combinations,
Write-only, Execute-only, Super:NoAccess+User:RWE,...
If one uses a different page table for user and supervisor, you only
need those 3-bits. In my case Guest OS has its own table and its own
ASID completely separate from application. ASID plays the part of G
(global).
Ok but all major OS's view the current process address space (P-space)
as directly addressable from system space (S-space), so the OS can reach directly into the current process to read or write memory to pass args to/from syscalls or deliver signals, etc.
To support this view of virtual memory on that platform all OS's would
need to maintain two parallel page tables to map each P-space twice,
one for user mode and one for super mode.
On 2023-12-20 0:37, MitchAlsup wrote:
Thomas Koenig wrote:
Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
That is the canonical example, yes. But it happens I have never
needed to do numerical integration in my Ada programs. I do have some
cases where I have used locally instantiated generics with generic
function parameters in the way I described above. In Ada the
integration example would be declared thusly:
generic
with function F (X : Float) return Float;
function Integrate (Lo, Hi : Float) return Float;
although one would usually not limit it to a specific number type
such as Float, but would make this type also a generic (type)
parameter. IIRC, the ability to define numerical integration as a
generic subprogram, with the function to be integrated as a generic
parameter, was one of the reasons presented for omitting function
pointers entirely from original Ada (Ada 83).
Interesting, thanks!
Just one remark: Making numerical routines generic can be a bit
problematic if constants depend on the accuracy of a type; things
like tolerances for checks for convergence,
whether compiler constant arithmetic has the same numeric properties as
compiled code instruction sequences,
Ada compilers are required to evaluate typeless constant arithmetic to unbounded precision (as "bignums"). For example, the constant pi is
defined thusly in the standard package Ada.Numerics:
Pi : constant :=
3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;
and all the digits are significant in any computation involving Pi and
other typeless constants.
Computation with typed constants is required to use the operations of
that type, so should match what the target code does.
On 19/12/2023 22:35, Niklas Holsti wrote:
On 2023-12-19 20:12, Anton Ertl wrote:
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
In my own (non-critical) Ada programs, small nested subprograms are not >>>> unusual, but pointers to them are very rare (I don't recall a single
case of it).
That's interesting, because a major reason for using nested functions
(or, more generally, closures), is if you need to pass a function with
a given interface, but have to pass additional data to that function.
In Ada, instead of passing a (nested) function pointer to some service
that needs such a parameter, one can implement the service as a
generic program unit, where the function to be passed is given as a
generic parameter, and instantiate that generic locally in the nested
context, thus binding the instance with the nested function.
Depending on the way the compiler implements such generics (as macros,
or with shared code) this may or may not be implemented by passing a
function pointer, but even if it is, the compiler can use a "fat
pointer" for this generic case, without being forced to use a fat
pointer for all function pointers.
That sounds very like lambdas in C++. It is much better than the way
nested functions in (extended) C sometimes have to be implemented, where run-time generated trampoline functions on the stack are used to hide
the "fat" when a fat pointer would be safer and more efficient.
EricP wrote:
MitchAlsup wrote:
EricP wrote:
If one has super and User mode, and R,W,E enables, that's 6 PTE bits.
The PTE is very cramped so there is a big incentive to save any bits.
We can get that down to 3 PTE bits by throwing out the silly
combinations,
Write-only, Execute-only, Super:NoAccess+User:RWE,...
If one uses a different page table for user and supervisor, you only
need those 3-bits. In my case Guest OS has its own table and its own
ASID completely separate from application. ASID plays the part of G
(global).
Ok but all major OS's view the current process address space (P-space)
as directly addressable from system space (S-space), so the OS can reach
directly into the current process to read or write memory to pass args
to/from syscalls or deliver signals, etc.
My 66000 provides this. Guest OS can reach down into application VaS, application cannot reach up into Guest OS VaS. When Guest OS accesses
its own VaS it uses its own ASID, when accessing application it uses application ASID. The same analogue occurs in Guest HV reaching down
into Guest OS VaS. When Guest HV accesses its own VaS it uses 1-level translation and its own ASID; when accessing Guest OS it uses 2-level translation and Guest OS ASID.
Switching between levels is fast because all the required information
is loaded at all times. Switching contexts is fast because only 1
register needs to be written, the data is obtained as if the control registers were a cache of the data at the written address.
To support this view of virtual memory on that platform all OS's would
need to maintain two parallel page tables to map each P-space twice,
one for user mode and one for super mode.
I believe Pascal, back in the 1970s implemented nested functions, though
I don't know how any specific implementation accomplished it.
MitchAlsup wrote:
EricP wrote:
If one has super and User mode, and R,W,E enables, that's 6 PTE bits.
The PTE is very cramped so there is a big incentive to save any bits.
We can get that down to 3 PTE bits by throwing out the silly
combinations,
Write-only, Execute-only, Super:NoAccess+User:RWE,...
If one uses a different page table for user and supervisor, you only
need those 3-bits. In my case Guest OS has its own table and its own
ASID completely separate from application. ASID plays the part of G
(global).
Ok but all major OS's view the current process address space (P-space)
as directly addressable from system space (S-space), so the OS can reach >directly into the current process to read or write memory to pass args >to/from syscalls or deliver signals, etc.
To support this view of virtual memory on that platform all OS's would
need to maintain two parallel page tables to map each P-space twice,
one for user mode and one for super mode.
Stephen Fuld <sfuld@alumni.cmu.edu.invalid> writes:
I believe Pascal, back in the 1970s implemented nested functions, though
I don't know how any specific implementation accomplished it.
AFAIK Pascal implementations all just use static links chains to the enclosing scopes. So you access an outer local by following the link
chain as many levels as you cross scopes.
Pascal allows passing
nested functions/procedures as parameters, but does not support
variables containing them, or any other first-class handling of them.
This avoids the upward funarg problem (as the Lispers call it). These parameters consist of the code pointer and the static link pointer.
Modula-2 has nested procedures, and allows putting procedures in
variables, fields, etc., but eliminates the upward funarg problem by
only allowing to store global-level procedures in variables, not
nested procedures. So it went closer to C.
Oberon originally had nested procedures, and AFAIK similar rules to
Modula-2. When I asked Wirth in 2020 if he has a good example for the
use of nested procedures (I thought he would have, given that he kept
this feature for so many years), he told me that he had removed nested functions in 2013 or so.
- anton
EricP <ThatWouldBeTelling@thevillage.com> writes:
MitchAlsup wrote:
To support this view of virtual memory on that platform all OS's would
need to maintain two parallel page tables to map each P-space twice,
one for user mode and one for super mode.
ARMv8 maps each half separately. There is a separate translation table
for each half of the virtual address space, with the high-half holding
the kernel code and data, and the low half holding the user portion.
Each half has it's own translation table base register and each half
can use a different asid.
Context switch between processes simply requires reloading the
table base register for the lower half (TTBR0_EL1). No other
TLB maintainance is required unless the 16-bit ASID wraps.
Anton Ertl wrote:
Stephen Fuld <sfuld@alumni.cmu.edu.invalid> writes:
I believe Pascal, back in the 1970s implemented nested functions, though >>>I don't know how any specific implementation accomplished it.
AFAIK Pascal implementations all just use static links chains to the
enclosing scopes. So you access an outer local by following the link
chain as many levels as you cross scopes.
How does Pascal do this counting when there are recursive frames on
the stack ??
MitchAlsup wrote:
EricP wrote:
MitchAlsup wrote:
EricP wrote:
If one has super and User mode, and R,W,E enables, that's 6 PTE bits. >>>>> The PTE is very cramped so there is a big incentive to save any bits. >>>>> We can get that down to 3 PTE bits by throwing out the silly
combinations,
Write-only, Execute-only, Super:NoAccess+User:RWE,...
If one uses a different page table for user and supervisor, you only
need those 3-bits. In my case Guest OS has its own table and its own
ASID completely separate from application. ASID plays the part of G
(global).
Ok but all major OS's view the current process address space (P-space)
as directly addressable from system space (S-space), so the OS can reach >>> directly into the current process to read or write memory to pass args
to/from syscalls or deliver signals, etc.
My 66000 provides this. Guest OS can reach down into application VaS,
application cannot reach up into Guest OS VaS. When Guest OS accesses
its own VaS it uses its own ASID, when accessing application it uses
application ASID. The same analogue occurs in Guest HV reaching down
into Guest OS VaS. When Guest HV accesses its own VaS it uses 1-level
translation and its own ASID; when accessing Guest OS it uses 2-level
translation and Guest OS ASID.
Switching between levels is fast because all the required information
is loaded at all times. Switching contexts is fast because only 1
register needs to be written, the data is obtained as if the control
registers were a cache of the data at the written address.
How does these two ASIDs allow 3 bits in a PTE to grant different
access for the same page to user and OS? For example S:RW,U:RE
To support this view of virtual memory on that platform all OS's would
need to maintain two parallel page tables to map each P-space twice,
one for user mode and one for super mode.
mitchalsup@aol.com (MitchAlsup) writes:
Anton Ertl wrote:The static scope level is independent of recursion. Note that this
Stephen Fuld <sfuld@alumni.cmu.edu.invalid> writes:How does Pascal do this counting when there are recursive frames on
I believe Pascal, back in the 1970s implemented nested functions, though >>>>I don't know how any specific implementation accomplished it.AFAIK Pascal implementations all just use static links chains to the
enclosing scopes. So you access an outer local by following the link
chain as many levels as you cross scopes.
the stack ??
kind of implementation has a static link in addition to the dynamic
link.
EricP wrote:
MitchAlsup wrote:
EricP wrote:
MitchAlsup wrote:
EricP wrote:
If one has super and User mode, and R,W,E enables, that's 6 PTE bits. >>>>>> The PTE is very cramped so there is a big incentive to save any bits. >>>>>> We can get that down to 3 PTE bits by throwing out the silly
combinations,
Write-only, Execute-only, Super:NoAccess+User:RWE,...
If one uses a different page table for user and supervisor, you only >>>>> need those 3-bits. In my case Guest OS has its own table and its own >>>>> ASID completely separate from application. ASID plays the part of G
(global).
Ok but all major OS's view the current process address space (P-space) >>>> as directly addressable from system space (S-space), so the OS can reach >>>> directly into the current process to read or write memory to pass args >>>> to/from syscalls or deliver signals, etc.
My 66000 provides this. Guest OS can reach down into application VaS,
application cannot reach up into Guest OS VaS. When Guest OS accesses
its own VaS it uses its own ASID, when accessing application it uses
application ASID. The same analogue occurs in Guest HV reaching down
into Guest OS VaS. When Guest HV accesses its own VaS it uses 1-level
translation and its own ASID; when accessing Guest OS it uses 2-level
translation and Guest OS ASID.
Switching between levels is fast because all the required information
is loaded at all times. Switching contexts is fast because only 1
register needs to be written, the data is obtained as if the control
registers were a cache of the data at the written address.
How does these two ASIDs allow 3 bits in a PTE to grant different
access for the same page to user and OS? For example S:RW,U:RE
I was planning on ignoring access rights when a more privileged thread >accesses less privileged data.
Anton Ertl wrote:
Stephen Fuld <sfuld@alumni.cmu.edu.invalid> writes:
I believe Pascal, back in the 1970s implemented nested functions, though >>>I don't know how any specific implementation accomplished it.
AFAIK Pascal implementations all just use static links chains to the
enclosing scopes.
So you access an outer local by following the link
chain as many levels as you cross scopes.
How does Pascal do this counting when there are recursive frames on
the stack ??
Pascal allows passing
nested functions/procedures as parameters, but does not support
variables containing them
, or any other first-class handling of them.
This avoids the upward funarg problem (as the Lispers call it). These
parameters consist of the code pointer and the static link pointer.
Modula-2 has nested procedures, and allows putting procedures in
variables, fields, etc., but eliminates the upward funarg problem by
only allowing to store global-level procedures in variables, not
nested procedures. So it went closer to C.
Oberon originally had nested procedures, and AFAIK similar rules to
Modula-2. When I asked Wirth in 2020 if he has a good example for the
use of nested procedures (I thought he would have, given that he kept
this feature for so many years), he told me that he had removed nested
functions in 2013 or so.
- anton
Anton Ertl [2023-12-20 21:37:44] wrote:
mitchalsup@aol.com (MitchAlsup) writes:
Anton Ertl wrote:The static scope level is independent of recursion. Note that this
Stephen Fuld <sfuld@alumni.cmu.edu.invalid> writes:How does Pascal do this counting when there are recursive frames on
I believe Pascal, back in the 1970s implemented nested functions, though >>>>>I don't know how any specific implementation accomplished it.AFAIK Pascal implementations all just use static links chains to the
enclosing scopes. So you access an outer local by following the link
chain as many levels as you cross scopes.
the stack ??
kind of implementation has a static link in addition to the dynamic
link.
Maybe I'm missing something but I think Pascal's display is "orthogonal"
to the issue discussed before of how to stuff together a code pointer >together with a pointer to its associated data.
Pascal's display is one possible representation of closures, but it
doesn't save you from the need to use a kind of "fat pointer" for the >first-class functions.
What saves Pascal is that first-class functions aren't first class at
all, so Pascal can safely and easily represent them any way it likes, >including as a pair of two values (a pointer to the code, and a pointer
to a stack frame, typically).
It's harder in C because some code wants to be able to convert
a function pointer to a `void*` and back, so you usually want to make it
fit into the size of a normal pointer.
On 2023-12-20 0:37, MitchAlsup wrote:
Thomas Koenig wrote:
Niklas Holsti <niklas.holsti@tidorum.invalid> schrieb:
E.g., consider a numerical integration function integrate(f,lo,hi),
which expects a function with one parameter f(x), and you want to
integrate x^y (pow(x,y) in C), where y is, say, passed as parameter.
That is the canonical example, yes. But it happens I have never
needed to do numerical integration in my Ada programs. I do have
some cases where I have used locally instantiated generics with
generic function parameters in the way I described above. In Ada the
integration example would be declared thusly:
   generic
      with function F (X : Float) return Float;
   function Integrate (Lo, Hi : Float) return Float;
although one would usually not limit it to a specific number type
such as Float, but would make this type also a generic (type)
parameter. IIRC, the ability to define numerical integration as a
generic subprogram, with the function to be integrated as a generic
parameter, was one of the reasons presented for omitting function
pointers entirely from original Ada (Ada 83).
Interesting, thanks!
Just one remark: Making numerical routines generic can be a bit
problematic if constants depend on the accuracy of a type; things
like tolerances for checks for convergence,
whether compiler constant arithmetic has the same numeric properties
as compiled code instruction sequences,
Ada compilers are required to evaluate typeless constant arithmetic to unbounded precision (as "bignums"). For example, the constant pi is
defined thusly in the standard package Ada.Numerics:
Pi : constant :=
3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;
and all the digits are significant in any computation involving Pi and
other typeless constants.
Computation with typed constants is required to use the operations of
that type, so should match what the target code does.
Niklas Holsti wrote:
Ada compilers are required to evaluate typeless constant arithmetic to
unbounded precision (as "bignums"). For example, the constant pi is
defined thusly in the standard package Ada.Numerics:
Pi : constant :=
3.14159_26535_89793_23846_26433_83279_50288_41971_69399_37511;
and all the digits are significant in any computation involving Pi and
other typeless constants.
And still, 50 digits is only ~170 bits, so not enough to do arbitrary parameter reductions (which requires ~1100 bits).
On 2023-12-19 20:12, Anton Ertl wrote:
Niklas Holsti <niklas.holsti@tidorum.invalid> writes:
In my own (non-critical) Ada programs, small nested subprograms are not
unusual, but pointers to them are very rare (I don't recall a single
case of it).
That's interesting, because a major reason for using nested functions
(or, more generally, closures), is if you need to pass a function with
a given interface, but have to pass additional data to that function.
In Ada, instead of passing a (nested) function pointer to some service
that needs such a parameter, one can implement the service as a generic program unit, where the function to be passed is given as a generic parameter, and instantiate that generic locally in the nested context,
thus binding the instance with the nested function.
Anton Ertl wrote:
Stephen Fuld <sfuld@alumni.cmu.edu.invalid> writes:
I believe Pascal, back in the 1970s implemented nested functions, though >>> I don't know how any specific implementation accomplished it.
AFAIK Pascal implementations all just use static links chains to the
enclosing scopes. So you access an outer local by following the link
chain as many levels as you cross scopes.
How does Pascal do this counting when there are recursive frames on the
stack ??
Modula-2 has nested procedures, and allows putting procedures in
variables, fields, etc., but eliminates the upward funarg problem by
only allowing to store global-level procedures in variables, not
nested procedures. So it went closer to C.
Oberon originally had nested procedures, and AFAIK similar rules to
Modula-2. When I asked Wirth in 2020 if he has a good example for the
use of nested procedures (I thought he would have, given that he kept
this feature for so many years), he told me that he had removed nested functions in 2013 or so.
Is the CPU even the place for sandboxing? A genuinely effective sandbox
would involve a physical separation between the protected computer and
the one connected to the Internet, after all. But that isn't
convenient...
There is a significant benefit to well-done nested functions, where
well-done means full closures, syntactically lightweight, and
allowing an anonymous expressional form, often called a lambda:
the ability to define first-class control structures in the
language, rather than needing control structures built into the
language. See for example blocks in Smalltalk.
Tim Rentsch wrote:
There is a significant benefit to well-done nested functions, where
well-done means full closures, ...
How do pointers to nested functions interact with try-throw-catch ??
Do you use the stack as it is now ?? or how it was when the closure was >constructed ??
According to MitchAlsup <mitchalsup@aol.com>:
Tim Rentsch wrote:
There is a significant benefit to well-done nested functions, where
well-done means full closures, ...
How do pointers to nested functions interact with try-throw-catch ??
Do you use the stack as it is now ?? or how it was when the closure was >>constructed ??
The usual answer is that you unwind the stack to its state at try, and
if you then call something that uses stack frames below that, you
lose.
Or you can do spaghetti stacks, where the stack can be forked and you
keep all the frames around that might be reactivated. See Scheme and Smalltalk.
Tim Rentsch wrote:
There is a significant benefit to well-done nested functions, where
well-done means full closures, syntactically lightweight, and
allowing an anonymous expressional form, often called a lambda:
the ability to define first-class control structures in the
language, rather than needing control structures built into the
language. See for example blocks in Smalltalk.
How do pointers to nested functions interact with try-throw-catch ??
Do you use the stack as it is now ?? or how it was when the closure was constructed ??
According to MitchAlsup <mitchalsup@aol.com>:
Tim Rentsch wrote:
There is a significant benefit to well-done nested functions, where
well-done means full closures, ...
How do pointers to nested functions interact with try-throw-catch ??
Do you use the stack as it is now ?? or how it was when the closure was >>constructed ??
The usual answer is that you unwind the stack to its state at try, and
if you then call something that uses stack frames below that, you
lose.
Or you can do spaghetti stacks, where the stack can be forked and you
keep all the frames around that might be reactivated. See Scheme and >Smalltalk.
John Levine <johnl@taugh.com> writes:
According to MitchAlsup <mitchalsup@aol.com>:
Tim Rentsch wrote:
There is a significant benefit to well-done nested functions, where
well-done means full closures, ...
How do pointers to nested functions interact with try-throw-catch ??
Do you use the stack as it is now ?? or how it was when the closure was >>>constructed ??
The usual answer is that you unwind the stack to its state at try, and
if you then call something that uses stack frames below that, you
lose.
It's not clear to me what you mean with "below", but anyway:
In the usual exception-handling variant (as exists in, e.g., Java)
where you cannot continue at the throwing site, you just restore
setup, including the stack pointer and the frame pointer (and thus
the static and the dynamic link) to what it was at the "try". The
same holds for Pascal's goto to a label in a statically surrounding >procedure; the more usual exception mechanisms find the try-catch
block dynamically, but that makes no difference after the target of
the throw has been determined.
If the static link was invalid after the "try", it was invalid before
the "try", too; many languages and their implementations are designed
to avoid that, either by preventing closures that live after the
procedure that created them has finished (e.g., Pascal and Modula-2),
or by keeping such closures in garbage-collected memory rather than
the stack (e.g., Scheme).
Or you can do spaghetti stacks, where the stack can be forked and you
keep all the frames around that might be reactivated. See Scheme and >>Smalltalk.
From the viewpoint of what to do that's pretty similar to the case
above: if the catch block can decide to continue at the throwing
place, before entering the catch block, the frame pointer is restored
to where it was at the "try", but the stack pointer is not. If the
catch block decides to continue at the throw, the frame pointer (and
the stack pointer, if changed in the meantime) is restored to the
value at the throw; If the catch block decides to continue after the
catch block, the stack pointer is restored to the depth at the "try".
- anton
Sysop: | Keyop |
---|---|
Location: | Huddersfield, West Yorkshire, UK |
Users: | 546 |
Nodes: | 16 (2 / 14) |
Uptime: | 04:22:41 |
Calls: | 10,387 |
Calls today: | 2 |
Files: | 14,061 |
Messages: | 6,416,782 |