Files
semi-libc/src/locale/iconv.c
T
sisungo ef7d225be7 feat: use the vm model for iconv
Musl uses hard-coded logic for iconv. This patch moves the logic
out of C code, and implement a virtual machine for dynamically loading
charsets at runtime and do conversion.
2026-02-19 09:29:41 +08:00

434 lines
11 KiB
C

#include <iconv.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "locale_impl.h"
/* input/output instructions: inputN <receiving/sending_reg> */
#define VMOP_INPUT8 1
#define VMOP_INPUT16 2
#define VMOP_INPUT32 3
#define VMOP_INPUT64 4
#define VMOP_OUTPUT8 5
#define VMOP_OUTPUT16 6
#define VMOP_OUTPUT32 7
#define VMOP_OUTPUT64 8
/* x-y=z instructions: op <x> <y> <z> */
#define VMOP_XOR 9
#define VMOP_AND 10
#define VMOP_OR 11
#define VMOP_ADD 13
#define VMOP_SUB 14
#define VMOP_SMUL 15
#define VMOP_UMUL 17
#define VMOP_EQ 19
#define VMOP_MT 20
#define VMOP_LT 21
#define VMOP_SHL 29
#define VMOP_SHR 30
/* not instruction: not <x> <result> */
#define VMOP_NOT 12
/* jnz instruction: jnz <condition_reg>, <addr(4, be)> */
#define VMOP_JNZ 22
/* jmp instruction: jmp <addr(4, be)> */
#define VMOP_JMP 23
/* load instructions: loadN <addr(4, be)> <receiving_reg> */
#define VMOP_LOAD8 24
#define VMOP_LOAD16 25
#define VMOP_LOAD32 26
#define VMOP_LOAD64 27
/* ptrload instructions: loadN <ptr_reg> <receiving_reg> */
#define VMOP_PTRLOAD8 33
#define VMOP_PTRLOAD16 34
#define VMOP_PTRLOAD32 35
#define VMOP_PTRLOAD64 36
/* store instruction: store64 <sending_reg> <addr(4, be)> */
#define VMOP_STORE64 28
/* return instruction: ret <status_reg> */
#define VMOP_RET 31
#define ICONV_CURRENT_VERSION 1
#define ICONV_REG_COUNT 16
#define ICONV_INSC_LEN 8
#define ICONV_ERR_EMPTY_STREAM -2
#define ICONV_ERR_STREAM_OVERFLOW -3
#define ICONV_ERR_ILLEGAL_OPCODE -4
#define ICONV_ERR_MEMORY_OVERFLOW -5
#define ICONV_CHARSET_PATH_PREFIX "/etc/iconv/"
static const uint8_t vm_binary_magic[4] = { 'S', 'I', 'C', 'V' };
struct vm_binary_header {
uint8_t magic[4];
uint32_t version;
uint32_t from_utf8_addr;
uint32_t to_utf8_addr;
};
struct iconv_vm {
uint8_t *memory;
size_t memlen;
size_t pc;
uint64_t reg[ICONV_REG_COUNT];
};
struct iconv_stream {
char *buf;
size_t off;
size_t len;
};
static struct iconv_vm iconv_vm_new(uint8_t *memory, size_t memlen) {
struct iconv_vm vm;
vm.memory = memory;
vm.memlen = memlen;
vm.pc = 0;
memset(&vm.reg, 0, sizeof(uint64_t) * ICONV_REG_COUNT);
return vm;
}
static uint64_t htonll(uint64_t val) {
return (((uint64_t) htonl(val)) << 32) + htonl(val >> 32);
}
static uint64_t ntohll(uint64_t val) {
return (((uint64_t) ntohl(val)) << 32) + ntohl(val >> 32);
}
static uint64_t vm_reg_read_raw(struct iconv_vm *vm, uint8_t n) {
if(n >= ICONV_REG_COUNT) {
return 0;
}
return vm->reg[n];
}
static void vm_reg_write_raw(struct iconv_vm *vm, uint8_t n, uint64_t val) {
if(n >= ICONV_REG_COUNT) {
return;
}
vm->reg[n] = val;
}
static uint64_t vm_reg_read(struct iconv_vm *vm, uint8_t n) {
return ntohll(vm_reg_read_raw(vm, n));
}
static void vm_reg_write(struct iconv_vm *vm, uint8_t n, uint64_t val) {
vm_reg_write_raw(vm, n, htonll(val));
}
static int iconv_stream_read(struct iconv_stream *st, void *dst, size_t n) {
if(st->off + n > st->len) {
return -1;
}
memcpy(dst, st->buf + st->off, n);
st->off += n;
return 0;
}
static int iconv_stream_write(struct iconv_stream *st, const void *src, size_t n) {
if(st->off + n > st->len) {
return -1;
}
memcpy(st->buf + st->off, src, n);
st->off += n;
return 0;
}
static int vm_memory_read(struct iconv_vm *vm, size_t off, void *buf, size_t n) {
if(off + n > vm->memlen) {
return -1;
}
memcpy(buf, vm->memory + off, n);
return 0;
}
static int vm_memory_write(struct iconv_vm *vm, size_t off, const void *buf, size_t n) {
if(off + n > vm->memlen) {
return -1;
}
memcpy(vm->memory + off, buf, n);
return 0;
}
static int vm_execute(struct iconv_vm *vm, struct iconv_stream *in, struct iconv_stream *out) {
uint8_t insc[ICONV_INSC_LEN];
size_t mop_nbytes;
char mop_buf[8];
int64_t signed_calc_result;
char addr_buf[4];
while(1) {
memset(&mop_buf, 0, 8);
if(vm_memory_read(vm, vm->pc, &insc, ICONV_INSC_LEN) == -1) {
return ICONV_ERR_MEMORY_OVERFLOW;
}
vm->pc += ICONV_INSC_LEN;
switch(insc[0]) {
case VMOP_INPUT8:
mop_nbytes = 1;
goto vmop_input;
case VMOP_INPUT16:
mop_nbytes = 2;
goto vmop_input;
case VMOP_INPUT32:
mop_nbytes = 4;
goto vmop_input;
case VMOP_INPUT64:
mop_nbytes = 8;
vmop_input:
if(iconv_stream_read(in, (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
return ICONV_ERR_EMPTY_STREAM;
}
vm_reg_write_raw(vm, insc[1], *(uint64_t*)&mop_buf);
break;
case VMOP_OUTPUT8:
mop_nbytes = 1;
goto vmop_output;
case VMOP_OUTPUT16:
mop_nbytes = 2;
goto vmop_output;
case VMOP_OUTPUT32:
mop_nbytes = 4;
goto vmop_output;
case VMOP_OUTPUT64:
mop_nbytes = 8;
vmop_output:
*(uint64_t*)&mop_buf = vm_reg_read_raw(vm, insc[1]);
if(iconv_stream_write(out, (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
return ICONV_ERR_STREAM_OVERFLOW;
}
break;
case VMOP_XOR:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) ^ vm_reg_read(vm, insc[2]));
break;
case VMOP_AND:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) & vm_reg_read(vm, insc[2]));
break;
case VMOP_OR:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) | vm_reg_read(vm, insc[2]));
break;
case VMOP_ADD:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) + vm_reg_read(vm, insc[2]));
break;
case VMOP_SUB:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) - vm_reg_read(vm, insc[2]));
break;
case VMOP_SMUL:
signed_calc_result = (int64_t)vm_reg_read(vm, insc[1]) * (int64_t)vm_reg_read(vm, insc[2]);
vm_reg_write(vm, insc[3], signed_calc_result);
break;
case VMOP_UMUL:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) * vm_reg_read(vm, insc[2]));
break;
case VMOP_EQ:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) == vm_reg_read(vm, insc[2]));
break;
case VMOP_LT:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) < vm_reg_read(vm, insc[2]));
break;
case VMOP_MT:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) > vm_reg_read(vm, insc[2]));
break;
case VMOP_SHL:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) << vm_reg_read(vm, insc[2]));
break;
case VMOP_SHR:
vm_reg_write(vm, insc[3], vm_reg_read(vm, insc[1]) >> vm_reg_read(vm, insc[2]));
break;
case VMOP_NOT:
vm_reg_write(vm, insc[2], ~vm_reg_read(vm, insc[1]));
break;
case VMOP_JNZ:
if(vm_reg_read(vm, insc[1])) {
memcpy(&addr_buf, insc + 2, 4);
goto vmop_jop;
}
break;
case VMOP_JMP:
memcpy(&addr_buf, insc + 1, 4);
vmop_jop:
vm->pc = ntohl(*(uint32_t*)&addr_buf);
break;
case VMOP_LOAD8:
mop_nbytes = 1;
goto vmop_load;
case VMOP_LOAD16:
mop_nbytes = 2;
goto vmop_load;
case VMOP_LOAD32:
mop_nbytes = 4;
goto vmop_load;
case VMOP_LOAD64:
mop_nbytes = 8;
vmop_load:
memcpy(&addr_buf, insc + 1, 4);
if(vm_memory_read(vm, ntohl(*(uint32_t*)&addr_buf), (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
return ICONV_ERR_MEMORY_OVERFLOW;
}
vm_reg_write_raw(vm, insc[5], *(uint64_t*)&mop_buf);
break;
case VMOP_PTRLOAD8:
mop_nbytes = 1;
goto vmop_ptr_load;
case VMOP_PTRLOAD16:
mop_nbytes = 2;
goto vmop_ptr_load;
case VMOP_PTRLOAD32:
mop_nbytes = 4;
goto vmop_ptr_load;
case VMOP_PTRLOAD64:
mop_nbytes = 8;
vmop_ptr_load:
if(vm_memory_read(vm, vm_reg_read(vm, insc[1]), (mop_buf + 8 - mop_nbytes), mop_nbytes) == -1) {
return ICONV_ERR_MEMORY_OVERFLOW;
}
vm_reg_write_raw(vm, insc[2], *(uint64_t*)&mop_buf);
break;
case VMOP_STORE64:
memcpy(&addr_buf, insc + 2, 4);
*(uint64_t*)&mop_buf = vm_reg_read_raw(vm, insc[1]);
if(vm_memory_write(vm, ntohl(*(uint32_t*)&addr_buf), &mop_buf, 8) == -1) {
return ICONV_ERR_MEMORY_OVERFLOW;
}
break;
case VMOP_RET:
return (int)vm_reg_read(vm, insc[1]);
default:
return ICONV_ERR_ILLEGAL_OPCODE;
}
}
}
struct iconv {
struct iconv_vm from;
struct iconv_vm to;
};
static char *read_file(const char *path, size_t *size) {
struct stat st;
int fd = open(path, O_RDONLY);
if (fd == -1) {
return NULL;
}
if (fstat(fd, &st) == -1) {
return NULL;
}
if (st.st_size > 128 * 1024 || st.st_size <= sizeof(struct vm_binary_header)) {
return NULL;
}
char *buf = malloc(st.st_size);
memset(buf, 0, st.st_size);
while (read(fd, buf, st.st_size) > 0) {}
*size = st.st_size;
return buf;
}
iconv_t iconv_open(const char *to, const char *from) {
size_t path_prefix_len = strlen(ICONV_CHARSET_PATH_PREFIX);
char *from_memory = NULL;
char *to_memory = NULL;
char *from_path = calloc(path_prefix_len + strlen(from) + 1, 1);
char *to_path = calloc(path_prefix_len + strlen(to) + 1, 1);
size_t from_size = 0;
size_t to_size = 0;
if (!from_path || !to_path) {
goto error;
}
strcpy(from_path, ICONV_CHARSET_PATH_PREFIX);
strcpy(to_path, ICONV_CHARSET_PATH_PREFIX);
strcpy(from_path + path_prefix_len, from);
strcpy(to_path + path_prefix_len, to);
from_memory = read_file(from_path, &from_size);
to_memory = read_file(to_path, &to_size);
free(from_path);
free(to_path);
from_path = NULL;
to_path = NULL;
if (!from_memory || !to_memory) {
goto error;
}
struct iconv *result = malloc(sizeof(struct iconv));
result->from = iconv_vm_new((uint8_t*)from_memory, from_size);
result->to = iconv_vm_new((uint8_t*)to_memory, to_size);
struct vm_binary_header *from_header = (struct vm_binary_header*)from_memory;
struct vm_binary_header *to_header = (struct vm_binary_header*)to_memory;
if(memcmp(&from_header->magic, &vm_binary_magic, sizeof(vm_binary_magic))
|| ntohl(from_header->version) != ICONV_CURRENT_VERSION
|| memcmp(&to_header->magic, &vm_binary_magic, sizeof(vm_binary_magic))
|| ntohl(to_header->version) != ICONV_CURRENT_VERSION) {
goto error;
}
return result;
error:
free(from_path);
free(to_path);
free(from_memory);
free(to_memory);
errno = EINVAL;
return (iconv_t)-1;
}
size_t iconv(iconv_t cd, char **restrict in, size_t *restrict inb, char **restrict out, size_t *restrict outb) {
struct iconv *iconv = cd;
char utf8_buf[6];
struct iconv_stream utf8_stream;
struct iconv_stream in_stream;
struct iconv_stream out_stream;
struct vm_binary_header *from_header = (struct vm_binary_header*)iconv->from.memory;
struct vm_binary_header *to_header = (struct vm_binary_header*)iconv->to.memory;
utf8_stream.buf = utf8_buf;
utf8_stream.off = 0;
utf8_stream.len = 6;
in_stream.buf = *in;
in_stream.off = 0;
in_stream.len = *inb;
out_stream.buf = *out;
out_stream.off = 0;
out_stream.len = *outb;
int status;
for(;;) {
iconv->from.pc = ntohl(from_header->to_utf8_addr);
iconv->to.pc = ntohl(to_header->from_utf8_addr);
status = vm_execute(&iconv->from, &in_stream, &utf8_stream);
if(status < 0) break;
utf8_stream.off = 0;
status = vm_execute(&iconv->to, &utf8_stream, &out_stream);
if(status < 0) break;
utf8_stream.off = 0;
}
*in += in_stream.off;
*out += out_stream.off;
*inb -= in_stream.off;
*outb -= out_stream.off;
return in_stream.off;
}
int iconv_close(iconv_t cd) {
struct iconv *iconv = cd;
free(iconv->from.memory);
free(iconv->to.memory);
free(iconv);
return 0;
}