bit by byte

Source code is for humans. So you should use every bit of syntactic abilities of a programming language combined with your writing skills and strive to actually make it so.

Machines don't really care, they want machine code. But there is an important intermediate representation before a source code gets there (if at all in some cases 🤔).

Java source code journey begins with javac. It's a front-endish compiler that turns java source code files into bytecode class files (it can also process annotations in already compiled class files... which would be a some other time topic for sure). Class files are hardly human readable, but you can disassemble them using javap tool:

➜ ✗ javac BitByByte.java
BitByByte.java
package bitbybyte;

public class BitByByte {

    public int y(int x) {
        return 3*x;
    }

}
➜ ✗ javap -c BitByByte.class
 
Compiled from "BitByByte.java"
public class bitbybyte.BitByByte {
  public bitbybyte.BitByByte();
    Code:
       0: aload_0
       1: invokespecial #1  // Method java/lang/Object."<init>":()V
       4: return

  public int y(int);
    Code:
       0: iconst_3
       1: iload_1
       2: imul
       3: ireturn
}
javap -v option would give more details, but you can already see the things javac does for you, like
1: invokespecial #1  // Method java/lang/Object."<init>":()V
which refers to the default constructor that compiler inserts for you. You can also get a sense of the opcode semantics: y method works with int, and its opcodes reflect it using "i" (type) as a prefix followed by the operation name - iconst, imul (if you're curious - list of Java bytecode instructions).

So Java bytecode is actually an instruction set for JVM. And JVM has both an interpreter to run the bytecode in a generic way right away and a JIT compiler that can turn it into machine code. Not all bytecode gets to be JIT-compiled as JVM is clever about compilation performance implication, so only most used (hot spot) methods get to be compiled.

Any language that can compile to Java bytecode can be run on JVM. And many do. Java is one of them 😐.
Java has a rich set of bytecode manipulation frameworks (ASM, Javassist), allowing things like instrumenting existing classes or dynamically generating new ones.

JavaScript started out as an interpreted language. SpiderMonkey, its first engine, was doing just that: reading and executing source code as is. But it couldn't just stay that way and TraceMonkey became the first JIT compiler for JS.

Guess what happens before it gets to machine code though? Bytecode!

bitbybyte.js
let y = x => 3*x;
y(10)
➜ ✗ node --print-bytecode --print-bytecode-filter=y bitbybyte.js
[generated bytecode for function: y (0x2fe97875bf71 <SharedFunctionInfo y>)]
Bytecode length: 6
Parameter count 2
Register count 0
Frame size 0
OSR nesting level: 0
Bytecode Age: 0
   14 S> 0x2fe97875c31e @    0 : 0b 03             Ldar a0
   14 E> 0x2fe97875c320 @    2 : 46 03 00          MulSmi [3], [0]
   16 S> 0x2fe97875c323 @    5 : a8                Return
Constant pool (size = 0)
Handler Table (size = 0)
Source Position Table (size = 8)
0x2fe97875c329 <ByteArray[8]>

So there is that y function again (same triple semantics). And we're viewing V8 bytecode, at least that's what node runs on at the time of writing. What's (arguably 🤓) interesting is that it's so lazy that even to get it generate the bytecode, y has to be called, that's what y(10) is there for. In V8, Ignition (interpreter) is responsible for bytecode generation. And you can see how Ldar loads function argument into accumulator register, how MulSmi decided to use small integer type, and how it stores multiplication result back into accumulator register [0] (if curious - V8's bytecodes).
If a function is used a lot (hot spot), Turbofan takes its bytecode together with type feedback (oh, JS type system) and produces machine code.

Julia is a bit different. Well it's a lot different 😐. Julia uses JIT compiler aka just-ahead-of-time JAOT compiler and by default compiles everything to machine code before running it. And to get to machine code it relies on LLVM Compiler Infrastructure.
I used to think that Julia produces LLVM bitcode and I think you can occasionally read or hear the same (have friends talking about Julia and bitcode? 🤓). But it actually produces an in memory LLVM IR: an assembly-like language that is used as an Intermediate Representation between different compiler optimization passes.
Julia is super transparent about all the transformations (code lowering) that the source code goes through.

julia> y(x) = 3x
y (generic function with 1 method)
And the first step is a lowered code construction that can be seen using @code_lowered macro.
julia> @code_lowered y(10)
CodeInfo(
1 ─ %1 = 3 * x
└──      return %1
)

There is nothing about type information yet. @code_typed macro allows to see a lower typed level:

julia> @code_typed y(10)
CodeInfo(
1 ─ %1 = Base.mul_int(3, x)::Int64
└──      return %1
) => Int64

Next (lower still 😐) comes LLVM. Julia uses LLVM's C++ API to produce in memory LLVM IR. @code_llvm macro prints it:

julia> @code_llvm y(10)
;  @ REPL[1]:1 within `y`
define i64 @julia_y_339(i64 signext %0) #0 {
top:
; ┌ @ int.jl:88 within `*`
   %1 = mul i64 %0, 3
; └
  ret i64 %1
}

At the bottom lies machine (native) code built by LLVM. @code_native shows native assembly instructions:

julia>  @code_native y(10)
        .text
; ┌ @ REPL[1]:1 within `y`
; │┌ @ int.jl:88 within `*`
        leaq    (%rdi,%rdi,2), %rax
; │└
        retq
        nopw    %cs:(%rax,%rax)
; └

And if you know Julia rules (performance tips) - it gets really-really fast (even if there is a delay during the first run aka time to first plot).