7 ms·
That's a stretch. If the only dimension you care about is whether or not the language spec is guaranteed to be invariant, sure, but that's essentially nitpickin
by paradroid 8y ago
That's a stretch. If the only dimension you care about is whether or not the language spec is guaranteed to be invariant, sure, but that's essentially nitpicking. One more layer of abstraction gets you an asm->asm transpiler that never changes.
- theoh 8y agoTake microcode: one human-visible instruction gets implemented by many microcoded instructions. With jets, a string of many very simple human-visible instructions gets translated to just a few actual machine instructions. So it's the opposite in the sense that a microcoded system expands each instruction you write, while a jet system contracts strings of the programmer's instructions into more efficient code. As one of the comments says: "The point is that jets create a particularly vicious abstraction inversion whereby a programmer must simultaneously think inside the object language and a kind of metalanguage." To take advantage of jets, the programmer has to get the shape of the code right as well as the semantics. It's a bit like writing poetry, with the extra requirements of rhyme and meter in addition to the semantic requirements of straight prose. You have to keep an eye on what jets might match the code you write if you are going to get the required performance. ASM and microcode have none of this intricacy.
- paradroid 8y agoWhat's the best way to say this.. It's a masochistic method of programming where you insert a randomly generated layer of abstraction between the hardware and the programmer, and tell the programmer to make sure to add enough hints to his code to make sure that it runs quickly. The benefits are unclear.
- derefr 8y ago> To take advantage of jets, the programmer has to get the shape of the code right as well as the semantics. Not true at all, because jets are almost never something that exist in the language the programmer is writing in. They exist in the language the compiler is targeting. Here's a concrete example (something otherwise missing in this thread): the keccak256() hash function in Solidity. The keccak256() function just looks like any other function, from the perspective of writing code in Solidity. But when you compile your Solidity program containing a keccak256() call, it compiles not to an inline implementation of keccak256(), or to a single intrinsic EVM opcode for "doing keccak256", but rather to a call to a keccak256-implementing smart contract at a "known" address, created alongside the particular Ethereum network. That smart contract does have the plain EVM code in it to compute the keccak256 hash for a given input, and if you implemented a "dumb" Ethereum VM for your Ethereum network node, that's what would happen. It would be very slow and expensive to run, but it would work just fine. But, instead, in less-naive EVM implementations (including the reference EVM), there's a jet: the EVM opcode sequence for "call the known keccak256 smart contract" is pattern-matched, and instead of actually doing that, a native keccak256() function is called instead. (Or, alternately, the definition of the "CALL" op checks the call address, and in the case of the known addresses, calls the native function instead. Same difference.) The Solidity programmer remains completely unaware of this. Instead, it's a contract between the developers of Solidity, and the developers of (some) EVM implementations, that 1. Solidity will emit code in a structure that the EVM can pattern-match; and that 2. the EVM devs will ensure that such code is valid on all EVM implementations, with or without the jet (in this case by working with the networks to ensure there's a smart-contract in place at the known address that does keccak256 hashing.) That's all that's required to get a jet working: mutual knowledge of the jet between the developer of a compiler targeting the ISA, and the developer of the interpreter/VM for that ISA.
- theoh 8y agoI was talking about someone programming in the very simple language (e.g. Simplicity, as discussed in the LtU conversation) in which the jets do exist. I guess for you that would be a compiler implementor. Edit: NB I'm drawing on this particular comment: http://lambda-the-ultimate.org/node/5482#comment-95188 http://lambda-the-ultimate.org/node/5482#comment-95188
- shub 8y agoBy your description, a jet is an intrinsic written by people who don't know about intrinsics. Plus the bonus of "if you don't trigger the intrinsic it literally costs you money" which is the kind of language-gotcha innovation I would expect from crypto-whatever.
- derefr 8y agoThat’s the way you’d look at it if the jet was there before the code got there. The usual point of jets is to make existing code go faster, without recompiling it (such as in the case where all code is immutable forever.)
- shub 8y agoUm yes JIT intrinsics do that. Well, the JIT is recompiling as needed (or more often), but your bytecode isn't changing.
- derefr 8y agoJets exist without a JIT. In fact, the whole point of distinguishing "jets" as an idea is that they're a potential feature of naive bytecode interpreters, rather than of JITing interpreters or of (potentially optimizing) compilers. I mean, you can think of an interpreter with jets, but without a JIT, as an interpreter which passes the code it loads through a specific kind of JIT that only does native codegen for explicitly specified patterns, and has no "generic" path, instead leaving everything else as calls back into the interpreter. But that's not actually what's happening, because there is no JIT in such interpreters. JITing necessarily happens either during code-loading, or asynchronously with a profiling thread. Jets, meanwhile, happen within the instruction-stream decode stage of a VM, where the VM has enough of a read-ahead buffer that it can decode a long sequence of plain instructions that fits a given pattern, to an intrinsic. Jets are "recognized" within the VM's instruction pipeline itself, each time the pipeline's state matches a given bytecode-sequence pattern. They're a register-transfer level optimization. In essence, jets are an implementation technique for bytecode interpreter optimization, alternative and complementary to a full JIT. --- Also, I'm speaking about VMs here, but jets apply as a thing you can do just as well in a hardware CPU design, too. An example of a common "hardware jet" is recognizing a multi-byte no-op sequence (that is, not a multi-byte NOP intrinsic, using a long instruction, but rather a sequence of single-byte NOPs), and making it have the same effect as a multibyte no-op intrinsic of the same size (i.e. given a NOP sequence of length N, the replacement would free up the ALU and other later pipeline stages for N-1 cycles.) (There's probably another name this technique is known by in the hardware world—I'm not a hardware guy. I'm just highlighting the equivalence.) And this isn't just a particular kind of microcode expansion, either. Microcode expansion is effectively a kind of cheap, throw-away JIT: CPUs expand their ISA to microcode not during the decode stage of their pipeline, but rather when the instruction pointer's movement causes the CPU to copy a new chunk of a code page into a cache-line. The cache line is expanded as a whole, and the result stored in a per-core microcode buffer. This works for regular instructions, since regular instructions are necessarily cache-line aligned; but the sort of composite, multi-instruction patterns a CPU might want to recognize aren't guaranteed to be cache-line aligned, so they won't get caught by this pass. A "hardware jet", on the other hand—since it happens at the register-transfer level—can optimize these instructions just fine. So CPU designers use both.