x86 machine code (DOS), 53 bytes
00000000: ba 00 0d b0 01 b0 b4 b0 09 b0 cd b0 21 b0 c3 be ............!...
00000010: 1a 01 ad cd 29 3c 21 75 f9 c3 48 00 65 00 6c 00 ....)<!u..H.e.l.
00000020: 6c 00 6f 00 2c 00 20 00 57 00 6f 00 72 00 6c 00 l.o.,. .W.o.r.l.
00000030: 64 00 21 00 24 d.!.$
Even bytes only, 27 bytes
00000000: ba 0d 01 b4 09 cd 21 c3 1a ad 29 21 f9 48 65 6c ......!...)!.Hel
00000010: 6c 6f 2c 20 57 6f 72 6c 64 21 24 lo, World!$
Assembly (NASM syntax)
; Encode the half version using the defacto hello world with some
; harmless mov instructions. I haven't found a good use for these bytes
; yet (point of interest: get 13 in CX or (hello - start) in AX.
; mov dx, hello
; The offset is halved, then shifted left 8 to get it to encode in the
; upper byte.
; As a 3 byte opcode, this jumps the full variant to an odd address,
; causing a desync.
mov dx, ((hello - start) / 2) << 8
mov al, 0x01
; mov ah, 0x09
mov al, 0xB4
mov al, 0x09
; int 0x21
mov al, 0xCD
mov al, 0x21
mov al, 0xC3
; Now for the thicc code. Instead of INT 21:09H, which expects a byte
; string terminated with '$', I print each char manually using INT 29H
; since I can do that with LODSW.
; Get the address of str into SI
mov si, hello
; Load one padded UTF-16 character into AX, and advance SI.
; Print AL using INT 29H.
; Check for the last character
cmp al, '!'
; Return to exit
; On the minified version, this is a $-terminated string for INT 21:09H.
; db "Hello, World!$"
; On the non-minified version it is just dummy thicc.
dw __UTF16__("Hello, World!") ; Unicode strings are proof that nasm > gas
; this one doesn't need padding
The full variant does this:
; bunch of pointless mov instructions
mov si, str
cmp al, '!'
dw __UTF16__("Hello, World!")
while the small variant does this
mov dx, str
mov ah, 0x09
; Unexecuted nonsense here
db "Hello, World!$"
This does not attempt to do even bytes only, unlike Hagen von Eitzen's solution. This allows me to simplify it greatly.
As the comments say, I use two methods for printing the string. For the larger code, I loop over the padded string with
LODSB; INT 29H, and for the shorter code I use
INT 21:09H to just print the string directly from memory. This means I don't need to use any complex tricks.
I feel like there is some improvement possible, as I think I could take better advantage of the nops and padding.