rxi / fe

A tiny, embeddable language implemented in ANSI C
MIT License
1.3k stars 81 forks source link

68k runtime issue #27

Open polluks opened 1 year ago

polluks commented 1 year ago

$ fe scripts/life.fe 
>> iteration 1 
(- # -) 
(- # -) 
(- # -) 
>> iteration 2 
(- - -) 
(# # #) 
(- - -) 
>> iteration 3 
(- # -) 
(- # -) 
(- # -) 
>> iteration 4 
(- - -) 
(# # #) 
(- - -) 
error: dotted pair in argument list 
=> (nth x (nth y grid)) 
=> (or (nth x (nth y grid)) 0) 
=> (get-cell grid (- x 1) y) 
=> (+ (get-cell grid (- x 1) (- y 1)) (get-cell grid (- x 1) y) (g 
=> (let n (+ (get-cell grid (- x 1) (- y 1)) (get-cell grid (- x 1 
=> (next-cell grid cell x y) 
=> (f (car lst)) 
=> (cons (f (car lst)) res) 
=> (= res (cons (f (car lst)) res)) 
=> (while lst (= res (cons (f (car lst)) res)) (= lst (cdr lst))) 
=> (map (fn (cell) (= x (+ x 1)) (next-cell grid cell x y)) row) 
=> (f (car lst)) 
=> (cons (f (car lst)) res) 
=> (= res (cons (f (car lst)) res)) 
=> (while lst (= res (cons (f (car lst)) res)) (= lst (cdr lst))) 
=> (map (fn (row) (= y (+ y 1)) (let x -1) (map (fn (cell) (= x (+ 
=> (next-grid grid) 
=> (= grid (next-grid grid)) 
=> (while (<= i n) (print ">> iteration" i) (print-grid grid) (pri 
ooichu commented 1 year ago

It looks like something bad is happening after collecting garbage. I added puts("Collect garbage...") to the collectgarbage() function and noticed that on m68k the garbage collector is called after the 4th iteration.

>> iteration 1
(- # -)
(- # -)
(- # -)

>> iteration 2
(- - -)
(# # #)
(- - -)

>> iteration 3
(- # -)
(- # -)
(- # -)

>> iteration 4
(- - -)
(# # #)
(- - -)

Collecting garbage...
>> iteration 5
(- # -)
(- # -)
(- # -)

But the problem is not with collectgarbage(). The only platform-dependent place in fe is the tagging of pointers. It is expected that in the low byte (in big-endian case, the high byte) of the fe_Object pointer, the two low bits (mask - 0x3) are always zeros; this is true for 32-bit alignment. These bits are used to indicate type and for marking when garbage collection is done. If the pointer actually has a different structure, it doesn't seem to work.

On qemu-m68k under linux all scripts work without problems.

polluks commented 1 year ago

BTW I tried a 6502 port and it didn't work. A platform without alignment.

ooichu commented 1 year ago

The current implementation requires at least 32-bit aligned addresses. It may be worth adding this problem to the 'Known Issues' in impl.md. This will not work on the 6502 without code changes because it has 16-bit pointers. This means that the pointer tagging trick will only work for bit 0 if the memory is aligned to 16 bits. You can try manually align the memory to 32 bits. In fe_open() you can replace: for (i = 0; i < ctx->object_count; i++) with for (i = 0; i < ctx->object_count; i += 2); the memory block passed to fe_open() must initially be 32-bit aligned. If this is the only problem, it should work, but it will require more memory.

polluks commented 1 year ago

Well, I had to reduce buf's size to 6000 because of memory constraints, 32-bit aligned is not an option. Maybe you are guessing that AmigaOS has an 31-bit address space because the most significant bit is used for management...

ooichu commented 1 year ago

I think if fe_open() aligns memory before populating free list, the issue should be solved. I did that, check it out. Funny, but 3 years ago, @rxi deleted something that would solve the problem now.

polluks commented 1 year ago

Nice try but it didn't help.

polluks commented 1 year ago

The compiler constructor said this code seems to be undefined behavior, please take a look: #define tag(x) ( (x)->car.c ) ... typedef union { fe_Object *o; fe_CFunc f; fe_Number n; char c; } Value; Maybe https://stackoverflow.com/questions/1812348/a-question-about-union-in-c-store-as-one-type-and-read-as-another-is-it-impl

ooichu commented 1 year ago

Perhaps the compiler says this because #define tag(x) ((x)->car.c ) depends on the CPU architecture, namely the size of the other fields of the Value union and the order of bytes in the system. If the architecture is little-endian, there should be no problem.

From C standard:

6.2.6 Representations of types ... 6.2.6.1 General ... 5 When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values. The values of padding bytes shall not affect whether the value of such an object is a trap representation. Those bits of a structure or union object that are in the same byte as a bit-field member, but are not part of that member, shall similarly not affect whether the value of such an object is a trap representation. 6 When a value is stored in a member of an object of union type, the bytes of the object representation that do not correspond to that member but do correspond to other members take unspecified values, but the value of the union object shall not thereby become a trap representation. ...

The problem described in https://stackoverflow.com/questions/1812348/a-question-about-union-in-c-store-as-one-type-and-read-as-another-is-it-impl is related to incorrect initialization, there is no such problem here.

ooichu commented 1 year ago

I'm tried to compile for Amiga with vbcc compiler (without float point), and I got stack overflow on collectgarbage() with -stack-check flag. I don't know much about Amiga, but I compiled for kickstart 1.3. Perhaps the behavior in AmigaOS will be different? What did you test on?

polluks commented 1 year ago

You may type stack 99999 before execution. I'm testing on MorphOS.

ooichu commented 1 year ago

Now it works well. If you leave out the fact that it's very slow.