commit 4706fe9e699582868f1caf3d64e3a68bd040afea
parent 04e5c663480809c351cc3e9668c4b72900aa4d89
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 20 Apr 2026 12:52:32 -0700
Add P1 portable pseudo-ISA, port hello.M1 to three arches
hello.M1 is now written in P1 mnemonics and assembles unchanged for
aarch64, amd64, and riscv64; the per-arch p1_<arch>.M1 defs file is
the only source that varies. Spike subset only — LI and SYSCALL —
which is all the hello demo needs; full P1 ISA per P1.md is deferred.
Makefile gains an ARCH variable (default aarch64) and a run-all
target; foreign-arch hello binaries run via podman --platform plus
binfmt/qemu-user.
Diffstat:
| A | ELF-amd64.hex2 | | | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | ELF-riscv64.hex2 | | | 74 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | Makefile | | | 95 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
| M | hello.M1 | | | 41 | +++++++++++++++++++++++------------------ |
| A | p1_aarch64.M1 | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | p1_amd64.M1 | | | 51 | +++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | p1_riscv64.M1 | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
7 files changed, 411 insertions(+), 48 deletions(-)
diff --git a/ELF-amd64.hex2 b/ELF-amd64.hex2
@@ -0,0 +1,74 @@
+### Copyright (C) 2016 Jeremiah Orians
+### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org>
+### This file is part of M2-Planet.
+###
+### M2-Planet is free software: you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation, either version 3 of the License, or
+### (at your option) any later version.
+###
+### M2-Planet is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with M2-Planet. If not, see <http://www.gnu.org/licenses/>.
+
+### stage0's hex2 format
+### !<label> 1 byte relative
+### $<label> 2 byte address
+### @<label> 2 byte relative
+### &<label> 4 byte address
+### %<label> 4 byte relative
+
+### if you wish to use this header, you need to add :ELF_end to the end of your
+### M1 or hex2 files.
+
+## ELF Header
+
+:ELF_base
+7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number
+
+02 # e_ident[EI_CLASS] Indicating 64 bit
+01 # e_ident[EI_DATA] Indicating little endianness
+01 # e_ident[EI_VERSION] Indicating original elf
+
+03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict
+00 # e_ident[EI_ABIVERSION] See above
+
+00 00 00 00 00 00 00 # e_ident[EI_PAD]
+
+02 00 # e_type Indicating Executable
+3E 00 # e_machine Indicating AMD64
+01 00 00 00 # e_version Indicating original elf
+
+&_start 00 00 00 00 # e_entry Address of the entry point
+%ELF_program_headers>ELF_base 00 00 00 00 # e_phoff Address of program header table
+00 00 00 00 00 00 00 00 # e_shoff Address of section header table
+
+00 00 00 00 # e_flags
+
+40 00 # e_ehsize Indicating our 64 Byte header
+
+38 00 # e_phentsize size of a program header table
+01 00 # e_phnum number of entries in program table
+
+00 00 # e_shentsize size of a section header table
+00 00 # e_shnum number of entries in section table
+
+00 00 # e_shstrndx index of the section names
+
+
+:ELF_program_headers
+:ELF_program_header__text
+01 00 00 00 # ph_type: PT-LOAD = 1
+07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7
+00 00 00 00 00 00 00 00 # ph_offset
+&ELF_base 00 00 00 00 # ph_vaddr
+&ELF_base 00 00 00 00 # ph_physaddr
+%ELF_end>ELF_base 00 00 00 00 # ph_filesz
+%ELF_end>ELF_base 00 00 00 00 # ph_memsz
+01 00 00 00 00 00 00 00 # ph_align
+
+:ELF_text
diff --git a/ELF-riscv64.hex2 b/ELF-riscv64.hex2
@@ -0,0 +1,74 @@
+### Copyright (C) 2016 Jeremiah Orians
+### Copyright (C) 2017 Jan Nieuwenhuizen <janneke@gnu.org>
+### This file is part of M2-Planet.
+###
+### M2-Planet is free software: you can redistribute it and/or modify
+### it under the terms of the GNU General Public License as published by
+### the Free Software Foundation, either version 3 of the License, or
+### (at your option) any later version.
+###
+### M2-Planet is distributed in the hope that it will be useful,
+### but WITHOUT ANY WARRANTY; without even the implied warranty of
+### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+### GNU General Public License for more details.
+###
+### You should have received a copy of the GNU General Public License
+### along with M2-Planet. If not, see <http://www.gnu.org/licenses/>.
+
+### stage0's hex2 format
+### !<label> 1 byte relative
+### $<label> 2 byte address
+### @<label> 2 byte relative
+### &<label> 4 byte address
+### %<label> 4 byte relative
+
+### if you wish to use this header, you need to add :ELF_end to the end of your
+### M1 or hex2 files.
+
+## ELF Header
+
+:ELF_base
+7F 45 4C 46 # e_ident[EI_MAG0-3] ELF's magic number
+
+02 # e_ident[EI_CLASS] Indicating 64 bit
+01 # e_ident[EI_DATA] Indicating little endianness
+01 # e_ident[EI_VERSION] Indicating original elf
+
+03 # e_ident[EI_OSABI] Set at 3 because FreeBSD is strict
+00 # e_ident[EI_ABIVERSION] See above
+
+00 00 00 00 00 00 00 # e_ident[EI_PAD]
+
+02 00 # e_type Indicating Executable
+F3 00 # e_machine Indicating RISC-V
+01 00 00 00 # e_version Indicating original elf
+
+&_start 00 00 00 00 # e_entry Address of the entry point
+%ELF_program_headers>ELF_base 00 00 00 00 # e_phoff Address of program header table
+00 00 00 00 00 00 00 00 # e_shoff Address of section header table
+
+00 00 00 00 # e_flags
+
+40 00 # e_ehsize Indicating our 64 Byte header
+
+38 00 # e_phentsize size of a program header table
+01 00 # e_phnum number of entries in program table
+
+00 00 # e_shentsize size of a section header table
+00 00 # e_shnum number of entries in section table
+
+00 00 # e_shstrndx index of the section names
+
+
+:ELF_program_headers
+:ELF_program_header__text
+01 00 00 00 # ph_type: PT-LOAD = 1
+07 00 00 00 # ph_flags: PF-X|PF-W|PF-R = 7
+00 00 00 00 00 00 00 00 # ph_offset
+&ELF_base 00 00 00 00 # ph_vaddr
+&ELF_base 00 00 00 00 # ph_physaddr
+%ELF_end>ELF_base 00 00 00 00 # ph_filesz
+%ELF_end>ELF_base 00 00 00 00 # ph_memsz
+01 00 00 00 00 00 00 00 # ph_align
+
+:ELF_text
diff --git a/Makefile b/Makefile
@@ -1,32 +1,62 @@
-# lispcc — alternative bootstrap path: aarch64 Lisp-in-M1 → C compiler in
-# Lisp → tcc-boot. See PLAN.md.
+# lispcc — P1 portable pseudo-ISA demo.
+#
+# hello.M1 is written in P1 mnemonics and assembles unchanged for all
+# three targets. The backing defs file (p1_<arch>.M1) is the only per-
+# arch source. See ../P1.md.
#
# Two-image setup:
-# - lispcc-builder (alpine + gcc): builds M1 and hex2 statically
-# - alpine:latest (pristine): runs M1, hex2, and the assembled output
+# - lispcc-builder (alpine + gcc, host-arch): builds M1 and hex2
+# statically. These tools run natively regardless of ARCH.
+# - alpine:latest (pristine, per-arch): runs the assembled hello under
+# the target's Linux ABI. Non-native targets go through podman's
+# binfmt + qemu-user path.
#
# Usage:
-# make image Build the builder image (one-time, idempotent)
-# make Build toolchain + assemble hello.M1 → build/hello
-# make run Run build/hello in pristine alpine
-# make clean Remove build/ artifacts
+# make image Build the builder image (one-time, idempotent)
+# make Build + assemble for ARCH (default aarch64)
+# make ARCH=amd64 Same, targeting amd64
+# make ARCH=riscv64 Same, targeting riscv64
+# make run Run the assembled hello for $(ARCH) under alpine
+# make run-all Build + run on all three arches
+# make clean Remove build/ artifacts
# --- Configuration ---------------------------------------------------------
+ARCH ?= aarch64
+
+# Map P1 ARCH -> Linux-platform tag for the runtime container.
+PLATFORM_aarch64 := linux/arm64
+PLATFORM_amd64 := linux/amd64
+PLATFORM_riscv64 := linux/riscv64
+PLATFORM := $(PLATFORM_$(ARCH))
+ifeq ($(PLATFORM),)
+ $(error ARCH '$(ARCH)' not supported — use aarch64, amd64, or riscv64)
+endif
+
HOST_ROOT := $(abspath $(CURDIR)/..)
TOOLCHAIN_SRC := /work/live-bootstrap/seed/stage0-posix/mescc-tools
BUILDER_IMAGE := lispcc-builder:latest
RUNTIME_IMAGE := docker.io/library/alpine:latest
-# Builder: alpine + gcc, mounts the bootstrap-explore parent dir at /work
-# so we can reach upstream mescc-tools C source.
+OUT_DIR := build/$(ARCH)
+
+# Builder: alpine + gcc at host arch, mounts the bootstrap-explore parent
+# dir at /work so we can reach upstream mescc-tools C source.
PODMAN_BUILD := podman run --rm \
-v $(HOST_ROOT):/work \
-w /work/lispcc \
$(BUILDER_IMAGE)
-# Runtime: pristine alpine, mounts only this project dir at /work.
-PODMAN_RUN := podman run --rm \
+# Native alpine — used to run the static M1/hex2 binaries (which are
+# built for host arch and don't care about P1 target arch).
+PODMAN_RUN_NATIVE := podman run --rm \
+ -v $(CURDIR):/work \
+ -w /work \
+ $(RUNTIME_IMAGE)
+
+# Target alpine — used to run the generated hello binary on its own
+# Linux ABI. Foreign arches transparently use binfmt + qemu-user.
+PODMAN_RUN_TARGET := podman run --rm --platform $(PLATFORM) \
-v $(CURDIR):/work \
-w /work \
$(RUNTIME_IMAGE)
@@ -36,17 +66,17 @@ CFLAGS := -D_GNU_SOURCE -std=c99 -ggdb -fno-common -static
# --- Targets ---------------------------------------------------------------
-.PHONY: all image toolchain run clean
+.PHONY: all image toolchain run run-all clean
-all: build/hello
+all: $(OUT_DIR)/hello
image:
podman build -t $(BUILDER_IMAGE) .
toolchain: build/M1 build/hex2
-build:
- mkdir -p build
+build $(OUT_DIR):
+ mkdir -p $@
build/M1: | build
$(PODMAN_BUILD) gcc $(CFLAGS) \
@@ -63,23 +93,28 @@ build/hex2: | build
$(TOOLCHAIN_SRC)/M2libc/bootstrappable.c \
-o build/hex2
-build/hello.hex2: hello.M1 aarch64_defs.M1 build/M1
- $(PODMAN_RUN) ./build/M1 \
- -f aarch64_defs.M1 \
+$(OUT_DIR)/hello.hex2: hello.M1 p1_$(ARCH).M1 build/M1 | $(OUT_DIR)
+ $(PODMAN_RUN_NATIVE) ./build/M1 \
+ -f p1_$(ARCH).M1 \
-f hello.M1 \
- --little-endian --architecture aarch64 \
- -o build/hello.hex2
-
-build/hello: build/hello.hex2 ELF-aarch64.hex2 build/hex2
- $(PODMAN_RUN) ./build/hex2 \
- -f ELF-aarch64.hex2 \
- -f build/hello.hex2 \
- --little-endian --architecture aarch64 \
+ --little-endian --architecture $(ARCH) \
+ -o $(OUT_DIR)/hello.hex2
+
+$(OUT_DIR)/hello: $(OUT_DIR)/hello.hex2 ELF-$(ARCH).hex2 build/hex2
+ $(PODMAN_RUN_NATIVE) ./build/hex2 \
+ -f ELF-$(ARCH).hex2 \
+ -f $(OUT_DIR)/hello.hex2 \
+ --little-endian --architecture $(ARCH) \
--base-address 0x400000 \
- -o build/hello
+ -o $(OUT_DIR)/hello
+
+run: $(OUT_DIR)/hello
+ $(PODMAN_RUN_TARGET) ./$(OUT_DIR)/hello
-run: build/hello
- $(PODMAN_RUN) ./build/hello
+run-all:
+ $(MAKE) --no-print-directory ARCH=aarch64 run
+ $(MAKE) --no-print-directory ARCH=amd64 run
+ $(MAKE) --no-print-directory ARCH=riscv64 run
clean:
rm -rf build/
diff --git a/hello.M1 b/hello.M1
@@ -1,24 +1,29 @@
-## aarch64 Linux hello world in M1
+## P1 "hello, world" — portable pseudo-ISA source.
+##
+## Same program on every P1 target; only the backing defs file swaps
+## (see p1_aarch64.M1, and eventually p1_amd64.M1 / p1_riscv64.M1).
+##
+## Linux calling convention on P1:
+## SYSCALL num -> r0, args -> r1..r6, result -> r0
:_start
- ;; write(1, msg, 14)
- SET_X0_TO_1 ; x0 = 1 (stdout)
+ ## write(fd=1, buf=&msg, count=14)
+ P1_LI_R0
+ SYS_WRITE # r0 = syscall number (write)
+ P1_LI_R1
+ '01000000' # r1 = fd (stdout)
+ P1_LI_R2
+ &msg # r2 = buf
+ P1_LI_R3
+ '0E000000' # r3 = count (14)
+ P1_SYSCALL
- LOAD_W1_AHEAD ; ldr w1, [pc+8]
- SKIP_32_DATA ; b +8 (over inline data)
- &msg ; <-- 4-byte msg address loaded into w1/x1
-
- LOAD_W2_AHEAD ; ldr w2, [pc+8]
- SKIP_32_DATA
- '0E000000' ; <-- length 14, little-endian, loaded into w2/x2
-
- SET_X8_TO_SYS_WRITE ; x8 = 64
- SYSCALL ; svc 0
-
- ;; exit(0)
- SET_X0_TO_0 ; x0 = 0
- SET_X8_TO_SYS_EXIT ; x8 = 93
- SYSCALL ; svc 0
+ ## exit(0)
+ P1_LI_R0
+ SYS_EXIT # r0 = syscall number (exit)
+ P1_LI_R1
+ '00000000' # r1 = status
+ P1_SYSCALL
:msg
"Hello, world!
diff --git a/p1_aarch64.M1 b/p1_aarch64.M1
@@ -0,0 +1,61 @@
+## P1 pseudo-ISA — aarch64 backing defs (v0.1 spike)
+##
+## Implements the subset needed for the hello-world demo: LI, SYSCALL.
+## See ../P1.md for the full ISA and register mapping.
+##
+## Register mapping (P1 → aarch64):
+## r0 → x0 , r1 → x1 , r2 → x2 , r3 → x3
+## r4 → x4 , r5 → x5 , r6 → x19, r7 → x20
+##
+## LI rD, <4-byte-literal> — load a 4-byte little-endian literal into rD
+## (zero-extended into the 64-bit register). Usage in source:
+##
+## P1_LI_R1
+## &some_label # or '0E000000'
+##
+## Expansion is "ldr w<D>, [pc+8] ; b +8" (8 bytes). The caller emits
+## the 4 inline literal bytes that follow. Addresses that fit in 32 bits
+## (true for our ELF-at-0x400000 binaries) round-trip through the W reg
+## because aarch64 LDR-W zero-extends into X.
+##
+## The 4-byte form is deliberate for the spike: it pairs with hex2's
+## `&label` sigil unchanged. A proper 64-bit LI via a PC-relative LDR X
+## literal is left for the aarch64 hex2_word extensions described in
+## P1.md §"What needs to be added".
+
+DEFINE P1_LI_R0 4000001802000014
+DEFINE P1_LI_R1 4100001802000014
+DEFINE P1_LI_R2 4200001802000014
+DEFINE P1_LI_R3 4300001802000014
+DEFINE P1_LI_R4 4400001802000014
+DEFINE P1_LI_R5 4500001802000014
+DEFINE P1_LI_R6 5300001802000014
+DEFINE P1_LI_R7 5400001802000014
+
+## SYSCALL — num in r0, args r1..r6, result in r0.
+##
+## aarch64 Linux wants num in x8 and args in x0..x5. P1's mapping puts
+## args one register higher than the native ABI, so SYSCALL shuffles
+## x0..x19 down into x0..x5 and moves the num into x8, then svc #0.
+##
+## Unconditional (every P1 ISA expansion is unoptimized). Inputs not
+## used by a given syscall are shuffled through harmlessly — x0..x5 are
+## caller-saved on the aarch64 platform ABI, and the kernel only reads
+## the registers the specific syscall cares about.
+##
+## Expansion:
+## mov x8, x0 ; P1 r0 (num) -> native num reg
+## mov x0, x1 ; P1 r1 -> native arg1
+## mov x1, x2 ; P1 r2 -> native arg2
+## mov x2, x3 ; P1 r3 -> native arg3
+## mov x3, x4 ; P1 r4 -> native arg4
+## mov x4, x5 ; P1 r5 -> native arg5
+## mov x5, x19 ; P1 r6 (x19) -> native arg6
+## svc #0
+DEFINE P1_SYSCALL e80300aae00301aae10302aae20303aae30304aae40305aae50313aa010000d4
+
+
+## Linux syscall numbers (aarch64 uses the generic table).
+## Emitted as 4-byte little-endian immediates, to be consumed by P1_LI_R*.
+DEFINE SYS_WRITE 40000000
+DEFINE SYS_EXIT 5D000000
diff --git a/p1_amd64.M1 b/p1_amd64.M1
@@ -0,0 +1,51 @@
+## P1 pseudo-ISA — amd64 backing defs (v0.1 spike)
+##
+## Implements the subset needed for the hello-world demo: LI, SYSCALL.
+## See ../P1.md for the full ISA and register mapping.
+##
+## Register mapping (P1 → amd64):
+## r0 → rax , r1 → rdi , r2 → rsi , r3 → rdx
+## r4 → r10 , r5 → r8 , r6 → rbx , r7 → r12
+##
+## LI rD, <4-byte-literal> — zero-extended load into rD.
+## Expands to "mov r<D>d, imm32" (5 or 6 bytes). Because x86-64
+## zero-extends 32-bit mov-to-GPR into the 64-bit register, the
+## 4-byte literal that immediately follows in source order ends up
+## as the full 64-bit value.
+##
+## Usage:
+## P1_LI_R1
+## &some_label # or '0E000000'
+
+DEFINE P1_LI_R0 B8 # mov eax, imm32
+DEFINE P1_LI_R1 BF # mov edi, imm32
+DEFINE P1_LI_R2 BE # mov esi, imm32
+DEFINE P1_LI_R3 BA # mov edx, imm32
+DEFINE P1_LI_R4 41BA # mov r10d, imm32
+DEFINE P1_LI_R5 41B8 # mov r8d, imm32
+DEFINE P1_LI_R6 BB # mov ebx, imm32
+DEFINE P1_LI_R7 41BC # mov r12d, imm32
+
+## SYSCALL — num in r0, args r1..r6, result in r0.
+##
+## amd64 Linux syscall ABI: num in rax, args in rdi,rsi,rdx,r10,r8,r9.
+## P1's mapping already places num (r0→rax) and args r1..r5 (→rdi,rsi,
+## rdx,r10,r8) in their native slots. The only mismatch is arg 6:
+## P1 r6 = rbx, native arg6 = r9. So SYSCALL shuffles that one register
+## and then traps.
+##
+## Expansion:
+## mov r9, rbx ; 49 89 D9
+## syscall ; 0F 05
+##
+## The shuffle runs unconditionally — syscalls that ignore arg 6 don't
+## care that r9 got overwritten with rbx (both are caller-saved on the
+## platform ABI; the kernel reads only the registers the specific
+## syscall cares about).
+DEFINE P1_SYSCALL 4989D90F05
+
+
+## Linux syscall numbers (amd64-specific table).
+## Emitted as 4-byte little-endian immediates, consumed by P1_LI_R*.
+DEFINE SYS_WRITE 01000000
+DEFINE SYS_EXIT 3C000000
diff --git a/p1_riscv64.M1 b/p1_riscv64.M1
@@ -0,0 +1,63 @@
+## P1 pseudo-ISA — riscv64 backing defs (v0.1 spike)
+##
+## Implements the subset needed for the hello-world demo: LI, SYSCALL.
+## See ../P1.md for the full ISA and register mapping.
+##
+## Register mapping (P1 → RISC-V):
+## r0 → a0 (x10) , r1 → a1 (x11) , r2 → a2 (x12) , r3 → a3 (x13)
+## r4 → a4 (x14) , r5 → a5 (x15) , r6 → s1 (x9) , r7 → s2 (x18)
+##
+## LI rD, <4-byte-literal> — zero-extended load into rD.
+## RISC-V lacks a single "load imm32" form, so the expansion uses the
+## same PC-relative-inline-data trick as aarch64, in three words:
+##
+## auipc rD, 0 ; rD = pc_of_auipc
+## lwu rD, 12(rD) ; rD = *(u32*)(pc_of_auipc + 12) [zero-ext to 64]
+## jal x0, +8 ; skip past the 4-byte data slot
+## <4-byte literal>
+##
+## The LWU offset is 12 because the literal lives 12 bytes past the
+## auipc: auipc(4) + lwu(4) + jal(4) = 12.
+##
+## Hex2 `&label` emits the 4-byte absolute address in little-endian,
+## matching how lwu reads it.
+##
+## Usage:
+## P1_LI_R1
+## &some_label # or '0E000000'
+
+## Each DEFINE below is three little-endian 32-bit words concatenated:
+## [auipc rD,0] [lwu rD,12(rD)] [jal x0,+8].
+DEFINE P1_LI_R0 170500000365C5006F008000
+DEFINE P1_LI_R1 9705000083E5C5006F008000
+DEFINE P1_LI_R2 170600000366C6006F008000
+DEFINE P1_LI_R3 9706000083E6C6006F008000
+DEFINE P1_LI_R4 170700000367C7006F008000
+DEFINE P1_LI_R5 9707000083E7C7006F008000
+DEFINE P1_LI_R6 9704000083E4C4006F008000
+DEFINE P1_LI_R7 170900000369C9006F008000
+
+## SYSCALL — num in r0, args r1..r6, result in r0.
+##
+## riscv64 Linux syscall ABI: num in a7, args in a0..a5. P1's mapping
+## puts args one register higher than native, so SYSCALL shuffles
+## a0..s1 down into a0..a5, moves num (a0) into a7, and traps.
+##
+## Expansion:
+## mv a7, a0 ; P1 r0 (num) -> native num reg
+## mv a0, a1 ; P1 r1 -> native arg1
+## mv a1, a2 ; P1 r2 -> native arg2
+## mv a2, a3 ; P1 r3 -> native arg3
+## mv a3, a4 ; P1 r4 -> native arg4
+## mv a4, a5 ; P1 r5 -> native arg5
+## mv a5, s1 ; P1 r6 -> native arg6
+## ecall
+##
+## Unconditional — unused shuffles read caller-saved scratch, and the
+## kernel reads only the regs each syscall cares about.
+DEFINE P1_SYSCALL 9308050013850500930506001386060093060700138707009307040073000000
+
+
+## Linux syscall numbers (riscv64 uses the generic table — same as aarch64).
+DEFINE SYS_WRITE 40000000
+DEFINE SYS_EXIT 5D000000