It's commonly stated that Intel's Itanium 64-bit processor architecture failed because the revolutionary EPIC instruction set was very difficult to write a good compiler for, which meant a lack of good developer tools for IA64, which meant a lack of developers creating programs for the architecture, and so no one wanted to use hardware without much software for it, and so the platform failed, and all for the want of a horseshoe nail good compilers.
That's an oversimplified view. I worked on one of the first two Merced systems in the UK from early 2000, trying to port a mathematical modeller to its 64-bit Windows. I reported a few dozen compiler bugs, and got most of them fixed, so I speak from some practical experience. I had a pretty good relationship with my Intel customer engineer, and with Microsoft's compiler team.
It isn't especially hard to write a compiler that produces working code for IPF. It is very hard, with frequent excursions into "impossible", to write a compiler that makes IPF run fast. The reasons for this are actually fundamental to the architecture.
The compiler lacks information that is available at run-time
The idea that the compiler, with plenty of time available, can do a better job of scheduling memory accesses than hardware can at run-time is wrong.
It would be true for single-core, single-hardware-thread machines without processor caches. That takes us back to 8-bit and early 16-bit machines; it is not true of fast PCs since about 1990, which have had caches. It may be that some of the senior figures at Intel in the late 1990s who'd moved into management and come back to engineering so as to be involved with the company's "next great success" were still thinking that way. If so, that's a terrible failure of project management.
In a cached, multi-core system, running multi-threaded programs, it is impossible for the compiler to know for sure what is in cache and what isn't. Out-of-Order execution addresses that problem, very effectively, by issuing loads, allowing instructions to proceed as soon as their data arrives, and in meanwhile, running other instructions. A compiler can't do that.
You need to generate "bundles" of instructions that don't clash
The idea of "explicitly parallel instruction computing" is that you generate bundles of instructions, and all the instructions in a bundle are wholly independent of each other. They can't write to any of the same registers or memory locations, and they can't use each other's results in any other way. This is harder than it sounds.
Doing this with inherently serial languages like C, C++, and many styles of Fortran, requires the compiler to develop a sufficiently comprehensive model of the code it's compiling that it can find operations that can safely be run in parallel. This is called "instruction-level parallelism" ("ILP"). This had been an objective of compiler design since the early 1970s. HP and Intel did not have a general method of solving it in the late 1990s, and nor does anyone else, as of 2023. It is a genuinely hard problem in computer science, and if it is ever solved, that will require some great insight.
The Intel compiler people were very over-optimistic
They knew they did not have a solution to the ILP problem, but they seem to have thought that, given lots of developers, they could develop collections of heuristic rules that would be good enough. This, of course, let them expand their empire within Intel. They also told the hardware planners, when those guys hit difficult problems, "We can handle that in the compiler." That let them be a vital part of the huge project which Intel was sure was going to dominate the world of computing.
This is a very career- and company-orientated view, which neglects the engineering problem of actually delivering solutions. They presumably "had people to do that."
There were many flaws in the hardware design
It does not seem to have been simulated sufficiently comprehensively, because many of these problems would have shown up there. When I was working with an IPF simulator in 1999, I asked if it was generated from the abstract model of the architecture. Nobody at Intel seemed to understand that question, even after a lot of explaining.
The instruction set was very bulky. Someone appeared to have decided that in an era of gigabyte memories, that didn't matter. However, it mattered a lot on two pain points: memory bandwidth, and cache size. IPF always needed more of those than x86-64, and they're expensive.
There was a design flaw with speculative access to floating-point data in memory. It's a bit complicated to describe here, but it seemed to be based in an assumption that floating-point work would only happen in leaf functions. When that wasn't true, the code got a lot bigger and slower.
The compilers were buggy
I'd had good support from Intel on builds I did with their x86-32 compiler, but some resistance from my ISV customers, who all used the Microsoft compiler, to using them. I expected Intel to do a better job with an Itanium compiler, owing to more expertise with the architecture. So I started out doing parallel builds on Microsoft and Intel compilers. Initially, both of them were quite buggy, and I reported bugs and got fixes. After a while, Microsoft pulled ahead.
When I could run my "was it built right?" tests successfully in an optimised build with the Microsoft compiler, and could not in a de-optimised build with the Intel compiler, I dropped Intel. They weren't very pleased about that, but could not argue with the reasoning.
De-optimised Itanium code is very de-optimised. There are three "slots" for instructions in a bundle, and some instructions had to be in the middle slot. De-optimised code only puts a real instruction in the middle slot, and no-ops in the other two. It is very large, and very, very slow. Shipping it to end-users is out of the question. It's just too slow.
Intel suggested that using link-time code generation based on profile-guided optimisation would achieve excellent performance. I tried it just once, and it took over an hour to link my main DLL. Intel tried to claim that "you only do that for your production link" but I pointed out "I've spent over a year digging out compiler bugs, there are still plenty, and you want me to give the compiler new and difficult challenges? Which will add an hour to each iteration of my modify-build-test-debug cycle?"
Developer tools and support
Intel seemed to appreciate that code would need revising to perform well on IPF, and said they'd be willing to help, but the price was that they'd acquire rights in the software. No ISV was going to agree to that.
There was no IDE: developers for Windows got a cross-compiler and the platform SDK. This did not bother me at all, but a lot of ISVs decided they'd wait for an IDE - which never appeared for Windows.
Intel handed out a lot of Merced-based developer machines, something like 15,000 of them. Of course, many of them went unused because of the lack of an IDE. They weren't very fast, but they worked. However, if you wanted to upgrade them to C-series Merced processors with the errata fixed, that was $1,000 per processor. Many organisations decided not to bother and carried on using B-series processors with some errata, and using the compiler workarounds. Of course, those made things slower.
When HP started selling zx2000 and zx6000 workstations with McKinley processors, those were faster. But they were also expensive, and Intel dropped support on the Merced systems a few months after the HPs shipped. ISVs were suddenly being asked to spend a lot of money on something that wasn't going nearly as well as they'd been led to believe. There was no price competition on McKinley systems, because HP were the only company with a motherboard chipset for 1-4 processor systems, and they were only selling it in complete systems.
Critical mass was never achieved
Lots of ISVs gave up on Itanium because of the compiler bugs, the lack of an IDE, the expensive hardware, and the competition from AMD. Athlon and Opteron, the first x86-64 processors, were much easier to work with.
That meant some ISVs who were still in the game could not get tools, libraries, or other software that they needed to complete their products. So they gave up. The situation snowballed, and very little Itanium software was ever produced.
Companies that wanted to run it found that maybe they could get some of the applications they wanted, but they could not get others. Or a management tool they had standardised on wasn't available. Meanwhile, the AMD processors would run everything that 32-bit x86 would, quickly, and would integrate with existing networks. Itanium just died of neglect, in the mass market.
But what about that bytecode idea?
The bytecode would have been complex, because it would have to express all the semantics of all the languages that could be compiled into it, so that the bytecode processor could generate non-clashing bundles. Normal bytecodes describe very simple abstract processors, and are much easier to work with.
A bytecode could not have solved the problem of extracting the instruction-level parallelism, because that problem remains unsolved to this day.
It would have been possible to create a bytecode that was just a representation of IPF instructions and bundles, but there's no point: why not just use the real instruction set?