Z80Golf , 53 36 34 octets
-16 octets grâce à @Lynn
-2 octets grâce à @Neil
Comme il ne s'agit que du code machine Z80, il y a beaucoup d'imprimables dans celui-ci, il y a donc un xxd -r
hexdump réversible:
00000000: ddb6 2120 10dd b615 280c 003e 62ff 3e65 ..! ....(..>b.>e
00000010: ffff 3e70 ff76 003e 62ff 3e65 ffff 3e70 ..>p.v.>b.>e..>p
00000020: ff76 .v
Essayez-le en ligne! (testeur exhaustif en Python)
Explication
z80golf est la machine Z80 hypothétique d'Anarchy Golf, où se call $8000
trouve un putchar, call $8003
un getchar, halt
fait quitter l'interpréteur, votre programme est placé sur $0000
et toute autre mémoire est remplie de zéros. Rendre les programmes résistants aux radiations dans l'assemblage est assez difficile, mais une technique génériquement utile utilise des instructions idempotentes à un octet. Par exemple,
or c ; b1 ; a = a | c
n'est qu'un octet, et a | c | c == a | c
il peut donc être rendu résistant aux rayonnements en répétant simplement l'instruction. Sur le Z80, une charge immédiate de 8 bits est de deux octets (où l'immédiat se trouve dans le deuxième octet), vous pouvez donc également charger certaines valeurs dans des registres de manière fiable. C'est ce que j'ai fait à l'origine au début du programme, vous pouvez donc analyser les variantes plus longues que j'ai archivées au bas de la réponse, mais j'ai ensuite réalisé qu'il y avait un moyen plus simple.
Le programme comprend deux charges utiles indépendantes, dont l'une aurait pu être endommagée par les radiations. Je vérifie si un octet a été supprimé et si l'octet supprimé était avant la deuxième copie de la charge utile, en vérifiant les valeurs de certaines adresses mémoire absolues.
Tout d'abord, nous devons sortir si aucun rayonnement n'a été observé:
or a, (ix+endbyte) ; dd b6 21 ; a |= memory[ix+0x0021]
jr nz, midbyte ; 20 10 ; jump to a halt instruction if not zero
Si un octet a été supprimé, tous les octets se déplaceront et $0020
contiendront le dernier 76
, ce $0021
sera donc un zéro. Nous pouvons nous permettre de rayonner le début du programme, même s'il n'y a pratiquement pas de redondance:
- Si le décalage de saut
$10
est supprimé, le rayonnement sera correctement détecté, le saut ne sera pas effectué et le décalage n'aura pas d'importance. Le premier octet de l'instruction suivante sera consommé, mais comme il est conçu pour résister aux suppressions d'octets, cela n'a pas d'importance.
- Si l'opcode jump
$20
est supprimé, alors l'offset de saut $10
sera décodé en djnz $ffe4
(consommant l'octet d'instruction suivant comme offset - voir ci-dessus), qui est une instruction de boucle - décrémenter B, et sauter si le résultat n'est pas nul. Parce qu'il ffe4-ffff
est rempli de zéros nop
et que le compteur de programme se termine, cela exécutera le début du programme 256 fois, puis continuera finalement. Je suis étonné que cela fonctionne.
- La suppression du
$dd
fera décoder le reste de l'extrait en tant que or (hl) / ld ($1020), hl
, puis se glissera dans la partie suivante du programme. Le or
ne modifiera aucun registre important et, comme HL est nul à ce stade, l'écriture sera également annulée.
- La suppression du
$b6
fera décoder le reste comme ld ($1020), ix
et procéder comme ci-dessus.
- La suppression du
$21
fera que le décodeur mangera le $20
, déclenchant le djnz
comportement.
Notez que l'utilisation or a, (ix+*)
économise plus de deux octets ld a, (**) / and a / and a
grâce à la vérification intégrée de zéro.
Nous devons maintenant décider laquelle des deux copies de la charge utile exécuter:
or (ix+midbyte) ; dd b6 15
jr z, otherimpl ; 28 0c
nop ; 00
; first payload
ld a, 'b' ; 3e 62
rst $0038 ; ff
ld a, 'e' ; 3e 65
rst $0038 ; ff
rst $0038 ; ff
ld a, 'p' ; 3e 70
rst $0038 ; ff
midbyte:
halt ; 76
otherimpl:
nop ; 00
ld a, 'b' ; 3e 62
; ... ; ...
rst $0038 ; ff
endbyte:
halt ; 76
Les deux copies sont séparées par un nop, car un saut relatif est utilisé pour choisir entre elles, et le rayonnement aurait pu déplacer le programme d'une manière qui ferait sauter le premier octet après la destination. De plus, le nop est codé comme un zéro, ce qui facilite la détection des octets décalés. Notez que la charge utile choisie n'a pas d'importance si le commutateur lui-même est corrompu, car les deux copies sont alors sûres. Assurons-nous qu'il ne sautera pas dans la mémoire non initialisée, cependant:
- La suppression
$dd
fera décoder les deux octets suivants comme or (hl) / dec d
. Clobbers D. Pas grand-chose.
- La suppression
$b6
créera un encodage plus long non documenté pour dec d
. Comme ci-dessus.
- La suppression
$15
lira à la $28
place le décalage, et l'exécution se poursuivra au $0c
, comme ci-dessous.
- Lorsque
$28
disparaît, le $0c
est décodé en inc c
. La charge utile ne se soucie pas c
.
- Supprimer
$0c
- c'est à cela que sert le nop. Sinon, le premier octet de la charge utile aurait été lu comme décalage de saut et le programme sauterait dans la mémoire non initialisée.
La charge utile elle-même est assez simple. Je pense que la petite taille de la chaîne rend cette approche plus petite qu'une boucle, et il est plus facile de rendre la position indépendante de cette façon. Le e
in se beep
répète, donc je peux en raser un ld a
. En outre, parce que toute la mémoire entre $0038
et $8000
est remis à zéro, je peux tomber à travers et utiliser une plus courte rst
variante de l' call
instruction, qui ne fonctionne que pour $0
, $8
, $10
et ainsi de suite, jusqu'à $38
.
Approches plus anciennes
64 octets
00000000: 2e3f 3f2e 3f3f 7e7e a7a7 201f 1e2b 2b1e .??.??~~.. ..++.
00000010: 2b2b 6b00 7ea7 2814 003e 62cd 0080 3e65 ++k.~.(..>b...>e
00000020: cd00 80cd 0080 3e70 cd00 8076 003e 62cd ......>p...v.>b.
00000030: 0080 3e65 cd00 80cd 0080 3e70 cd00 8076 ..>e......>p...v
58 octets
00000000: 2e39 392e 3939 7e7e a7a7 2019 3a25 00a7 .99.99~~.. .:%..
00000010: 2814 003e 62cd 0080 3e65 cd00 80cd 0080 (..>b...>e......
00000020: 3e70 cd00 8076 003e 62cd 0080 3e65 cd00 >p...v.>b...>e..
00000030: 80cd 0080 3e70 cd00 8076 ....>p...v
53 octets
Celui-ci a une explication dans l'historique des modifications, mais ce n'est pas trop différent.
00000000: 3a34 00a7 a720 193a 2000 a728 1400 3e62 :4... .: ..(..>b
00000010: cd00 803e 65cd 0080 cd00 803e 70cd 0080 ...>e......>p...
00000020: 7600 3e62 cd00 803e 65cd 0080 cd00 803e v.>b...>e......>
00000030: 70cd 0080 76 p...v
Et si: toute sortie non vide était correcte au lieu d'un bip
1 octet
v
halt
s le programme normalement, mais si le rayonnement le supprime, alors la mémoire sera pleine de zéros, faisant $8000
exécuter un nombre infini de fois, imprimant beaucoup d'octets nuls.