Compare commits

...

82 Commits
v1.2 ... master

Author SHA1 Message Date
willow
8ff0651552 parse: deny non-digit after minus in getint
also: '0' <= c <= '9' -> isdigit(c)
Signed-off-by: willow <im@purring.fyi>
2026-02-28 19:05:57 +01:00
willow
7ac9722ccb remove unused variable
Signed-off-by: willow <im@purring.fyi>
2026-02-28 19:05:15 +01:00
Quentin Carbonneaux
4f9b94a9b3 formatting fixes in tests 2026-02-12 09:23:13 +01:00
Quentin Carbonneaux
5f40188f9e cosmetics in emit.c 2026-02-12 09:21:15 +01:00
Scott Graham
01102ad63b winabi: fix isel of large consts
ABI lowering for winabi was incorrectly using a Kl cls when emitting
Ostorel, which in turn was causing isel to fail to lower large constants
in to temporaries ( https://c9x.me/git/qbe.git/tree/amd64/isel.c#n107 )
when necessary.

(This should be applied on the 'winabi' branch.)
2026-02-12 09:17:17 +01:00
Scott Graham
d5f02dc67c winabi: fix allocation of parameters to regs with hidden arg
In the presence of the hidden arg for return-by-value, the
registers used for natural arguments were incorrect.

(This should be applied on the 'winabi' branch.)
2026-02-12 09:17:17 +01:00
Scott Graham
d166a61141 Implementation of Windows amd64_win target
This is an implementation of the Windows ABI. It supports most features
(struct passing/returning, varargs, env). TLS is not yet supported.

This patch does not actually port QBE to Windows, it only allows QBE to
generate correct asm to target Windows. As a result, testing is
accomplished on a Linux host, by using a cross-compiling toolchain, and
running the resulting binaries by using wine. See:

	TARGET=amd64_win tools/test.sh all

A few cross-platform tests were changed from 'long' to 'long long' in
driver code because long in C does not match the size of a QBE 'l' on
Windows.
2026-02-12 09:17:17 +01:00
Richard McCormack
cf06ce159d Modify amd64 fixarg to fix calling constant addresses
On x86_64, direct calls are always PC-relative. This means that
in order to call an absolute address, the call must be indirect.

To accomplish this, update fixarg to introduce a temporary before
emitting.
2026-01-13 21:24:31 +01:00
Quentin Carbonneaux
640c78d0da fix typo in simplcfg 2026-01-13 20:36:23 +01:00
Quentin Carbonneaux
afd5d2e518 drop dead preds in fixphis
It is possible that GVN removes
some dead blocks, this could lead
to odd - but probably harmless -
phi args appearing in the IL.
This patch cleans things up during
fillcfg().
2026-01-13 18:27:50 +01:00
Quentin Carbonneaux
e8365dd0a2 new simplcfg pass
Useful for ifopt to match more
often. Empty blocks are fused
and conditional jumps on empty
blocks with the same successor
(and no phis in the successor)
are collapsed.
2026-01-13 18:17:35 +01:00
Quentin Carbonneaux
c6336557da ifopt simplifications 2026-01-13 18:11:37 +01:00
Roland Paterson-Jones
5c1eb24e2c If-conversion RFC 4 - x86 only (for now), use cmovXX
Replacement of tiny conditional jump graphlets with
conditional move instructions.

Currently enabled only for x86. Arm64 support using cselXX
will be essentially identical.

Adds (internal) frontend sel0/sel1 ops with flag-specific
backend xselXX following jnz implementation pattern.

Testing: standard QBE, cproc, harec, hare, roland
2026-01-13 18:11:30 +01:00
Quentin Carbonneaux
7201079137 update copyright years 2026-01-13 17:09:41 +01:00
Quentin Carbonneaux
112cc1b824 rv64: handle slots in jnz 2026-01-13 17:09:41 +01:00
Quentin Carbonneaux
6a2dca8b99 fix jmp arg spilling
In case we need to spill to accomodate
for the jump argument, piggyback the
reloads from slots to regalloc so that
they can be correctly inserted on edges.
2026-01-13 17:09:41 +01:00
Quentin Carbonneaux
e0ded59639 please as with truncated constants
Apple's assembler actually hard
crashed on overflows.
2026-01-06 20:43:42 +01:00
Quentin Carbonneaux
0f6bbb1c7c arm64_apple: fix argxbh support 2026-01-06 20:43:42 +01:00
Quentin Carbonneaux
73f0accb45 arm64: prevent bogus IP1 clobbers 2026-01-06 20:43:38 +01:00
Quentin Carbonneaux
03da40271f rv64: fix invalid float immediates
Thanks to Luke Graham for reporting
and fixing this issue.
2026-01-05 22:19:53 +01:00
Quentin Carbonneaux
120f316162 skip deleted phis in use width scan 2025-05-30 17:40:17 +02:00
Quentin Carbonneaux
8d5b86ac4c fix fp constants on big endian hosts 2025-04-16 10:29:00 +02:00
Horst H. von Brand
348f2eac90 minic: C23 doesn't allow bool as identifier
Signed-off-by: Horst H. von Brand <vonbrand@inf.utfsm.cl>
2025-03-16 11:27:49 +01:00
Antonio Terceiro
903610de4f tools/test.sh: test the native architecture without QEMU
While at it, extract most duplicated code across targets into a
function.
2025-03-15 14:43:33 +01:00
Antonio Terceiro
f7ab20680b tools/test.sh: allow running against installed package
If $bin is set in the environment, use it instead of using `qbe` from
the source tree. The same for $binref. This supports the following use
cases:

- I have a qbe package installed, and I want to test my local changes
  with the installed packages as a reference:

  $ binref=/usr/bin/qbe ./tools/test.sh all

- I want to test the installed qbe against new tests that I have
  written, to reproduce a bug:

  $ bin=/usr/bin/qbe ./tools/test.sh test/newtest.ssa

In Debian, we also run tests against the installed package when
dependencies change, etc. We will also run on several architectures
where the necessary cross compilers might not be available. So make
tests that cannot be run because of a missing compiler exit with 77,
signaling to Debian's autopkgtest that the test is skipped.
2025-03-15 14:43:00 +01:00
Antonio Terceiro
789e38fdb2 Makefile: add explicit target to test the x86_64 backend
When developing on an arm64 machine, it's useful to be able to test the
x86_64 target.
2025-03-15 14:40:55 +01:00
Quentin Carbonneaux
86d13b93e8 arm64: use IP1 as scratch register
On Apple platforms x18 is not guaranteed
to be preserved across context switches.
So we now use IP1 as scratch register.

En passant, one dubious use of IP0 in
arm64/emit.c fixarg() was transitioned
to IP1. I believe the previous code could
clobber a user value if IP0 was live.
2025-03-15 14:27:34 +01:00
Quentin Carbonneaux
ec5502645f 10 years of qbe! 2025-03-14 13:09:42 +01:00
Quentin Carbonneaux
f3ca257737 gvn/gcm review
- Many stylistic nits.
- Removed blkmerge().
- Some minor bug fixes.
- GCM reassoc is now "sink"; a pass that
  moves trivial ops in their target block
  with the same goal of reducing register
  pressure, but starting from instructions
  that benefit from having their inputs
  close.
2025-03-14 13:09:21 +01:00
Roland Paterson-Jones
1cb255cb04 Get rid of movins() infra. 2025-03-14 13:07:40 +01:00
Roland Paterson-Jones
c2ff93e75e Global Value Numbering / Global Code Motion
More or less as proposed in its ninth iteration with the
addition of a gcmmove() functionality to restore coherent
local schedules.

Changes since RFC 8:

Features:

 - generalization of phi 1/0 detection
 - collapse linear jmp chains before GVN; simplifies if-graph
     detection used in 0/non-0 value inference and if-elim...
 - infer 0/non-0 values from dominating blk jnz; eliminates
     redundant cmp eq/ne 0 and associated jnz/blocks, for example
     redundant null pointer checks (hare codebase likes this)
 - remove (emergent) empty if-then-else graphlets between GVN and
     GCM; improves GCM instruction placement, particularly cmps.
 - merge %addr =l add %addr1, N sequences - reduces tmp count,
     register pressure.
 - squash consecutive associative ops with constant args, e.g.
     t1 = add t, N ... t2 = add t2, M -> t2 = add t, N+M

Bug Fixes:

 - remove "cmp eq/ne of non-identical RCon's " in copyref().
   RCon's are not guaranteed to be dedup'ed, and symbols can
   alias.

Codebase:

  - moved some stuff into cfg.c including blkmerge()
  - some refactoring in gvn.c
  - simplification of reassoc.c - always reassoc all cmp ops
      and Kl add %t, N. Better on coremark, smaller codebase.
  - minor simplification of movins() - use vins

Testing - standard QBE, cproc, hare, harec, coremark
          [still have Rust build issues with latest roland]

Benchmark
- coremark is ~15%+ faster than master
- hare "HARETEST_INCLUDE='slow' make check" ~8% faster
    (crypto::sha1::sha1_1gb is biggest obvious win - ~25% faster)

Changes since RFC 7:

Bug fixes:

- remove isbad4gcm() in GVN/GCM - it is unsound due to different state
    at GVN vs GCM time; replace with "reassociation" pass after GCM
- fix intra-blk use-before-def after GCM
- prevent GVN from deduping trapping instructions cos GCM will not
    move them
- remove cmp eq/ne identical arg copy detection for floating point, it
    is not valid for NaN
- fix cges/cged flagged as commutative in ops.h instead of cnes/cned
    respectively; just a typo

Minor features:

- copy detection handles cmp le/lt/ge/gt with identical args
- treat (integer) div/rem by non-zero constant as non-trapping
- eliminate add N/sub N pairs in copy detection
- maintain accurate tmp use in GVN; not strictly necessary but enables
    interim global state sanity checking
- "reassociation" of trivial constant offset load/store addresses, and
    cmp ops with point-of-use in pass after GCM
- normalise commutative op arg order - e.g. op con, tmp -> op tmp, con
    to simplify copy detection and GVN instruction dedup

Codebase:

- split out core copy detection and constant folding (back) out into
    copy.c, fold.c respectively; gvn.c was getting monolithic
- generic support for instruction moving in ins.c - used by GCM and
    reassoc
- new reassociation pass in reassoc.c
- other minor clean-up/refactor

Changes since RFC 6:

- More ext elimination in GVN by examination of def and use bit width
- elimination of redundant and mask by bit width examination
- Incorporation of Song's patch

Changes since RFC 5:

- avoidance of "bad" candidates for GVN/GCM - trivial address offset
    calculations, and comparisons
- more copy detection mostly around boolean values
- allow elimination of unused load, alloc, trapping instructions
- detection of trivial boolean v ? 1 : 0 phi patterns
- bug fix for (removal of) "chg" optimisation in ins recreation - it
    was missing removal of unused instructions in some cases

ifelim() between GVN and GCM; deeper nopunused()
2025-03-14 09:58:37 +01:00
Roland Paterson-Jones
9e36cbe4d8 Combine fillrpo() and fillpreds() into fillcfg().
Remove edgedel() calls from fillrpo().

Call new prunephis() from fillpreds().
[Curiously this never seems to do anything even tho edgedel()
is no longer called from fillrpo()]

One remaining fillpreds() call in parse.c typecheck - seems
like it will still work the same.

defensive; fillcfg() combining fillrpo() and fillpreds() - problem after simpljmp() - think it is cos fillrpo() is still doing edgedel() which should now be covered by fillpreds()

comment out edgedel() in fillrpo() - fillcfg() no longer asserts after simpljmp() but seems like prunephis() never triggers???

static fillrpo(); remove edgedel() from fillrpo()

replace fillrpo() and/or fillpreds() with fillcfg()
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
ecfdac4f00 Simplify fillpreds()
Now that b->pred is a vector we do can remove the counting pass.
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
1c769584ac Simplify fillrpo()
Essentially use post-order as id, then reverse to rpo.
Avoids needing f->nblk initially; slightly simpler logic.
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
0ce9966c23 Re-use (vgrow) b->ins vector in backend xxx_abi() fn's.
Removes last re-allocation of b->ins.
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
434cf5fdc4 idup(Ins **, Ins *, ulong) -> idup(Blk *, Ins *, ulong)
Always used this way and factors setting b->nins.
Makes b->ins vector contract more obvious.
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
ca928f6f8d Blk::ins is a vector
Scratching an itch - avoid unnecesary re-allocation in idup()
which is called often in the optimisation chain.

Blk::ins is reallocated in xxx_abi() - needs further fiddling.
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
024dffac8b Blk::pred is a vector
Scratching an itch - avoid unnecesary re-allocation in fillpred()
which is called often in the optimisation chain.
2025-03-14 09:47:05 +01:00
Roland Paterson-Jones
c16f7eafca Fn::rpo is a vector
Scratching an itch - avoid unnecesary re-allocation in fillrpo()
which is called multiple times in the optimisation chain.
2025-03-14 09:47:05 +01:00
Quentin Carbonneaux
327736b3a6 handle large hfas correctly on arm64 2024-12-19 15:27:16 +01:00
Quentin Carbonneaux
90050202f5 fix various codegen bugs on arm64
- dynamic allocations could generate
  bad 'and' instructions (for the
  and with -16 in salloc()).
- symbols used in w context would
  generate adrp and add instructions
  on wN registers while they seem to
  only work on xN registers.

Thanks to Rosie for reporting them.
2024-10-01 19:42:50 +02:00
Quentin Carbonneaux
626f0b2781 skip preludes for some leaf fns
When rbp is not necessary to compile
a leaf function, we skip saving and
restoring it.
2024-08-23 18:31:53 +02:00
Alexey Yerin
bb8de8c633 arm64/isel: Avoid signed overflow when handling immediates
Clang incorrectly optimizes this negation with -O2 and causes QBE to
emit 0 in place of INT64_MIN.
2024-08-15 23:21:05 +02:00
Quentin Carbonneaux
efcb5465e5 align emitted code
Functions are now aligned on 16-byte
boundaries. This mimics gcc and should
help reduce the maximum perf impact of
cosmetic code changes. Previously, any
change in the output of qbe could have
far reaching implications on alignment.

Thanks to Roland Paterson-Jones for
pointing out the variability issue.
2024-08-15 23:11:41 +02:00
Quentin Carbonneaux
e8fa27bcdb drop imul rewriting
This was cute to do, but it is
largely inconsequential, as shown
by the rough timings below:

    benchmarking mul8_lea
      3.9 ticks ± 0.88 (min: 3)
    benchmarking mul8_imul
      3.3 ticks ± 0.27 (min: 3)
    benchmarking div8_udiv
      6.5 ticks ± 0.52 (min: 6)
    benchmarking div8_shr
      3.3 ticks ± 0.34 (min: 3)
2024-06-19 12:39:26 +02:00
Quentin Carbonneaux
7de34d3454 no mul->shl as it confuses address matching
Additionally, the strength-reduction
for small powers of two is handled
by amd64/emit.c now.
2024-06-19 10:58:17 +02:00
Quentin Carbonneaux
6dab93b4b2 cheaper mul by small constants on amd64 2024-06-18 17:06:56 +02:00
Quentin Carbonneaux
96f18be71d simplify 8*x as well as x*8 2024-06-18 16:59:42 +02:00
Quentin Carbonneaux
0631bc4fec prevent bogus simplifications 2024-06-17 13:24:22 +02:00
Quentin Carbonneaux
e7ebdc8fb6 qbe has its own magic 2024-06-17 13:17:39 +02:00
Quentin Carbonneaux
b5be429091 fix unintended assignment 2024-06-16 22:31:38 +02:00
Quentin Carbonneaux
ddf5ced4a7 revert 4bc4c958
Hopefully the right time now!
2024-06-16 22:26:51 +02:00
Roland Paterson-Jones
7a19de5cf4 Simplify int mul/udiv/urem of 2^N into shl/shr/and.
Passes the "standard" test suite.

(cproc bootstrap, hare[c] make test, roland units, linpack/coremark run)

However linpack benchmark is now notably slower. Coremark is ~2% faster.

As noticed before, linmark timing is dubious, and maybe my cheap (AMD) laptop
prefers mul to shl.
2024-06-16 21:42:17 +02:00
Roland Paterson-Jones
cf9f95f521 Optab-driven copy detection 2024-06-09 22:33:38 +02:00
Quentin Carbonneaux
2c2051542b relax one assert
In this branch we only need that br[b->loop].b
is defined. This is the case if b->loop >= n.
2024-06-05 12:50:36 +02:00
Erica Z
c8220b638b replace asm keyword
when applying a custom set of CFLAGS under clang that does not include
-std=c99, asm is treated as a keyword and as such can not be used as an
identifier. this prevents the issue by renaming the offending variables.
2024-05-28 10:39:41 +02:00
Quentin Carbonneaux
620277c004 add width info for comparisons
Comparisons return a 1-bit value, in theory
we could add a Wu1 width for them but I did
not bother and just used Wub. This simply
means that if a frontend generates an extsb
of a comparison result (silly), we will not
generate good code.
2024-05-03 21:51:30 +02:00
Quentin Carbonneaux
1dd22a9b47 function params must be unique 2024-04-27 10:52:03 +02:00
Quentin Carbonneaux
b24af7d3f7 revert 1b7770e271
Quotes are used on Apple target
variants to flag that we must
not add the _ symbol prefix.
2024-04-22 14:01:50 +02:00
Michael Forney
99169df2ff parse: use dynamically sized hashtable for temporaries
This significantly improves parsing performance for massive functions
with a huge number of temporaries. Parsing the 86MiB IL produced
by cproc during zig bootstrap drops from 17m15s to 2.5s (over 400x
speedup).

The speedup is much smaller for IL produced from normal non-autogenerated
C code. Parsing the sqlite3 amalgamation drops from 0.40s to 0.33s.
2024-04-13 13:31:02 +02:00
Quentin Carbonneaux
fc98435f81 add "make wc" 2024-04-12 11:49:00 +02:00
Quentin Carbonneaux
875c1abf93 drop unnecessary check 2024-04-12 11:37:35 +02:00
Quentin Carbonneaux
8ded7a56a2 add common linkage for data 2024-04-12 11:33:58 +02:00
Quentin Carbonneaux
4a809d69b5 fold scaled offsets in addresses 2024-04-11 14:14:53 +02:00
Quentin Carbonneaux
8e8f706436 drop over-zealous offset accumulation 2024-04-11 10:22:46 +02:00
Quentin Carbonneaux
2d046a0ac6 use mgen in amd64/isel.c 2024-04-09 21:47:16 +02:00
Quentin Carbonneaux
a609527752 mgen: match automatons and C generation
The algorithm to generate matchers
took a long time to be discovered
and refined to its present version.
The rest of mgen is mostly boring
engineering.

Extensive fuzzing ensures that the
two core components of mgen (tables
and matchers generation) are correct
on specific problem instances.
2024-04-09 21:45:42 +02:00
Quentin Carbonneaux
56e2263ca4 fuse ac rules in ins-tree matching
The initial plan was to have one
matcher per ac-variant, but that
leads to way too much generated
code. Instead, we can fuse ac
variants of the rules and have
a smarter matching algorithm to
recover bound variables.
2024-04-09 21:34:57 +02:00
Quentin Carbonneaux
8a5e1c3a23 does not look too good 2024-04-09 21:34:57 +02:00
Quentin Carbonneaux
a374da3c2e modulo ac matching and more tests 2024-04-09 21:32:49 +02:00
Quentin Carbonneaux
24d1324424 wip ins-tree matcher 2024-04-09 21:32:49 +02:00
Quentin Carbonneaux
a2ad38086c mcf's improved rsval()
It is semantically the same but
does not rely on implementation-
defined behavior.
2024-04-07 22:18:01 +02:00
Quentin Carbonneaux
dc3f7d7c4a fix accidentally noop loop
Credit goes to Roland Paterson-Jones
for spotting this bug.
2024-04-04 21:56:50 +02:00
Quentin Carbonneaux
de5ced474d do not parse +N constants
The parsing code for these constants
conflicts with the Tplus token.
2024-04-03 23:30:07 +02:00
Richard McCormack
c8fc20b8ef Eliminate fully overlapping blits after coalescing stack slots
I noticed that my compiler was generating redundant blits, and after
looking through the QBE debug output I believe that I found some low
hanging fruit to help clean them up.

I'm new to this codebase, so please treat this patch with a lot of
skepticism. Happy to make any changes.

Thanks for reviewing, and thank you for QBE!
2024-04-03 22:12:36 +02:00
Michael Forney
85827e2232 check that data alignment is in range and a power of two
Otherwise, the alignment gets truncated to fit in char, so
`align 256` is handled as no alignment requirement.
2024-03-28 10:35:11 +01:00
Michael Forney
1b7770e271 Drop quotes around floating point constant labels
This is incompatible with binutils gas older than 2.26.
2024-03-26 09:22:06 +01:00
Nguyễn Gia Phong
be5d46fe7d test/fpcnv: skip of inexactly converted integers
In C, if a floating point cannot be represented exactly as an integer,
conversion from the former to the latter is implementation-defined.
Therefore, it can be flaky to test this against QBE-defined behavior.

This was discovered from (unsigned int) 4294967295.0f being an UB,
because (uint64_t) 4294967295.0f is 4294967296 > UINT_MAX
on amd64 when compiled by either gcc or clang.
2024-03-07 16:52:43 +01:00
Nguyễn Gia Phong
b582e84e10 remove duplicated function declarations
References: 12f9d16c7b ("create cfg.c for cfg-related functions")
2024-03-07 16:46:10 +01:00
Quentin Carbonneaux
9ee57a637a update copyright years 2024-03-07 16:45:22 +01:00
Nguyễn Gia Phong
190fd3fb09 declare check-rv64 as a phony make target
References: 4e93eeaa3b ("add rv64 backend")
2024-03-07 16:44:48 +01:00
Quentin Carbonneaux
bd7a73d0ea fix bug in alias analysis
The handling of phi was incorrect
and we would sometimes miss escapes.
We now handle phis at the very end
of the pass to make sure the defs
for their arguments have all been
processed.
2024-03-07 16:40:51 +01:00
71 changed files with 7004 additions and 1333 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ qbe
config.h
.comfile
*.out
*~

View File

@ -1,4 +1,4 @@
© 2015-2023 Quentin Carbonneaux <quentin@c9x.me>
© 2015-2026 Quentin Carbonneaux <quentin@c9x.me>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@ -5,8 +5,9 @@ PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
COMMOBJ = main.o util.o parse.o abi.o cfg.o mem.o ssa.o alias.o load.o \
copy.o fold.o simpl.o live.o spill.o rega.o emit.o
AMD64OBJ = amd64/targ.o amd64/sysv.o amd64/isel.o amd64/emit.o
copy.o fold.o gvn.o gcm.o simpl.o ifopt.o live.o spill.o rega.o \
emit.o
AMD64OBJ = amd64/targ.o amd64/sysv.o amd64/isel.o amd64/emit.o amd64/winabi.o
ARM64OBJ = arm64/targ.o arm64/abi.o arm64/isel.o arm64/emit.o
RV64OBJ = rv64/targ.o rv64/abi.o rv64/isel.o rv64/emit.o
OBJ = $(COMMOBJ) $(AMD64OBJ) $(ARM64OBJ) $(RV64OBJ)
@ -71,12 +72,18 @@ clean-gen: clean
check: qbe
tools/test.sh all
check-x86_64: qbe
TARGET=x86_64 tools/test.sh all
check-arm64: qbe
TARGET=arm64 tools/test.sh all
check-rv64: qbe
TARGET=rv64 tools/test.sh all
check-amd64_win: qbe
TARGET=amd64_win tools/test.sh all
src:
@echo $(SRCALL)
@ -90,4 +97,7 @@ src:
}" < $$F; \
done
.PHONY: clean clean-gen check check-arm64 src 80 install uninstall
wc:
@wc -l $(SRCALL)
.PHONY: clean clean-gen check check-arm64 check-rv64 src 80 wc install uninstall

2
README
View File

@ -15,4 +15,4 @@ The LICENSE file applies to all files distributed.
Invoke make in this directory to create the executable
file qbe. Install using 'make install', the standard
DESTDIR and PREFIX environment variables are supported.
Alternatively, you may simply copy the qbe binary manually.
Alternatively, you may simply copy the qbe binary.

View File

@ -132,7 +132,7 @@ store(Ref r, int sz, Fn *fn)
void
fillalias(Fn *fn)
{
uint n, m;
uint n;
int t, sz;
int64_t x;
Blk *b;
@ -146,8 +146,6 @@ fillalias(Fn *fn)
for (n=0; n<fn->nblk; ++n) {
b = fn->rpo[n];
for (p=b->phi; p; p=p->link) {
for (m=0; m<p->narg; m++)
esc(p->arg[m], fn);
assert(rtype(p->to) == RTmp);
a = &fn->tmp[p->to.val].alias;
assert(a->type == ABot);
@ -217,4 +215,8 @@ fillalias(Fn *fn)
if (b->jmp.type != Jretc)
esc(b->jmp.arg, fn);
}
for (b=fn->start; b; b=b->link)
for (p=b->phi; p; p=p->link)
for (n=0; n<p->narg; n++)
esc(p->arg[n], fn);
}

81
all.h
View File

@ -21,6 +21,7 @@ typedef struct Phi Phi;
typedef struct Blk Blk;
typedef struct Use Use;
typedef struct Sym Sym;
typedef struct Num Num;
typedef struct Alias Alias;
typedef struct Tmp Tmp;
typedef struct Con Con;
@ -43,6 +44,7 @@ enum {
struct Target {
char name[16];
char apple;
char windows;
int gpr0; /* first general purpose reg */
int ngpr;
int fpr0; /* first floating point reg */
@ -61,6 +63,7 @@ struct Target {
void (*emitfin)(FILE *);
char asloc[4];
char assym[4];
uint cansel:1;
};
#define BIT(n) ((bits)1 << (n))
@ -115,7 +118,7 @@ static inline int rtype(Ref r)
static inline int rsval(Ref r)
{
return (int32_t)((int64_t)r.val << 3) >> 3;
return ((int)r.val ^ 0x10000000) - 0x10000000;
}
enum CmpI {
@ -182,6 +185,8 @@ enum {
Oalloc1 = Oalloc16,
Oflag = Oflagieq,
Oflag1 = Oflagfuo,
Oxsel = Oxselieq,
Oxsel1 = Oxselfuo,
NPubOp = Onop,
Jjf = Jjfieq,
Jjf1 = Jjffuo,
@ -190,6 +195,7 @@ enum {
#define INRANGE(x, l, u) ((unsigned)(x) - l <= u - l) /* linear in x */
#define isstore(o) INRANGE(o, Ostoreb, Ostored)
#define isload(o) INRANGE(o, Oloadsb, Oload)
#define isalloc(o) INRANGE(o, Oalloc4, Oalloc16)
#define isext(o) INRANGE(o, Oextsb, Oextuw)
#define ispar(o) INRANGE(o, Opar, Opare)
#define isarg(o) INRANGE(o, Oarg, Oargv)
@ -197,6 +203,7 @@ enum {
#define isparbh(o) INRANGE(o, Oparsb, Oparuh)
#define isargbh(o) INRANGE(o, Oargsb, Oarguh)
#define isretbh(j) INRANGE(j, Jretsb, Jretuh)
#define isxsel(o) INRANGE(o, Oxsel, Oxsel1)
enum {
Kx = -1, /* "top" class (see usecheck() and clsmerge()) */
@ -212,7 +219,16 @@ enum {
struct Op {
char *name;
short argcls[2][4];
int canfold;
uint canfold:1;
uint hasid:1; /* op identity value? */
uint idval:1; /* identity value 0/1 */
uint commutes:1; /* commutative op? */
uint assoc:1; /* associative op? */
uint idemp:1; /* idempotent op? */
uint cmpeqwl:1; /* Kl/Kw cmp eq/ne? */
uint cmplgtewl:1; /* Kl/Kw cmp lt/gt/le/ge? */
uint eqval:1; /* 1 for eq; 0 for ne */
uint pinned:1; /* GCM pinned op? */
};
struct Ins {
@ -227,7 +243,8 @@ struct Phi {
Ref *arg;
Blk **blk;
uint narg;
int cls;
short cls;
uint visit:1;
Phi *link;
};
@ -250,6 +267,7 @@ struct Blk {
Blk *dom, *dlink;
Blk **fron;
uint nfron;
int depth;
Blk **pred;
uint npred;
@ -281,6 +299,12 @@ struct Sym {
uint32_t id;
};
struct Num {
uchar n;
uchar nl, nr;
Ref l, r;
};
enum {
NoAlias,
MayAlias,
@ -335,6 +359,7 @@ struct Tmp {
Wuw
} width;
int visit;
uint gcmbid;
};
struct Con {
@ -364,6 +389,7 @@ struct Addr { /* amd64 addressing */
struct Lnk {
char export;
char thread;
char common;
char align;
char *sec;
char *secf;
@ -383,8 +409,10 @@ struct Fn {
Blk **rpo;
bits reg;
int slot;
int salign;
char vararg;
char dynalloc;
char leaf;
char name[NString];
Lnk lnk;
};
@ -458,29 +486,36 @@ void freeall(void);
void *vnew(ulong, size_t, Pool);
void vfree(void *);
void vgrow(void *, ulong);
void addins(Ins **, uint *, Ins *);
void addbins(Ins **, uint *, Blk *);
void strf(char[NString], char *, ...);
uint32_t intern(char *);
char *str(uint32_t);
int argcls(Ins *, int);
int isreg(Ref);
int iscmp(int, int *, int *);
void igroup(Blk *, Ins *, Ins **, Ins **);
void emit(int, int, Ref, Ref, Ref);
void emiti(Ins);
void idup(Ins **, Ins *, ulong);
void idup(Blk *, Ins *, ulong);
Ins *icpy(Ins *, Ins *, ulong);
int cmpop(int);
int cmpneg(int);
int cmpwlneg(int);
int clsmerge(short *, short);
int phicls(int, Tmp *);
uint phiargn(Phi *, Blk *);
Ref phiarg(Phi *, Blk *);
Ref newtmp(char *, int, Fn *);
void chuse(Ref, int, Fn *);
int symeq(Sym, Sym);
Ref newcon(Con *, Fn *);
Ref getcon(int64_t, Fn *);
int addcon(Con *, Con *);
int addcon(Con *, Con *, int);
int isconbits(Fn *fn, Ref r, int64_t *v);
void salloc(Ref, Ref, Fn *);
void dumpts(BSet *, Tmp *, FILE *);
void runmatch(uchar *, Num *, Ref, Ref *);
void bsinit(BSet *, uint);
void bszero(BSet *);
uint bscount(BSet *);
@ -512,16 +547,21 @@ void elimsb(Fn *);
/* cfg.c */
Blk *newblk(void);
void edgedel(Blk *, Blk **);
void fillpreds(Fn *);
void fillrpo(Fn *);
void fillcfg(Fn *);
void filldom(Fn *);
int sdom(Blk *, Blk *);
int dom(Blk *, Blk *);
void fillfron(Fn *);
void loopiter(Fn *, void (*)(Blk *, Blk *));
void filldepth(Fn *);
Blk *lca(Blk *, Blk *);
void fillloop(Fn *);
void simpljmp(Fn *);
int reaches(Fn *, Blk *, Blk *);
int reachesnotvia(Fn *, Blk *, Blk *, Blk *);
int ifgraph(Blk *, Blk **, Blk **, Blk **);
void simplcfg(Fn *);
/* mem.c */
void promote(Fn *);
@ -539,17 +579,31 @@ int storesz(Ins *);
void loadopt(Fn *);
/* ssa.c */
void adduse(Tmp *, int, Blk *, ...);
void filluse(Fn *);
void fillpreds(Fn *);
void fillrpo(Fn *);
void ssa(Fn *);
void ssacheck(Fn *);
/* copy.c */
void copy(Fn *);
void narrowpars(Fn *fn);
Ref copyref(Fn *, Blk *, Ins *);
Ref phicopyref(Fn *, Blk *, Phi *);
/* fold.c */
void fold(Fn *);
int foldint(Con *, int, int, Con *, Con *);
Ref foldref(Fn *, Ins *);
/* gvn.c */
extern Ref con01[2]; /* 0 and 1 */
int zeroval(Fn *, Blk *, Ref, int, int *);
void gvn(Fn *);
/* gcm.c */
int pinned(Ins *);
void gcm(Fn *);
/* ifopt.c */
void ifconvert(Fn *fn);
/* simpl.c */
void simpl(Fn *);
@ -570,7 +624,8 @@ void emitfnlnk(char *, Lnk *, FILE *);
void emitdat(Dat *, FILE *);
void emitdbgfile(char *, FILE *);
void emitdbgloc(uint, uint, FILE *);
int stashbits(void *, int);
int stashbits(bits, int);
void elf_emitfnfin(char *, FILE *);
void elf_emitfin(FILE *);
void macho_emitfin(FILE *);
void pe_emitfin(FILE *);

View File

@ -4,14 +4,14 @@ typedef struct Amd64Op Amd64Op;
enum Amd64Reg {
RAX = RXX+1, /* caller-save */
RCX,
RDX,
RSI,
RDI,
R8,
R9,
R10,
R11,
RCX, /* caller-save */
RDX, /* caller-save */
RSI, /* caller-save on sysv, callee-save on win */
RDI, /* caller-save on sysv, callee-save on win */
R8, /* caller-save */
R9, /* caller-save */
R10, /* caller-save */
R11, /* caller-save */
RBX, /* callee-save */
R12,
@ -41,9 +41,13 @@ enum Amd64Reg {
NFPR = XMM14 - XMM0 + 1, /* reserve XMM15 */
NGPR = RSP - RAX + 1,
NGPS = R11 - RAX + 1,
NFPS = NFPR,
NCLR = R15 - RBX + 1,
NGPS_SYSV = R11 - RAX + 1,
NCLR_SYSV = R15 - RBX + 1,
NGPS_WIN = R11 - RAX + 1 - 2, /* -2 for RDI/RDI */
NCLR_WIN = R15 - RBX + 1 + 2, /* +2 for RDI/RDI */
};
MAKESURE(reg_not_tmp, XMM15 < (int)Tmp0);
@ -63,8 +67,16 @@ bits amd64_sysv_retregs(Ref, int[2]);
bits amd64_sysv_argregs(Ref, int[2]);
void amd64_sysv_abi(Fn *);
/* winabi.c */
extern int amd64_winabi_rsave[];
extern int amd64_winabi_rclob[];
bits amd64_winabi_retregs(Ref, int[2]);
bits amd64_winabi_argregs(Ref, int[2]);
void amd64_winabi_abi(Fn *);
/* isel.c */
void amd64_isel(Fn *);
/* emit.c */
void amd64_emitfn(Fn *, FILE *);
void amd64_sysv_emitfn(Fn *, FILE *);
void amd64_winabi_emitfn(Fn *, FILE *);

View File

@ -1,25 +1,33 @@
#include "all.h"
typedef struct E E;
struct E {
FILE *f;
Fn *fn;
int fp;
uint64_t fsz;
int nclob;
};
#define CMP(X) \
X(Ciule, "be") \
X(Ciult, "b") \
X(Cisle, "le") \
X(Cislt, "l") \
X(Cisgt, "g") \
X(Cisge, "ge") \
X(Ciugt, "a") \
X(Ciuge, "ae") \
X(Cieq, "z") \
X(Cine, "nz") \
X(NCmpI+Cfle, "be") \
X(NCmpI+Cflt, "b") \
X(NCmpI+Cfgt, "a") \
X(NCmpI+Cfge, "ae") \
X(NCmpI+Cfeq, "z") \
X(NCmpI+Cfne, "nz") \
X(NCmpI+Cfo, "np") \
X(NCmpI+Cfuo, "p")
X(Ciule, "be", "a") \
X(Ciult, "b", "ae") \
X(Cisle, "le", "g") \
X(Cislt, "l", "ge") \
X(Cisgt, "g", "le") \
X(Cisge, "ge", "l") \
X(Ciugt, "a", "be") \
X(Ciuge, "ae", "b") \
X(Cieq, "z", "nz") \
X(Cine, "nz", "z") \
X(NCmpI+Cfle, "be", "a") \
X(NCmpI+Cflt, "b", "ae") \
X(NCmpI+Cfgt, "a", "be") \
X(NCmpI+Cfge, "ae", "b") \
X(NCmpI+Cfo, "np", "p") \
X(NCmpI+Cfuo, "p", "np")
enum {
SLong = 0,
@ -60,67 +68,79 @@ enum {
static struct {
short op;
short cls;
char *asm;
char *fmt;
} omap[] = {
{ Oadd, Ka, "+add%k %1, %=" },
{ Osub, Ka, "-sub%k %1, %=" },
{ Oand, Ki, "+and%k %1, %=" },
{ Oor, Ki, "+or%k %1, %=" },
{ Oxor, Ki, "+xor%k %1, %=" },
{ Osar, Ki, "-sar%k %B1, %=" },
{ Oshr, Ki, "-shr%k %B1, %=" },
{ Oshl, Ki, "-shl%k %B1, %=" },
{ Omul, Ki, "+imul%k %1, %=" },
{ Omul, Ks, "+mulss %1, %=" },
{ Omul, Kd, "+mulsd %1, %=" },
{ Odiv, Ka, "-div%k %1, %=" },
{ Ostorel, Ka, "movq %L0, %M1" },
{ Ostorew, Ka, "movl %W0, %M1" },
{ Ostoreh, Ka, "movw %H0, %M1" },
{ Ostoreb, Ka, "movb %B0, %M1" },
{ Ostores, Ka, "movss %S0, %M1" },
{ Ostored, Ka, "movsd %D0, %M1" },
{ Oload, Ka, "mov%k %M0, %=" },
{ Oloadsw, Kl, "movslq %M0, %L=" },
{ Oloadsw, Kw, "movl %M0, %W=" },
{ Oloaduw, Ki, "movl %M0, %W=" },
{ Oloadsh, Ki, "movsw%k %M0, %=" },
{ Oloaduh, Ki, "movzw%k %M0, %=" },
{ Oloadsb, Ki, "movsb%k %M0, %=" },
{ Oloadub, Ki, "movzb%k %M0, %=" },
{ Oextsw, Kl, "movslq %W0, %L=" },
{ Oextuw, Kl, "movl %W0, %W=" },
{ Oextsh, Ki, "movsw%k %H0, %=" },
{ Oextuh, Ki, "movzw%k %H0, %=" },
{ Oextsb, Ki, "movsb%k %B0, %=" },
{ Oextub, Ki, "movzb%k %B0, %=" },
{ Oadd, Ka, "+add%k %1, %=" },
{ Osub, Ka, "-sub%k %1, %=" },
{ Oand, Ki, "+and%k %1, %=" },
{ Oor, Ki, "+or%k %1, %=" },
{ Oxor, Ki, "+xor%k %1, %=" },
{ Osar, Ki, "-sar%k %B1, %=" },
{ Oshr, Ki, "-shr%k %B1, %=" },
{ Oshl, Ki, "-shl%k %B1, %=" },
{ Omul, Ki, "+imul%k %1, %=" },
{ Omul, Ks, "+mulss %1, %=" },
{ Omul, Kd, "+mulsd %1, %=" },
{ Odiv, Ka, "-div%k %1, %=" },
{ Ostorel, Ka, "movq %L0, %M1" },
{ Ostorew, Ka, "movl %W0, %M1" },
{ Ostoreh, Ka, "movw %H0, %M1" },
{ Ostoreb, Ka, "movb %B0, %M1" },
{ Ostores, Ka, "movss %S0, %M1" },
{ Ostored, Ka, "movsd %D0, %M1" },
{ Oload, Ka, "mov%k %M0, %=" },
{ Oloadsw, Kl, "movslq %M0, %L=" },
{ Oloadsw, Kw, "movl %M0, %W=" },
{ Oloaduw, Ki, "movl %M0, %W=" },
{ Oloadsh, Ki, "movsw%k %M0, %=" },
{ Oloaduh, Ki, "movzw%k %M0, %=" },
{ Oloadsb, Ki, "movsb%k %M0, %=" },
{ Oloadub, Ki, "movzb%k %M0, %=" },
{ Oextsw, Kl, "movslq %W0, %L=" },
{ Oextuw, Kl, "movl %W0, %W=" },
{ Oextsh, Ki, "movsw%k %H0, %=" },
{ Oextuh, Ki, "movzw%k %H0, %=" },
{ Oextsb, Ki, "movsb%k %B0, %=" },
{ Oextub, Ki, "movzb%k %B0, %=" },
{ Oexts, Kd, "cvtss2sd %0, %=" },
{ Otruncd, Ks, "cvtsd2ss %0, %=" },
{ Ostosi, Ki, "cvttss2si%k %0, %=" },
{ Odtosi, Ki, "cvttsd2si%k %0, %=" },
{ Oswtof, Ka, "cvtsi2%k %W0, %=" },
{ Osltof, Ka, "cvtsi2%k %L0, %=" },
{ Ocast, Ki, "movq %D0, %L=" },
{ Ocast, Ka, "movq %L0, %D=" },
{ Oexts, Kd, "cvtss2sd %0, %=" },
{ Otruncd, Ks, "cvtsd2ss %0, %=" },
{ Ostosi, Ki, "cvttss2si%k %0, %=" },
{ Odtosi, Ki, "cvttsd2si%k %0, %=" },
{ Oswtof, Ka, "cvtsi2%k %W0, %=" },
{ Osltof, Ka, "cvtsi2%k %L0, %=" },
{ Ocast, Ki, "movq %D0, %L=" },
{ Ocast, Ka, "movq %L0, %D=" },
{ Oaddr, Ki, "lea%k %M0, %=" },
{ Oswap, Ki, "xchg%k %0, %1" },
{ Osign, Kl, "cqto" },
{ Osign, Kw, "cltd" },
{ Oxdiv, Ki, "div%k %0" },
{ Oxidiv, Ki, "idiv%k %0" },
{ Oxcmp, Ks, "ucomiss %S0, %S1" },
{ Oxcmp, Kd, "ucomisd %D0, %D1" },
{ Oxcmp, Ki, "cmp%k %0, %1" },
{ Oxtest, Ki, "test%k %0, %1" },
#define X(c, s) \
{ Oflag+c, Ki, "set" s " %B=\n\tmovzb%k %B=, %=" },
{ Oaddr, Ki, "lea%k %M0, %=" },
{ Oswap, Ki, "xchg%k %0, %1" },
{ Osign, Kl, "cqto" },
{ Osign, Kw, "cltd" },
{ Oxdiv, Ki, "div%k %0" },
{ Oxidiv, Ki, "idiv%k %0" },
{ Oxcmp, Ks, "ucomiss %S0, %S1" },
{ Oxcmp, Kd, "ucomisd %D0, %D1" },
{ Oxcmp, Ki, "cmp%k %0, %1" },
{ Oxtest, Ki, "test%k %0, %1" },
#define X(c, s, _) \
{ Oflag+c, Ki, "set" s " %B=\n\tmovzb%k %B=, %=" },
CMP(X)
#undef X
{ Oflagfeq, Ki, "setz %B=\n\tmovzb%k %B=, %=" },
{ Oflagfne, Ki, "setnz %B=\n\tmovzb%k %B=, %=" },
{ NOp, 0, 0 }
};
static char cmov[][2][16] = {
#define X(c, s0, s1) \
[c] = { \
"cmov" s0 " %0, %=", \
"cmov" s1 " %1, %=", \
},
CMP(X)
#undef X
};
static char *rname[][4] = {
[RAX] = {"rax", "eax", "ax", "al"},
[RBX] = {"rbx", "ebx", "bx", "bl"},
@ -142,23 +162,32 @@ static char *rname[][4] = {
static int
slot(Ref r, Fn *fn)
slot(Ref r, E *e)
{
int s;
s = rsval(r);
assert(s <= fn->slot);
assert(s <= e->fn->slot);
/* specific to NAlign == 3 */
if (s < 0)
return -4 * s;
else if (fn->vararg)
return -176 + -4 * (fn->slot - s);
else
return -4 * (fn->slot - s);
if (s < 0) {
if (e->fp == RSP)
return 4*-s - 8 + e->fsz + e->nclob*8;
else
return 4*-s;
}
else if (e->fp == RSP)
return 4*s + e->nclob*8;
else if (e->fn->vararg) {
if (T.windows)
return -4 * (e->fn->slot - s);
else
return -176 + -4 * (e->fn->slot - s);
} else
return -4 * (e->fn->slot - s);
}
static void
emitcon(Con *con, FILE *f)
emitcon(Con *con, E *e)
{
char *p, *l;
@ -168,16 +197,16 @@ emitcon(Con *con, FILE *f)
p = l[0] == '"' ? "" : T.assym;
if (con->sym.type == SThr) {
if (T.apple)
fprintf(f, "%s%s@TLVP", p, l);
fprintf(e->f, "%s%s@TLVP", p, l);
else
fprintf(f, "%%fs:%s%s@tpoff", p, l);
fprintf(e->f, "%%fs:%s%s@tpoff", p, l);
} else
fprintf(f, "%s%s", p, l);
fprintf(e->f, "%s%s", p, l);
if (con->bits.i)
fprintf(f, "%+"PRId64, con->bits.i);
fprintf(e->f, "%+"PRId64, con->bits.i);
break;
case CBits:
fprintf(f, "%"PRId64, con->bits.i);
fprintf(e->f, "%"PRId64, con->bits.i);
break;
default:
die("unreachable");
@ -212,10 +241,10 @@ getarg(char c, Ins *i)
}
}
static void emitins(Ins, Fn *, FILE *);
static void emitins(Ins, E *);
static void
emitcopy(Ref r1, Ref r2, int k, Fn *fn, FILE *f)
emitcopy(Ref r1, Ref r2, int k, E *e)
{
Ins icp;
@ -223,11 +252,11 @@ emitcopy(Ref r1, Ref r2, int k, Fn *fn, FILE *f)
icp.arg[0] = r2;
icp.to = r1;
icp.cls = k;
emitins(icp, fn, f);
emitins(icp, e);
}
static void
emitf(char *s, Ins *i, Fn *fn, FILE *f)
emitf(char *s, Ins *i, E *e)
{
static char clstoa[][3] = {"l", "q", "ss", "sd"};
char c;
@ -247,25 +276,25 @@ emitf(char *s, Ins *i, Fn *fn, FILE *f)
case '-':
assert((!req(i->arg[1], i->to) || req(i->arg[0], i->to)) &&
"cannot convert to 2-address");
emitcopy(i->to, i->arg[0], i->cls, fn, f);
emitcopy(i->to, i->arg[0], i->cls, e);
s++;
break;
}
fputc('\t', f);
fputc('\t', e->f);
Next:
while ((c = *s++) != '%')
if (!c) {
fputc('\n', f);
fputc('\n', e->f);
return;
} else
fputc(c, f);
fputc(c, e->f);
switch ((c = *s++)) {
case '%':
fputc('%', f);
fputc('%', e->f);
break;
case 'k':
fputs(clstoa[i->cls], f);
fputs(clstoa[i->cls], e->f);
break;
case '0':
case '1':
@ -282,37 +311,42 @@ Next:
switch (rtype(ref)) {
case RTmp:
assert(isreg(ref));
fprintf(f, "%%%s", regtoa(ref.val, sz));
fprintf(e->f, "%%%s", regtoa(ref.val, sz));
break;
case RSlot:
fprintf(f, "%d(%%rbp)", slot(ref, fn));
fprintf(e->f, "%d(%%%s)",
slot(ref, e),
regtoa(e->fp, SLong)
);
break;
case RMem:
Mem:
m = &fn->mem[ref.val];
m = &e->fn->mem[ref.val];
if (rtype(m->base) == RSlot) {
off.type = CBits;
off.bits.i = slot(m->base, fn);
addcon(&m->offset, &off);
m->base = TMP(RBP);
off.bits.i = slot(m->base, e);
addcon(&m->offset, &off, 1);
m->base = TMP(e->fp);
}
if (m->offset.type != CUndef)
emitcon(&m->offset, f);
fputc('(', f);
emitcon(&m->offset, e);
fputc('(', e->f);
if (!req(m->base, R))
fprintf(f, "%%%s", regtoa(m->base.val, SLong));
fprintf(e->f, "%%%s",
regtoa(m->base.val, SLong)
);
else if (m->offset.type == CAddr)
fprintf(f, "%%rip");
fprintf(e->f, "%%rip");
if (!req(m->index, R))
fprintf(f, ", %%%s, %d",
fprintf(e->f, ", %%%s, %d",
regtoa(m->index.val, SLong),
m->scale
);
fputc(')', f);
fputc(')', e->f);
break;
case RCon:
fputc('$', f);
emitcon(&fn->con[ref.val], f);
fputc('$', e->f);
emitcon(&e->fn->con[ref.val], e);
break;
default:
die("unreachable");
@ -337,18 +371,21 @@ Next:
case RMem:
goto Mem;
case RSlot:
fprintf(f, "%d(%%rbp)", slot(ref, fn));
fprintf(e->f, "%d(%%%s)",
slot(ref, e),
regtoa(e->fp, SLong)
);
break;
case RCon:
off = fn->con[ref.val];
emitcon(&off, f);
off = e->fn->con[ref.val];
emitcon(&off, e);
if (off.type == CAddr)
if (off.sym.type != SThr || T.apple)
fprintf(f, "(%%rip)");
fprintf(e->f, "(%%rip)");
break;
case RTmp:
assert(isreg(ref));
fprintf(f, "(%%%s)", regtoa(ref.val, SLong));
fprintf(e->f, "(%%%s)", regtoa(ref.val, SLong));
break;
default:
die("unreachable");
@ -360,13 +397,13 @@ Next:
goto Next;
}
static void *negmask[4] = {
[Ks] = (uint32_t[4]){ 0x80000000 },
[Kd] = (uint64_t[2]){ 0x8000000000000000 },
static bits negmask[4] = {
[Ks] = 0x80000000,
[Kd] = 0x8000000000000000,
};
static void
emitins(Ins i, Fn *fn, FILE *f)
emitins(Ins i, E *e)
{
Ref r;
int64_t val;
@ -377,6 +414,8 @@ emitins(Ins i, Fn *fn, FILE *f)
switch (i.op) {
default:
if (isxsel(i.op))
goto case_Oxsel;
Table:
/* most instructions are just pulled out of
* the table omap[], some special cases are
@ -393,7 +432,7 @@ emitins(Ins i, Fn *fn, FILE *f)
|| (omap[o].cls == Ka))
break;
}
emitf(omap[o].asm, &i, fn, f);
emitf(omap[o].fmt, &i, e);
break;
case Onop:
/* just do nothing for nops, they are inserted
@ -410,7 +449,7 @@ emitins(Ins i, Fn *fn, FILE *f)
if (KBASE(i.cls) == 0 /* only available for ints */
&& rtype(i.arg[0]) == RCon
&& rtype(i.arg[1]) == RTmp) {
emitf("imul%k %0, %1, %=", &i, fn, f);
emitf("imul%k %0, %1, %=", &i, e);
break;
}
goto Table;
@ -419,18 +458,18 @@ emitins(Ins i, Fn *fn, FILE *f)
* some 3-address subtractions */
if (req(i.to, i.arg[1]) && !req(i.arg[0], i.to)) {
ineg = (Ins){Oneg, i.cls, i.to, {i.to}};
emitins(ineg, fn, f);
emitf("add%k %0, %=", &i, fn, f);
emitins(ineg, e);
emitf("add%k %0, %=", &i, e);
break;
}
goto Table;
case Oneg:
if (!req(i.to, i.arg[0]))
emitf("mov%k %0, %=", &i, fn, f);
emitf("mov%k %0, %=", &i, e);
if (KBASE(i.cls) == 0)
emitf("neg%k %=", &i, fn, f);
emitf("neg%k %=", &i, e);
else
fprintf(f,
fprintf(e->f,
"\txorp%c %sfp%d(%%rip), %%%s\n",
"xxsd"[i.cls],
T.asloc,
@ -443,8 +482,8 @@ emitins(Ins i, Fn *fn, FILE *f)
* conversion to 2-address in emitf() would fail */
if (req(i.to, i.arg[1])) {
i.arg[1] = TMP(XMM0+15);
emitf("mov%k %=, %1", &i, fn, f);
emitf("mov%k %0, %=", &i, fn, f);
emitf("mov%k %=, %1", &i, e);
emitf("mov%k %0, %=", &i, e);
i.arg[0] = i.to;
}
goto Table;
@ -460,53 +499,54 @@ emitins(Ins i, Fn *fn, FILE *f)
t0 = rtype(i.arg[0]);
if (i.cls == Kl
&& t0 == RCon
&& fn->con[i.arg[0].val].type == CBits) {
val = fn->con[i.arg[0].val].bits.i;
&& e->fn->con[i.arg[0].val].type == CBits) {
val = e->fn->con[i.arg[0].val].bits.i;
if (isreg(i.to))
if (val >= 0 && val <= UINT32_MAX) {
emitf("movl %W0, %W=", &i, fn, f);
emitf("movl %W0, %W=", &i, e);
break;
}
if (rtype(i.to) == RSlot)
if (val < INT32_MIN || val > INT32_MAX) {
emitf("movl %0, %=", &i, fn, f);
emitf("movl %0>>32, 4+%=", &i, fn, f);
emitf("movl %0, %=", &i, e);
emitf("movl %0>>32, 4+%=", &i, e);
break;
}
}
if (isreg(i.to)
&& t0 == RCon
&& fn->con[i.arg[0].val].type == CAddr) {
emitf("lea%k %M0, %=", &i, fn, f);
&& e->fn->con[i.arg[0].val].type == CAddr) {
emitf("lea%k %M0, %=", &i, e);
break;
}
if (rtype(i.to) == RSlot
&& (t0 == RSlot || t0 == RMem)) {
i.cls = KWIDE(i.cls) ? Kd : Ks;
i.arg[1] = TMP(XMM0+15);
emitf("mov%k %0, %1", &i, fn, f);
emitf("mov%k %1, %=", &i, fn, f);
emitf("mov%k %0, %1", &i, e);
emitf("mov%k %1, %=", &i, e);
break;
}
/* conveniently, the assembler knows if it
* should use movabsq when reading movq */
emitf("mov%k %0, %=", &i, fn, f);
emitf("mov%k %0, %=", &i, e);
break;
case Oaddr:
if (!T.apple
&& rtype(i.arg[0]) == RCon
&& fn->con[i.arg[0].val].sym.type == SThr) {
&& e->fn->con[i.arg[0].val].sym.type == SThr) {
/* derive the symbol address from the TCB
* address at offset 0 of %fs */
assert(isreg(i.to));
con = &fn->con[i.arg[0].val];
con = &e->fn->con[i.arg[0].val];
sym = str(con->sym.id);
emitf("movq %%fs:0, %L=", &i, fn, f);
fprintf(f, "\tleaq %s%s@tpoff",
emitf("movq %%fs:0, %L=", &i, e);
fprintf(e->f, "\tleaq %s%s@tpoff",
sym[0] == '"' ? "" : T.assym, sym);
if (con->bits.i)
fprintf(f, "%+"PRId64, con->bits.i);
fprintf(f, "(%%%s), %%%s\n",
fprintf(e->f, "%+"PRId64,
con->bits.i);
fprintf(e->f, "(%%%s), %%%s\n",
regtoa(i.to.val, SLong),
regtoa(i.to.val, SLong));
break;
@ -517,12 +557,12 @@ emitins(Ins i, Fn *fn, FILE *f)
* assembly... */
switch (rtype(i.arg[0])) {
case RCon:
fprintf(f, "\tcallq ");
emitcon(&fn->con[i.arg[0].val], f);
fprintf(f, "\n");
fprintf(e->f, "\tcallq ");
emitcon(&e->fn->con[i.arg[0].val], e);
fprintf(e->f, "\n");
break;
case RTmp:
emitf("callq *%L0", &i, fn, f);
emitf("callq *%L0", &i, e);
break;
default:
die("invalid call argument");
@ -533,9 +573,10 @@ emitins(Ins i, Fn *fn, FILE *f)
* maybe we should split Osalloc in 2 different
* instructions depending on the result
*/
emitf("subq %L0, %%rsp", &i, fn, f);
assert(e->fp == RBP);
emitf("subq %L0, %%rsp", &i, e);
if (!req(i.to, R))
emitcopy(i.to, TMP(RSP), Kl, fn, f);
emitcopy(i.to, TMP(RSP), Kl, e);
break;
case Oswap:
if (KBASE(i.cls) == 0)
@ -543,34 +584,51 @@ emitins(Ins i, Fn *fn, FILE *f)
/* for floats, there is no swap instruction
* so we use xmm15 as a temporary
*/
emitcopy(TMP(XMM0+15), i.arg[0], i.cls, fn, f);
emitcopy(i.arg[0], i.arg[1], i.cls, fn, f);
emitcopy(i.arg[1], TMP(XMM0+15), i.cls, fn, f);
emitcopy(TMP(XMM0+15), i.arg[0], i.cls, e);
emitcopy(i.arg[0], i.arg[1], i.cls, e);
emitcopy(i.arg[1], TMP(XMM0+15), i.cls, e);
break;
case Odbgloc:
emitdbgloc(i.arg[0].val, i.arg[1].val, f);
emitdbgloc(i.arg[0].val, i.arg[1].val, e->f);
break;
case_Oxsel:
if (req(i.to, i.arg[1]))
emitf(cmov[i.op-Oxsel][0], &i, e);
else {
if (!req(i.to, i.arg[0]))
emitf("mov %0, %=", &i, e);
emitf(cmov[i.op-Oxsel][1], &i, e);
}
break;
}
}
static uint64_t
framesz(Fn *fn)
static void
sysv_framesz(E *e)
{
uint64_t i, o, f;
/* specific to NAlign == 3 */
for (i=0, o=0; i<NCLR; i++)
o ^= 1 & (fn->reg >> amd64_sysv_rclob[i]);
f = fn->slot;
o = 0;
if (!e->fn->leaf) {
for (i=0, o=0; i<NCLR_SYSV; i++)
o ^= e->fn->reg >> amd64_sysv_rclob[i];
o &= 1;
}
f = e->fn->slot;
f = (f + 3) & -4;
return 4*f + 8*o + 176*fn->vararg;
if (f > 0
&& e->fp == RSP
&& e->fn->salign == 4)
f += 2;
e->fsz = 4*f + 8*o + 176*e->fn->vararg;
}
void
amd64_emitfn(Fn *fn, FILE *f)
amd64_sysv_emitfn(Fn *fn, FILE *f)
{
static char *ctoa[] = {
#define X(c, s) [c] = s,
#define X(c, s, _) [c] = s,
CMP(X)
#undef X
};
@ -578,13 +636,20 @@ amd64_emitfn(Fn *fn, FILE *f)
Blk *b, *s;
Ins *i, itmp;
int *r, c, o, n, lbl;
uint64_t fs;
uint p;
E *e;
e = &(E){.f = f, .fn = fn};
emitfnlnk(fn->name, &fn->lnk, f);
fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f);
fs = framesz(fn);
if (fs)
fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", fs);
fputs("\tendbr64\n", f);
if (!fn->leaf || fn->vararg || fn->dynalloc) {
e->fp = RBP;
fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f);
} else
e->fp = RSP;
sysv_framesz(e);
if (e->fsz)
fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", e->fsz);
if (fn->vararg) {
o = -176;
for (r=amd64_sysv_rsave; r<&amd64_sysv_rsave[6]; r++, o+=8)
@ -592,18 +657,24 @@ amd64_emitfn(Fn *fn, FILE *f)
for (n=0; n<8; ++n, o+=16)
fprintf(f, "\tmovaps %%xmm%d, %d(%%rbp)\n", n, o);
}
for (r=amd64_sysv_rclob; r<&amd64_sysv_rclob[NCLR]; r++)
for (r=amd64_sysv_rclob; r<&amd64_sysv_rclob[NCLR_SYSV]; r++)
if (fn->reg & BIT(*r)) {
itmp.arg[0] = TMP(*r);
emitf("pushq %L0", &itmp, fn, f);
fs += 8;
emitf("pushq %L0", &itmp, e);
e->nclob++;
}
for (lbl=0, b=fn->start; b; b=b->link) {
if (lbl || b->npred > 1)
if (lbl || b->npred > 1) {
for (p=0; p<b->npred; p++)
if (b->pred[p]->id >= b->id)
break;
if (p != b->npred)
fprintf(f, ".p2align 4\n");
fprintf(f, "%sbb%d:\n", T.asloc, id0+b->id);
}
for (i=b->ins; i!=&b->ins[b->nins]; i++)
emitins(*i, fn, f);
emitins(*i, e);
lbl = 1;
switch (b->jmp.type) {
case Jhlt:
@ -614,17 +685,19 @@ amd64_emitfn(Fn *fn, FILE *f)
fprintf(f,
"\tmovq %%rbp, %%rsp\n"
"\tsubq $%"PRIu64", %%rsp\n",
fs
);
for (r=&amd64_sysv_rclob[NCLR]; r>amd64_sysv_rclob;)
e->fsz + e->nclob * 8);
for (r=&amd64_sysv_rclob[NCLR_SYSV]; r>amd64_sysv_rclob;)
if (fn->reg & BIT(*--r)) {
itmp.arg[0] = TMP(*r);
emitf("popq %L0", &itmp, fn, f);
emitf("popq %L0", &itmp, e);
}
fprintf(f,
"\tleave\n"
"\tret\n"
);
if (e->fp == RBP)
fputs("\tleave\n", f);
else if (e->fsz)
fprintf(f,
"\taddq $%"PRIu64", %%rsp\n",
e->fsz);
fputs("\tret\n", f);
break;
case Jjmp:
Jmp:
@ -654,3 +727,118 @@ amd64_emitfn(Fn *fn, FILE *f)
if (!T.apple)
elf_emitfnfin(fn->name, f);
}
static void
winabi_framesz(E *e)
{
uint64_t i, o, f;
/* specific to NAlign == 3 */
o = 0;
if (!e->fn->leaf) {
for (i=0, o=0; i<NCLR_WIN; i++)
o ^= e->fn->reg >> amd64_winabi_rclob[i];
o &= 1;
}
f = e->fn->slot;
f = (f + 3) & -4;
if (f > 0
&& e->fp == RSP
&& e->fn->salign == 4)
f += 2;
e->fsz = 4*f + 8*o;
}
void
amd64_winabi_emitfn(Fn *fn, FILE *f)
{
static char *ctoa[] = {
#define X(c, s, _) [c] = s,
CMP(X)
#undef X
};
static int id0;
Blk *b, *s;
Ins *i, itmp;
int *r, c, lbl;
E *e;
e = &(E){.f = f, .fn = fn};
emitfnlnk(fn->name, &fn->lnk, f);
fputs("\tendbr64\n", f);
if (fn->vararg) {
fprintf(f, "\tmovq %%rcx, 0x8(%%rsp)\n");
fprintf(f, "\tmovq %%rdx, 0x10(%%rsp)\n");
fprintf(f, "\tmovq %%r8, 0x18(%%rsp)\n");
fprintf(f, "\tmovq %%r9, 0x20(%%rsp)\n");
}
if (!fn->leaf || fn->vararg || fn->dynalloc) {
e->fp = RBP;
fputs("\tpushq %rbp\n\tmovq %rsp, %rbp\n", f);
} else
e->fp = RSP;
winabi_framesz(e);
if (e->fsz)
fprintf(f, "\tsubq $%"PRIu64", %%rsp\n", e->fsz);
for (r=amd64_winabi_rclob; r<&amd64_winabi_rclob[NCLR_WIN]; r++)
if (fn->reg & BIT(*r)) {
itmp.arg[0] = TMP(*r);
emitf("pushq %L0", &itmp, e);
e->nclob++;
}
for (lbl=0, b=fn->start; b; b=b->link) {
if (lbl || b->npred > 1)
fprintf(f, "%sbb%d:\n", T.asloc, id0+b->id);
for (i=b->ins; i!=&b->ins[b->nins]; i++)
emitins(*i, e);
lbl = 1;
switch (b->jmp.type) {
case Jhlt:
fprintf(f, "\tud2\n");
break;
case Jret0:
if (fn->dynalloc)
fprintf(f,
"\tmovq %%rbp, %%rsp\n"
"\tsubq $%"PRIu64", %%rsp\n",
e->fsz + e->nclob * 8);
for (r=&amd64_winabi_rclob[NCLR_WIN]; r>amd64_winabi_rclob;)
if (fn->reg & BIT(*--r)) {
itmp.arg[0] = TMP(*r);
emitf("popq %L0", &itmp, e);
}
if (e->fp == RBP)
fputs("\tleave\n", f);
else if (e->fsz)
fprintf(f,
"\taddq $%"PRIu64", %%rsp\n",
e->fsz);
fputs("\tret\n", f);
break;
case Jjmp:
Jmp:
if (b->s1 != b->link)
fprintf(f, "\tjmp %sbb%d\n",
T.asloc, id0+b->s1->id);
else
lbl = 0;
break;
default:
c = b->jmp.type - Jjf;
if (0 <= c && c <= NCmp) {
if (b->link == b->s2) {
s = b->s1;
b->s1 = b->s2;
b->s2 = s;
} else
c = cmpneg(c);
fprintf(f, "\tj%s %sbb%d\n", ctoa[c],
T.asloc, id0+b->s2->id);
goto Jmp;
}
die("unhandled jump %d", b->jmp.type);
}
}
id0 += fn->nblk;
}

View File

@ -18,14 +18,7 @@
* dce should be moved out...
*/
typedef struct ANum ANum;
struct ANum {
char n, l, r;
Ins *i;
};
static int amatch(Addr *, Ref, int, ANum *, Fn *);
static int amatch(Addr *, Num *, Ref, Fn *);
static int
noimm(Ref r, Fn *fn)
@ -94,11 +87,23 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
vgrow(&fn->mem, ++fn->nmem);
memset(&a, 0, sizeof a);
a.offset.type = CAddr;
n = stashbits(&fn->con[r0.val].bits, KWIDE(k) ? 8 : 4);
n = stashbits(fn->con[r0.val].bits.i, KWIDE(k) ? 8 : 4);
/* quote the name so that we do not
* add symbol prefixes on the apple
* target variant
*/
sprintf(buf, "\"%sfp%d\"", T.asloc, n);
a.offset.sym.id = intern(buf);
fn->mem[fn->nmem-1] = a;
}
else if (op == Ocall && r == &i->arg[0]
&& rtype(r0) == RCon && fn->con[r0.val].type != CAddr) {
/* use a temporary register so that we
* produce an indirect call
*/
r1 = newtmp("isel", Kl, fn);
emit(Ocopy, Kl, r1, r0, R);
}
else if (op != Ocopy && k == Kl && noimm(r0, fn)) {
/* load constants that do not fit in
* a 32bit signed integer into a
@ -166,11 +171,15 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
m->base = r0;
}
}
else if (isxsel(op) && rtype(*r) == RCon) {
r1 = newtmp("isel", i->cls, fn);
emit(Ocopy, i->cls, r1, *r, R);
}
*r = r1;
}
static void
seladdr(Ref *r, ANum *an, Fn *fn)
seladdr(Ref *r, Num *tn, Fn *fn)
{
Addr a;
Ref r0;
@ -178,7 +187,7 @@ seladdr(Ref *r, ANum *an, Fn *fn)
r0 = *r;
if (rtype(r0) == RTmp) {
memset(&a, 0, sizeof a);
if (!amatch(&a, r0, an[r0.val].n, an, fn))
if (!amatch(&a, tn, r0, fn))
return;
if (!req(a.base, R))
if (a.offset.type == CAddr) {
@ -242,7 +251,7 @@ selcmp(Ref arg[2], int k, int swap, Fn *fn)
}
static void
sel(Ins i, ANum *an, Fn *fn)
sel(Ins i, Num *tn, Fn *fn)
{
Ref r0, r1, tmp[7];
int x, j, k, kc, sh, swap;
@ -404,10 +413,10 @@ sel(Ins i, ANum *an, Fn *fn)
if (i.op == Ostores)
i.op = Ostorew;
}
seladdr(&i.arg[1], an, fn);
seladdr(&i.arg[1], tn, fn);
goto Emit;
case_Oload:
seladdr(&i.arg[0], an, fn);
seladdr(&i.arg[0], tn, fn);
goto Emit;
case Odbgloc:
case Ocall:
@ -428,7 +437,8 @@ sel(Ins i, ANum *an, Fn *fn)
case Oexts:
case Otruncd:
case Ocast:
case_OExt:
case_Oxsel:
case_Oext:
Emit:
emiti(i);
i1 = curi; /* fixarg() can change curi */
@ -442,7 +452,9 @@ Emit:
break;
default:
if (isext(i.op))
goto case_OExt;
goto case_Oext;
if (isxsel(i.op))
goto case_Oxsel;
if (isload(i.op))
goto case_Oload;
if (iscmp(i.op, &kc, &x)) {
@ -496,6 +508,88 @@ flagi(Ins *i0, Ins *i)
return 0;
}
static Ins*
selsel(Fn *fn, Blk *b, Ins *i, Num *tn)
{
Ref r, cr[2];
int c, k, swap, gencmp, gencpy;
Ins *isel0, *isel1, *fi;
Tmp *t;
assert(i->op == Osel1);
for (isel0=i; b->ins<isel0; isel0--) {
if (isel0->op == Osel0)
break;
assert(isel0->op == Osel1);
}
assert(isel0->op == Osel0);
r = isel0->arg[0];
assert(rtype(r) == RTmp);
t = &fn->tmp[r.val];
fi = flagi(b->ins, isel0);
cr[0] = cr[1] = R;
gencmp = gencpy = swap = 0;
k = Kw;
c = Cine;
if (!fi || !req(fi->to, r)) {
gencmp = 1;
cr[0] = r;
cr[1] = CON_Z;
}
else if (iscmp(fi->op, &k, &c)) {
if (c == NCmpI+Cfeq
|| c == NCmpI+Cfne) {
/* these are selected as 'and'
* or 'or', so we check their
* result with Cine
*/
c = Cine;
goto Other;
}
swap = cmpswap(fi->arg, c);
if (swap)
c = cmpop(c);
if (t->nuse == 1) {
gencmp = 1;
cr[0] = fi->arg[0];
cr[1] = fi->arg[1];
*fi = (Ins){.op = Onop};
}
}
else if (fi->op == Oand && t->nuse == 1
&& (rtype(fi->arg[0]) == RTmp ||
rtype(fi->arg[1]) == RTmp)) {
fi->op = Oxtest;
fi->to = R;
if (rtype(fi->arg[1]) == RCon) {
r = fi->arg[1];
fi->arg[1] = fi->arg[0];
fi->arg[0] = r;
}
}
else {
Other:
/* since flags are not tracked in liveness,
* the result of the flag-setting instruction
* has to be marked as live
*/
if (t->nuse == 1)
gencpy = 1;
}
/* generate conditional moves */
for (isel1=i; isel0<isel1; --isel1) {
isel1->op = Oxsel+c;
sel(*isel1, tn, fn);
}
assert(!gencmp || !gencpy);
if (gencmp)
selcmp(cr, k, swap, fn);
if (gencpy)
emit(Ocopy, Kw, R, r, R);
*isel0 = (Ins){.op = Onop};
return isel0;
}
static void
seljmp(Blk *b, Fn *fn)
{
@ -525,7 +619,7 @@ seljmp(Blk *b, Fn *fn)
b->jmp.type = Jjf + Cine;
}
else if (iscmp(fi->op, &k, &c)
&& c != NCmpI+Cfeq /* see sel() */
&& c != NCmpI+Cfeq /* see sel(), selsel() */
&& c != NCmpI+Cfne) {
swap = cmpswap(fi->arg, c);
if (swap)
@ -559,155 +653,225 @@ seljmp(Blk *b, Fn *fn)
}
}
enum {
Pob,
Pbis,
Pois,
Pobis,
Pbi1,
Pobi1,
};
/* mgen generated code
*
* (with-vars (o b i s)
* (patterns
* (ob (add (con o) (tmp b)))
* (bis (add (tmp b) (mul (tmp i) (con s 1 2 4 8))))
* (ois (add (con o) (mul (tmp i) (con s 1 2 4 8))))
* (obis (add (con o) (tmp b) (mul (tmp i) (con s 1 2 4 8))))
* (bi1 (add (tmp b) (tmp i)))
* (obi1 (add (con o) (tmp b) (tmp i)))
* ))
*/
static int
aref(Ref r, ANum *ai)
opn(int op, int l, int r)
{
switch (rtype(r)) {
case RCon:
static uchar Oaddtbl[91] = {
2,
2,2,
4,4,5,
6,6,8,8,
4,4,9,10,9,
7,7,5,8,9,5,
4,4,12,10,12,12,12,
4,4,9,10,9,9,12,9,
11,11,5,8,9,5,12,9,5,
7,7,5,8,9,5,12,9,5,5,
11,11,5,8,9,5,12,9,5,5,5,
4,4,9,10,9,9,12,9,9,9,9,9,
7,7,5,8,9,5,12,9,5,5,5,9,5,
};
int t;
if (l < r)
t = l, l = r, r = t;
switch (op) {
case Omul:
if (2 <= l)
if (r == 0) {
return 3;
}
return 2;
case RTmp:
return ai[r.val].n;
case Oadd:
return Oaddtbl[(l + l*l)/2 + r];
default:
die("constant or temporary expected");
return 2;
}
}
static int
ascale(Ref r, Con *con)
refn(Ref r, Num *tn, Con *con)
{
int64_t n;
if (rtype(r) != RCon)
return 0;
if (con[r.val].type != CBits)
return 0;
n = con[r.val].bits.i;
return n == 1 || n == 2 || n == 4 || n == 8;
switch (rtype(r)) {
case RTmp:
if (!tn[r.val].n)
tn[r.val].n = 2;
return tn[r.val].n;
case RCon:
if (con[r.val].type != CBits)
return 1;
n = con[r.val].bits.i;
if (n == 8 || n == 4 || n == 2 || n == 1)
return 0;
return 1;
default:
return INT_MIN;
}
}
static bits match[13] = {
[4] = BIT(Pob),
[5] = BIT(Pbi1),
[6] = BIT(Pob) | BIT(Pois),
[7] = BIT(Pob) | BIT(Pobi1),
[8] = BIT(Pbi1) | BIT(Pbis),
[9] = BIT(Pbi1) | BIT(Pobi1),
[10] = BIT(Pbi1) | BIT(Pbis) | BIT(Pobi1) | BIT(Pobis),
[11] = BIT(Pob) | BIT(Pobi1) | BIT(Pobis),
[12] = BIT(Pbi1) | BIT(Pobi1) | BIT(Pobis),
};
static uchar *matcher[] = {
[Pbi1] = (uchar[]){
1,3,1,3,2,0
},
[Pbis] = (uchar[]){
5,1,8,5,27,1,5,1,2,5,13,3,1,1,3,3,3,2,0,1,
3,3,3,2,3,1,0,1,29
},
[Pob] = (uchar[]){
1,3,0,3,1,0
},
[Pobi1] = (uchar[]){
5,3,9,9,10,33,12,35,45,1,5,3,11,9,7,9,4,9,
17,1,3,0,3,1,3,2,0,3,1,1,3,0,34,1,37,1,5,2,
5,7,2,7,8,37,29,1,3,0,1,32
},
[Pobis] = (uchar[]){
5,2,10,7,11,19,49,1,1,3,3,3,2,1,3,0,3,1,0,
1,3,0,5,1,8,5,25,1,5,1,2,5,13,3,1,1,3,3,3,
2,0,1,3,3,3,2,26,1,51,1,5,1,6,5,9,1,3,0,51,
3,1,1,3,0,45
},
[Pois] = (uchar[]){
1,3,0,1,3,3,3,2,0
},
};
/* end of generated code */
static void
anumber(ANum *ai, Blk *b, Con *con)
anumber(Num *tn, Blk *b, Con *con)
{
/* This should be made obsolete by a proper
* reassoc pass.
*
* Rules:
*
* RTmp(_) -> 0 tmp
* ( RTmp(_) -> 1 slot )
* RCon(_) -> 2 con
* 0 * 2 -> 3 s * i (when constant is 1,2,4,8)
*/
static char add[10][10] = {
[2] [4] = 4, [4] [2] = 4,
[2] [6] = 6, [6] [2] = 6,
[2] [7] = 7, [7] [2] = 7,
[0] [2] = 4, [2] [0] = 4, /* 4: o + b */
[0] [0] = 5, /* 5: b + s * i */
[0] [3] = 5, [3] [0] = 5,
[2] [3] = 6, [3] [2] = 6, /* 6: o + s * i */
[2] [5] = 7, [5] [2] = 7, /* 7: o + b + s * i */
[0] [6] = 7, [6] [0] = 7,
[4] [3] = 7, [3] [4] = 7,
};
int a, a1, a2, n1, n2, t1, t2;
Ins *i;
Num *n;
for (i=b->ins; i<&b->ins[b->nins]; i++) {
if (rtype(i->to) == RTmp)
ai[i->to.val].i = i;
if (i->op != Oadd && i->op != Omul)
if (rtype(i->to) != RTmp)
continue;
a1 = aref(i->arg[0], ai);
a2 = aref(i->arg[1], ai);
t1 = a1 != 1 && a1 != 2;
t2 = a2 != 1 && a2 != 2;
if (i->op == Oadd) {
a = add[n1 = a1][n2 = a2];
if (t1 && a < add[0][a2])
a = add[n1 = 0][n2 = a2];
if (t2 && a < add[a1][0])
a = add[n1 = a1][n2 = 0];
if (t1 && t2 && a < add[0][0])
a = add[n1 = 0][n2 = 0];
} else {
n1 = n2 = a = 0;
if (ascale(i->arg[0], con) && t2)
a = 3, n1 = 2, n2 = 0;
if (t1 && ascale(i->arg[1], con))
a = 3, n1 = 0, n2 = 2;
}
ai[i->to.val].n = a;
ai[i->to.val].l = n1;
ai[i->to.val].r = n2;
n = &tn[i->to.val];
n->l = i->arg[0];
n->r = i->arg[1];
n->nl = refn(n->l, tn, con);
n->nr = refn(n->r, tn, con);
n->n = opn(i->op, n->nl, n->nr);
}
}
static Ref
adisp(Con *c, Num *tn, Ref r, Fn *fn, int s)
{
Ref v[2];
int n;
while (!req(r, R)) {
assert(rtype(r) == RTmp);
n = refn(r, tn, fn->con);
if (!(match[n] & BIT(Pob)))
break;
runmatch(matcher[Pob], tn, r, v);
assert(rtype(v[0]) == RCon);
addcon(c, &fn->con[v[0].val], s);
r = v[1];
}
return r;
}
static int
amatch(Addr *a, Ref r, int n, ANum *ai, Fn *fn)
amatch(Addr *a, Num *tn, Ref r, Fn *fn)
{
Ins *i;
int nl, nr, t, s;
Ref al, ar;
static int pat[] = {Pobis, Pobi1, Pbis, Pois, Pbi1, -1};
Ref ro, rb, ri, rs, v[4];
Con *c, co;
int s, n, *p;
if (rtype(r) == RCon) {
if (!addcon(&a->offset, &fn->con[r.val]))
err("unlikely sum of $%s and $%s",
str(a->offset.sym.id),
str(fn->con[r.val].sym.id));
return 1;
}
assert(rtype(r) == RTmp);
i = ai[r.val].i;
nl = ai[r.val].l;
nr = ai[r.val].r;
if (i) {
if (nl > nr) {
al = i->arg[1];
ar = i->arg[0];
t = nl, nl = nr, nr = t;
} else {
al = i->arg[0];
ar = i->arg[1];
}
}
switch (n) {
case 3: /* s * i */
a->index = al;
a->scale = fn->con[ar.val].bits.i;
if (rtype(r) != RTmp)
return 0;
case 5: /* b + s * i */
switch (nr) {
case 0:
if (fn->tmp[ar.val].slot != -1) {
al = i->arg[1];
ar = i->arg[0];
}
a->index = ar;
a->scale = 1;
break;
case 3:
amatch(a, ar, nr, ai, fn);
n = refn(r, tn, fn->con);
memset(v, 0, sizeof v);
for (p=pat; *p>=0; p++)
if (match[n] & BIT(*p)) {
runmatch(matcher[*p], tn, r, v);
break;
}
r = al;
/* fall through */
case 0:
s = fn->tmp[r.val].slot;
if (s != -1)
r = SLOT(s);
a->base = r;
return n || s != -1;
case 2: /* constants */
case 4: /* o + b */
case 6: /* o + s * i */
case 7: /* o + b + s * i */
amatch(a, ar, nr, ai, fn);
amatch(a, al, nl, ai, fn);
return 1;
default:
die("unreachable");
if (*p < 0)
v[1] = r;
memset(&co, 0, sizeof co);
ro = v[0];
rb = adisp(&co, tn, v[1], fn, 1);
ri = v[2];
rs = v[3];
s = 1;
if (*p < 0 && co.type != CUndef)
if (amatch(a, tn, rb, fn))
return addcon(&a->offset, &co, 1);
if (!req(ro, R)) {
assert(rtype(ro) == RCon);
c = &fn->con[ro.val];
if (!addcon(&co, c, 1))
return 0;
}
if (!req(rs, R)) {
assert(rtype(rs) == RCon);
c = &fn->con[rs.val];
assert(c->type == CBits);
s = c->bits.i;
}
ri = adisp(&co, tn, ri, fn, s);
*a = (Addr){co, rb, ri, s};
if (rtype(ri) == RTmp)
if (fn->tmp[ri.val].slot != -1) {
if (a->scale != 1
|| fn->tmp[rb.val].slot != -1)
return 0;
a->base = ri;
a->index = rb;
}
if (!req(a->base, R)) {
assert(rtype(a->base) == RTmp);
s = fn->tmp[a->base.val].slot;
if (s != -1)
a->base = SLOT(s);
}
return 1;
}
/* instruction selection
@ -722,7 +886,7 @@ amd64_isel(Fn *fn)
uint a;
int n, al;
int64_t sz;
ANum *ainfo;
Num *num;
/* assign slots to fast allocs */
b = fn->start;
@ -741,12 +905,13 @@ amd64_isel(Fn *fn)
die("alloc too large");
fn->tmp[i->to.val].slot = fn->slot;
fn->slot += sz;
fn->salign = 2 + al - Oalloc;
*i = (Ins){.op = Onop};
}
/* process basic blocks */
n = fn->ntmp;
ainfo = emalloc(n * sizeof ainfo[0]);
num = emalloc(n * sizeof num[0]);
for (b=fn->start; b; b=b->link) {
curi = &insb[NIns];
for (sb=(Blk*[3]){b->s1, b->s2, 0}; *sb; sb++)
@ -755,15 +920,20 @@ amd64_isel(Fn *fn)
assert(a+1 < p->narg);
fixarg(&p->arg[a], p->cls, 0, fn);
}
memset(ainfo, 0, n * sizeof ainfo[0]);
anumber(ainfo, b, fn->con);
memset(num, 0, n * sizeof num[0]);
anumber(num, b, fn->con);
seljmp(b, fn);
for (i=&b->ins[b->nins]; i!=b->ins;)
sel(*--i, ainfo, fn);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
for (i=&b->ins[b->nins]; i!=b->ins;) {
--i;
assert(i->op != Osel0);
if (i->op == Osel1)
i = selsel(fn, b, i, num);
else
sel(*i, num, fn);
}
idup(b, curi, &insb[NIns]-curi);
}
free(ainfo);
free(num);
if (debug['I']) {
fprintf(stderr, "\n> After instruction selection:\n");

View File

@ -228,8 +228,8 @@ int amd64_sysv_rsave[] = {
int amd64_sysv_rclob[] = {RBX, R12, R13, R14, R15, -1};
MAKESURE(sysv_arrays_ok,
sizeof amd64_sysv_rsave == (NGPS+NFPS+1) * sizeof(int) &&
sizeof amd64_sysv_rclob == (NCLR+1) * sizeof(int)
sizeof amd64_sysv_rsave == (NGPS_SYSV+NFPS+1) * sizeof(int) &&
sizeof amd64_sysv_rclob == (NCLR_SYSV+1) * sizeof(int)
);
/* layout of call's second argument (RCall)
@ -508,8 +508,7 @@ split(Fn *fn, Blk *b)
++fn->nblk;
bn = newblk();
bn->nins = &insb[NIns] - curi;
idup(&bn->ins, curi, bn->nins);
idup(bn, curi, &insb[NIns]-curi);
curi = &insb[NIns];
bn->visit = ++b->visit;
strf(bn->name, "%s.%d", b->name, b->visit);
@ -657,9 +656,9 @@ void
amd64_sysv_abi(Fn *fn)
{
Blk *b;
Ins *i, *i0, *ip;
Ins *i, *i0;
RAlloc *ral;
int n, fa;
int n0, n1, ioff, fa;
for (b=fn->start; b; b=b->link)
b->visit = 0;
@ -669,12 +668,13 @@ amd64_sysv_abi(Fn *fn)
if (!ispar(i->op))
break;
fa = selpar(fn, b->ins, i);
n = b->nins - (i - b->ins) + (&insb[NIns] - curi);
i0 = alloc(n * sizeof(Ins));
ip = icpy(ip = i0, curi, &insb[NIns] - curi);
ip = icpy(ip, i, &b->ins[b->nins] - i);
b->nins = n;
b->ins = i0;
n0 = &insb[NIns] - curi;
ioff = i - b->ins;
n1 = b->nins - ioff;
vgrow(&b->ins, n0+n1);
icpy(b->ins+n0, b->ins+ioff, n1);
icpy(b->ins, curi, n0);
b->nins = n0+n1;
/* lower calls, returns, and vararg instructions */
ral = 0;
@ -711,8 +711,7 @@ amd64_sysv_abi(Fn *fn)
if (b == fn->start)
for (; ral; ral=ral->link)
emiti(ral->i);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
} while (b != fn->start);
if (debug['A']) {

View File

@ -19,20 +19,21 @@ amd64_memargs(int op)
.nfpr = NFPR, \
.rglob = BIT(RBP) | BIT(RSP), \
.nrglob = 2, \
.rsave = amd64_sysv_rsave, \
.nrsave = {NGPS, NFPS}, \
.retregs = amd64_sysv_retregs, \
.argregs = amd64_sysv_argregs, \
.memargs = amd64_memargs, \
.abi0 = elimsb, \
.abi1 = amd64_sysv_abi, \
.isel = amd64_isel, \
.emitfn = amd64_emitfn, \
.cansel = 1,
Target T_amd64_sysv = {
.name = "amd64_sysv",
.emitfin = elf_emitfin,
.asloc = ".L",
.abi1 = amd64_sysv_abi,
.rsave = amd64_sysv_rsave,
.nrsave = {NGPS_SYSV, NFPS},
.retregs = amd64_sysv_retregs,
.argregs = amd64_sysv_argregs,
.emitfn = amd64_sysv_emitfn,
AMD64_COMMON
};
@ -42,5 +43,25 @@ Target T_amd64_apple = {
.emitfin = macho_emitfin,
.asloc = "L",
.assym = "_",
.abi1 = amd64_sysv_abi,
.rsave = amd64_sysv_rsave,
.nrsave = {NGPS_SYSV, NFPS},
.retregs = amd64_sysv_retregs,
.argregs = amd64_sysv_argregs,
.emitfn = amd64_sysv_emitfn,
AMD64_COMMON
};
Target T_amd64_win = {
.name = "amd64_win",
.windows = 1,
.emitfin = pe_emitfin,
.asloc = "L",
.abi1 = amd64_winabi_abi,
.rsave = amd64_winabi_rsave,
.nrsave = {NGPS_WIN, NFPS},
.retregs = amd64_winabi_retregs,
.argregs = amd64_winabi_argregs,
.emitfn = amd64_winabi_emitfn,
AMD64_COMMON
};

763
amd64/winabi.c Executable file
View File

@ -0,0 +1,763 @@
#include "all.h"
#include <stdbool.h>
typedef enum ArgPassStyle {
APS_Invalid = 0,
APS_Register,
APS_InlineOnStack,
APS_CopyAndPointerInRegister,
APS_CopyAndPointerOnStack,
APS_VarargsTag,
APS_EnvTag,
} ArgPassStyle;
typedef struct ArgClass {
Typ* type;
ArgPassStyle style;
int align;
uint size;
int cls;
Ref ref;
} ArgClass;
typedef struct ExtraAlloc ExtraAlloc;
struct ExtraAlloc {
Ins instr;
ExtraAlloc* link;
};
#define ALIGN_DOWN(n, a) ((n) & ~((a)-1))
#define ALIGN_UP(n, a) ALIGN_DOWN((n) + (a)-1, (a))
// Number of stack bytes required be reserved for the callee.
#define SHADOW_SPACE_SIZE 32
int amd64_winabi_rsave[] = {RCX, RDX, R8, R9, R10, R11, RAX, XMM0,
XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7, XMM8,
XMM9, XMM10, XMM11, XMM12, XMM13, XMM14, -1};
int amd64_winabi_rclob[] = {RBX, R12, R13, R14, R15, RSI, RDI, -1};
MAKESURE(winabi_arrays_ok,
sizeof amd64_winabi_rsave == (NGPS_WIN + NFPS + 1) * sizeof(int) &&
sizeof amd64_winabi_rclob == (NCLR_WIN + 1) * sizeof(int));
// layout of call's second argument (RCall)
//
// bit 0: rax returned
// bit 1: xmm0 returned
// bits 23: 0
// bits 4567: rcx, rdx, r8, r9 passed
// bits 89ab: xmm0,1,2,3 passed
// bit c: env call (rax passed)
// bits d..1f: 0
bits amd64_winabi_retregs(Ref r, int p[2]) {
assert(rtype(r) == RCall);
bits b = 0;
int num_int_returns = r.val & 1;
int num_float_returns = r.val & 2;
if (num_int_returns == 1) {
b |= BIT(RAX);
} else {
b |= BIT(XMM0);
}
if (p) {
p[0] = num_int_returns;
p[1] = num_float_returns;
}
return b;
}
static uint popcnt(bits b) {
b = (b & 0x5555555555555555) + ((b >> 1) & 0x5555555555555555);
b = (b & 0x3333333333333333) + ((b >> 2) & 0x3333333333333333);
b = (b & 0x0f0f0f0f0f0f0f0f) + ((b >> 4) & 0x0f0f0f0f0f0f0f0f);
b += (b >> 8);
b += (b >> 16);
b += (b >> 32);
return b & 0xff;
}
bits amd64_winabi_argregs(Ref r, int p[2]) {
assert(rtype(r) == RCall);
// On SysV, these are counts. Here, a count isn't sufficient, we actually need
// to know which ones are in use because they're not necessarily contiguous.
int int_passed = (r.val >> 4) & 15;
int float_passed = (r.val >> 8) & 15;
bool env_param = (r.val >> 12) & 1;
bits b = 0;
b |= (int_passed & 1) ? BIT(RCX) : 0;
b |= (int_passed & 2) ? BIT(RDX) : 0;
b |= (int_passed & 4) ? BIT(R8) : 0;
b |= (int_passed & 8) ? BIT(R9) : 0;
b |= (float_passed & 1) ? BIT(XMM0) : 0;
b |= (float_passed & 2) ? BIT(XMM1) : 0;
b |= (float_passed & 4) ? BIT(XMM2) : 0;
b |= (float_passed & 8) ? BIT(XMM3) : 0;
b |= env_param ? BIT(RAX) : 0;
if (p) {
// TODO: The only place this is used is live.c. I'm not sure what should be
// returned here wrt to using the same counter for int/float regs on win.
// For now, try the number of registers in use even though they're not
// contiguous.
p[0] = popcnt(int_passed);
p[1] = popcnt(float_passed);
}
return b;
}
typedef struct RegisterUsage {
// Counter for both int/float as they're counted together. Only if the bool's
// set in regs_passed is the given register *actually* needed for a value
// (i.e. needs to be saved, etc.).
int num_regs_passed;
// Indexed first by 0=int, 1=float, use KBASE(cls).
// Indexed second by register index in calling convention, so for integer,
// 0=RCX, 1=RDX, 2=R8, 3=R9, and for float XMM0, XMM1, XMM2, XMM3.
bool regs_passed[2][4];
bool rax_returned;
bool xmm0_returned;
// This is also used as where the va_start will start for varargs functions
// (there's no 'Oparv', so we need to keep track of a count here.)
int num_named_args_passed;
// This is set when classifying the arguments for a call (but not when
// classifying the parameters of a function definition).
bool is_varargs_call;
bool has_env;
} RegisterUsage;
static int register_usage_to_call_arg_value(RegisterUsage reg_usage) {
return (reg_usage.rax_returned << 0) | //
(reg_usage.xmm0_returned << 1) | //
(reg_usage.regs_passed[0][0] << 4) | //
(reg_usage.regs_passed[0][1] << 5) | //
(reg_usage.regs_passed[0][2] << 6) | //
(reg_usage.regs_passed[0][3] << 7) | //
(reg_usage.regs_passed[1][0] << 8) | //
(reg_usage.regs_passed[1][1] << 9) | //
(reg_usage.regs_passed[1][2] << 10) | //
(reg_usage.regs_passed[1][3] << 11) | //
(reg_usage.has_env << 12);
}
// Assigns the argument to a register if there's any left according to the
// calling convention, and updates the regs_passed bools. Otherwise marks the
// value as needing stack space to be passed.
static void assign_register_or_stack(RegisterUsage* reg_usage,
ArgClass* arg,
bool is_float,
bool by_copy) {
if (reg_usage->num_regs_passed == 4) {
arg->style = by_copy ? APS_CopyAndPointerOnStack : APS_InlineOnStack;
} else {
reg_usage->regs_passed[is_float][reg_usage->num_regs_passed] = true;
++reg_usage->num_regs_passed;
arg->style = by_copy ? APS_CopyAndPointerInRegister : APS_Register;
}
++reg_usage->num_named_args_passed;
}
static bool type_is_by_copy(Typ* type) {
// Note that only these sizes are passed by register, even though e.g. a
// 5 byte struct would "fit", it still is passed by copy-and-pointer.
return type->isdark || (type->size != 1 && type->size != 2 &&
type->size != 4 && type->size != 8);
}
// This function is used for both arguments and parameters.
// begin_instr should either point at the first Oarg or Opar, and end_instr
// should point past the last one (so to the Ocall for arguments, or to the
// first 'real' instruction of the function for parameters).
static void classify_arguments(RegisterUsage* reg_usage,
Ins* begin_instr,
Ins* end_instr,
ArgClass* arg_classes,
Ref* env) {
ArgClass* arg = arg_classes;
// For each argument, determine how it will be passed (int, float, stack)
// and update the `reg_usage` counts. Additionally, fill out arg_classes for
// each argument.
for (Ins* instr = begin_instr; instr < end_instr; ++instr, ++arg) {
switch (instr->op) {
case Oarg:
case Opar:
assign_register_or_stack(reg_usage, arg, KBASE(instr->cls),
/*by_copy=*/false);
arg->cls = instr->cls;
arg->align = 3;
arg->size = 8;
break;
case Oargc:
case Oparc: {
int typ_index = instr->arg[0].val;
Typ* type = &typ[typ_index];
bool by_copy = type_is_by_copy(type);
assign_register_or_stack(reg_usage, arg, /*is_float=*/false, by_copy);
arg->cls = Kl;
if (!by_copy && type->size <= 4) {
arg->cls = Kw;
}
arg->align = 3;
arg->size = type->size;
break;
}
case Oarge:
*env = instr->arg[0];
arg->style = APS_EnvTag;
reg_usage->has_env = true;
break;
case Opare:
*env = instr->to;
arg->style = APS_EnvTag;
reg_usage->has_env = true;
break;
case Oargv:
reg_usage->is_varargs_call = true;
arg->style = APS_VarargsTag;
break;
}
}
if (reg_usage->has_env && reg_usage->is_varargs_call) {
die("can't use env with varargs");
}
// During a varargs call, float arguments have to be duplicated to their
// associated integer register, so mark them as in-use too.
if (reg_usage->is_varargs_call) {
for (int i = 0; i < 4; ++i) {
if (reg_usage->regs_passed[/*float*/ 1][i]) {
reg_usage->regs_passed[/*int*/ 0][i] = true;
}
}
}
}
static bool is_integer_type(int ty) {
assert(ty >= 0 && ty < 4 && "expecting Kw Kl Ks Kd");
return KBASE(ty) == 0;
}
static Ref register_for_arg(int cls, int counter) {
assert(counter < 4);
if (is_integer_type(cls)) {
return TMP(amd64_winabi_rsave[counter]);
} else {
return TMP(XMM0 + counter);
}
}
static Ins* lower_call(Fn* func,
Blk* block,
Ins* call_instr,
ExtraAlloc** pextra_alloc) {
// Call arguments are instructions. Walk through them to find the end of the
// call+args that we need to process (and return the instruction past the body
// of the instruction for continuing processing).
Ins* instr_past_args = call_instr - 1;
for (; instr_past_args >= block->ins; --instr_past_args) {
if (!isarg(instr_past_args->op)) {
break;
}
}
Ins* earliest_arg_instr = instr_past_args + 1;
// Don't need an ArgClass for the call itself, so one less than the total
// number of instructions we're dealing with.
uint num_args = call_instr - earliest_arg_instr;
ArgClass* arg_classes = alloc(num_args * sizeof(ArgClass));
RegisterUsage reg_usage = {0};
ArgClass ret_arg_class = {0};
// Ocall's two arguments are the the function to be called in 0, and, if the
// the function returns a non-basic type, then arg[1] is a reference to the
// type of the return. req checks if Refs are equal; `R` is 0.
bool il_has_struct_return = !req(call_instr->arg[1], R);
bool is_struct_return = false;
if (il_has_struct_return) {
Typ* ret_type = &typ[call_instr->arg[1].val];
is_struct_return = type_is_by_copy(ret_type);
if (is_struct_return) {
assign_register_or_stack(&reg_usage, &ret_arg_class, /*is_float=*/false,
/*by_copy=*/true);
}
ret_arg_class.size = ret_type->size;
}
Ref env = R;
classify_arguments(&reg_usage, earliest_arg_instr, call_instr, arg_classes,
&env);
// We now know which arguments are on the stack and which are in registers, so
// we can allocate the correct amount of space to stash the stack-located ones
// into.
uint stack_usage = 0;
for (uint i = 0; i < num_args; ++i) {
ArgClass* arg = &arg_classes[i];
// stack_usage only accounts for pushes that are for values that don't have
// enough registers. Large struct copies are alloca'd separately, and then
// only have (potentially) 8 bytes to add to stack_usage here.
if (arg->style == APS_InlineOnStack) {
if (arg->align > 4) {
err("win abi cannot pass alignments > 16");
}
stack_usage += arg->size;
} else if (arg->style == APS_CopyAndPointerOnStack) {
stack_usage += 8;
}
}
stack_usage = ALIGN_UP(stack_usage, 16);
// Note that here we're logically 'after' the call (due to emitting
// instructions in reverse order), so we're doing a negative stack
// allocation to clean up after the call.
Ref stack_size_ref =
getcon(-(int64_t)(stack_usage + SHADOW_SPACE_SIZE), func);
emit(Osalloc, Kl, R, stack_size_ref, R);
ExtraAlloc* return_pad = NULL;
if (is_struct_return) {
return_pad = alloc(sizeof(ExtraAlloc));
Ref ret_pad_ref = newtmp("abi.ret_pad", Kl, func);
return_pad->instr =
(Ins){Oalloc8, Kl, ret_pad_ref, {getcon(ret_arg_class.size, func)}};
return_pad->link = (*pextra_alloc);
*pextra_alloc = return_pad;
reg_usage.rax_returned = true;
emit(Ocopy, call_instr->cls, call_instr->to, TMP(RAX), R);
} else {
if (il_has_struct_return) {
// In the case that at the IL level, a struct return was specified, but as
// far as the calling convention is concerned it's not actually by
// pointer, we need to store the return value into an alloca because
// subsequent IL will still be treating the function return as a pointer.
ExtraAlloc* return_copy = alloc(sizeof(ExtraAlloc));
return_copy->instr =
(Ins){Oalloc8, Kl, call_instr->to, {getcon(8, func)}};
return_copy->link = (*pextra_alloc);
*pextra_alloc = return_copy;
Ref copy = newtmp("abi.copy", Kl, func);
emit(Ostorel, 0, R, copy, call_instr->to);
emit(Ocopy, Kl, copy, TMP(RAX), R);
reg_usage.rax_returned = true;
} else if (is_integer_type(call_instr->cls)) {
// Only a basic type returned from the call, integer.
emit(Ocopy, call_instr->cls, call_instr->to, TMP(RAX), R);
reg_usage.rax_returned = true;
} else {
// Basic type, floating point.
emit(Ocopy, call_instr->cls, call_instr->to, TMP(XMM0), R);
reg_usage.xmm0_returned = true;
}
}
// Emit the actual call instruction. There's no 'to' value by this point
// because we've lowered it into register manipulation (that's the `R`),
// arg[0] of the call is the function, and arg[1] is register usage is
// documented as above (copied from SysV).
emit(Ocall, call_instr->cls, R, call_instr->arg[0],
CALL(register_usage_to_call_arg_value(reg_usage)));
if (!req(R, env)) {
// If there's an env arg to be passed, it gets stashed in RAX.
emit(Ocopy, Kl, TMP(RAX), env, R);
}
if (reg_usage.is_varargs_call) {
// Any float arguments need to be duplicated to integer registers. This is
// required by the calling convention so that dumping to shadow space can be
// done without a prototype and for varargs.
#define DUP_IF_USED(index, floatreg, intreg) \
if (reg_usage.regs_passed[/*float*/ 1][index]) { \
emit(Ocast, Kl, TMP(intreg), TMP(floatreg), R); \
}
DUP_IF_USED(0, XMM0, RCX);
DUP_IF_USED(1, XMM1, RDX);
DUP_IF_USED(2, XMM2, R8);
DUP_IF_USED(3, XMM3, R9);
#undef DUP_IF_USED
}
int reg_counter = 0;
if (is_struct_return) {
Ref first_reg = register_for_arg(Kl, reg_counter++);
emit(Ocopy, Kl, first_reg, return_pad->instr.to, R);
}
// This is where we actually do the load of values into registers or into
// stack slots.
Ref arg_stack_slots = newtmp("abi.args", Kl, func);
uint slot_offset = SHADOW_SPACE_SIZE;
ArgClass* arg = arg_classes;
for (Ins* instr = earliest_arg_instr; instr != call_instr; ++instr, ++arg) {
switch (arg->style) {
case APS_Register: {
Ref into = register_for_arg(arg->cls, reg_counter++);
if (instr->op == Oargc) {
// If this is a small struct being passed by value. The value in the
// instruction in this case is a pointer, but it needs to be loaded
// into the register.
emit(Oload, arg->cls, into, instr->arg[1], R);
} else {
// Otherwise, a normal value passed in a register.
emit(Ocopy, instr->cls, into, instr->arg[0], R);
}
break;
}
case APS_InlineOnStack: {
Ref slot = newtmp("abi.off", Kl, func);
if (instr->op == Oargc) {
// This is a small struct, so it's not passed by copy, but the
// instruction is a pointer. So we need to copy it into the stack
// slot. (And, remember that these are emitted backwards, so store,
// then load.)
Ref smalltmp = newtmp("abi.smalltmp", arg->cls, func);
emit(Ostorel, 0, R, smalltmp, slot);
emit(Oload, arg->cls, smalltmp, instr->arg[1], R);
} else {
// Stash the value into the stack slot.
emit(Ostorel, 0, R, instr->arg[0], slot);
}
emit(Oadd, Kl, slot, arg_stack_slots, getcon(slot_offset, func));
slot_offset += arg->size;
break;
}
case APS_CopyAndPointerInRegister:
case APS_CopyAndPointerOnStack: {
// Alloca a space to copy into, and blit the value from the instr to the
// copied location.
ExtraAlloc* arg_copy = alloc(sizeof(ExtraAlloc));
Ref copy_ref = newtmp("abi.copy", Kl, func);
arg_copy->instr =
(Ins){Oalloc8, Kl, copy_ref, {getcon(arg->size, func)}};
arg_copy->link = (*pextra_alloc);
*pextra_alloc = arg_copy;
emit(Oblit1, 0, R, INT(arg->size), R);
emit(Oblit0, 0, R, instr->arg[1], copy_ref);
// Now load the pointer into the correct register or stack slot.
if (arg->style == APS_CopyAndPointerInRegister) {
Ref into = register_for_arg(arg->cls, reg_counter++);
emit(Ocopy, Kl, into, copy_ref, R);
} else {
assert(arg->style == APS_CopyAndPointerOnStack);
Ref slot = newtmp("abi.off", Kl, func);
emit(Ostorel, 0, R, copy_ref, slot);
emit(Oadd, Kl, slot, arg_stack_slots, getcon(slot_offset, func));
slot_offset += 8;
}
break;
}
case APS_EnvTag:
case APS_VarargsTag:
// Nothing to do here, see right before the call for reg dupe.
break;
case APS_Invalid:
die("unreachable");
}
}
if (stack_usage) {
// The last (first in call order) thing we do is allocate the the stack
// space we're going to fill with temporaries.
emit(Osalloc, Kl, arg_stack_slots,
getcon(stack_usage + SHADOW_SPACE_SIZE, func), R);
} else {
// When there's no usage for temporaries, we can add this into the other
// alloca, but otherwise emit it separately (not storing into a reference)
// so that it doesn't get removed later for being useless.
emit(Osalloc, Kl, R, getcon(SHADOW_SPACE_SIZE, func), R);
}
return instr_past_args;
}
static void lower_block_return(Fn* func, Blk* block) {
int jmp_type = block->jmp.type;
if (!isret(jmp_type) || jmp_type == Jret0) {
return;
}
// Save the argument, and set the block to be a void return because once it's
// lowered it's handled by the the register/stack manipulation.
Ref ret_arg = block->jmp.arg;
block->jmp.type = Jret0;
RegisterUsage reg_usage = {0};
if (jmp_type == Jretc) {
Typ* type = &typ[func->retty];
if (type_is_by_copy(type)) {
assert(rtype(func->retr) == RTmp);
emit(Ocopy, Kl, TMP(RAX), func->retr, R);
emit(Oblit1, 0, R, INT(type->size), R);
emit(Oblit0, 0, R, ret_arg, func->retr);
} else {
emit(Oload, Kl, TMP(RAX), ret_arg, R);
}
reg_usage.rax_returned = true;
} else {
int k = jmp_type - Jretw;
if (is_integer_type(k)) {
emit(Ocopy, k, TMP(RAX), ret_arg, R);
reg_usage.rax_returned = true;
} else {
emit(Ocopy, k, TMP(XMM0), ret_arg, R);
reg_usage.xmm0_returned = true;
}
}
block->jmp.arg = CALL(register_usage_to_call_arg_value(reg_usage));
}
static void lower_vastart(Fn* func,
RegisterUsage* param_reg_usage,
Ref valist) {
assert(func->vararg);
// In varargs functions:
// 1. the int registers are already dumped to the shadow stack space;
// 2. any parameters passed in floating point registers have
// been duplicated to the integer registers
// 3. we ensure (later) that for varargs functions we're always using an rbp
// frame pointer.
// So, the ... argument is just indexed past rbp by the number of named values
// that were actually passed.
Ref offset = newtmp("abi.vastart", Kl, func);
emit(Ostorel, 0, R, offset, valist);
// *8 for sizeof(u64), +16 because the return address and rbp have been pushed
// by the time we get to the body of the function.
emit(Oadd, Kl, offset, TMP(RBP),
getcon(param_reg_usage->num_named_args_passed * 8 + 16, func));
}
static void lower_vaarg(Fn* func, Ins* vaarg_instr) {
// va_list is just a void** on winx64, so load the pointer, then load the
// argument from that pointer, then increment the pointer to the next arg.
// (All emitted backwards as usual.)
Ref inc = newtmp("abi.vaarg.inc", Kl, func);
Ref ptr = newtmp("abi.vaarg.ptr", Kl, func);
emit(Ostorel, 0, R, inc, vaarg_instr->arg[0]);
emit(Oadd, Kl, inc, ptr, getcon(8, func));
emit(Oload, vaarg_instr->cls, vaarg_instr->to, ptr, R);
emit(Oload, Kl, ptr, vaarg_instr->arg[0], R);
}
static void lower_args_for_block(Fn* func,
Blk* block,
RegisterUsage* param_reg_usage,
ExtraAlloc** pextra_alloc) {
// global temporary buffer used by emit. Reset to the end, and predecremented
// when adding to it.
curi = &insb[NIns];
lower_block_return(func, block);
if (block->nins) {
// Work backwards through the instructions, either copying them unchanged,
// or modifying as necessary.
for (Ins* instr = &block->ins[block->nins - 1]; instr >= block->ins;) {
switch (instr->op) {
case Ocall:
instr = lower_call(func, block, instr, pextra_alloc);
break;
case Ovastart:
lower_vastart(func, param_reg_usage, instr->arg[0]);
--instr;
break;
case Ovaarg:
lower_vaarg(func, instr);
--instr;
break;
case Oarg:
case Oargc:
die("unreachable");
default:
emiti(*instr);
--instr;
break;
}
}
}
// This it the start block, which is processed last. Add any allocas that
// other blocks needed.
bool is_start_block = block == func->start;
if (is_start_block) {
for (ExtraAlloc* ea = *pextra_alloc; ea; ea = ea->link) {
emiti(ea->instr);
}
}
// emit/emiti add instructions from the end to the beginning of the temporary
// global buffer. dup the final version into the final block storage.
block->nins = &insb[NIns] - curi;
idup(block, curi, block->nins);
}
static Ins* find_end_of_func_parameters(Blk* start_block) {
Ins* i;
for (i = start_block->ins; i < &start_block->ins[start_block->nins]; ++i) {
if (!ispar(i->op)) {
break;
}
}
return i;
}
// Copy from registers/stack into values.
static RegisterUsage lower_func_parameters(Fn* func) {
// This is half-open, so end points after the last Opar.
Blk* start_block = func->start;
Ins* start_of_params = start_block->ins;
Ins* end_of_params = find_end_of_func_parameters(start_block);
size_t num_params = end_of_params - start_of_params;
ArgClass* arg_classes = alloc(num_params * sizeof(ArgClass));
ArgClass arg_ret = {0};
// global temporary buffer used by emit. Reset to the end, and predecremented
// when adding to it.
curi = &insb[NIns];
int reg_counter = 0;
RegisterUsage reg_usage = {0};
if (func->retty >= 0) {
bool by_copy = type_is_by_copy(&typ[func->retty]);
if (by_copy) {
assign_register_or_stack(&reg_usage, &arg_ret, /*is_float=*/false,
by_copy);
Ref ret_ref = newtmp("abi.ret", Kl, func);
emit(Ocopy, Kl, ret_ref, TMP(RCX), R);
func->retr = ret_ref;
++reg_counter;
}
}
Ref env = R;
classify_arguments(&reg_usage, start_of_params, end_of_params, arg_classes,
&env);
func->reg = amd64_winabi_argregs(
CALL(register_usage_to_call_arg_value(reg_usage)), NULL);
// Copy from the registers or stack slots into the named parameters. Depending
// on how they're passed, they either need to be copied or loaded.
ArgClass* arg = arg_classes;
uint slot_offset = SHADOW_SPACE_SIZE / 4 + 4;
for (Ins* instr = start_of_params; instr < end_of_params; ++instr, ++arg) {
switch (arg->style) {
case APS_Register: {
Ref from = register_for_arg(arg->cls, reg_counter++);
// If it's a struct at the IL level, we need to copy the register into
// an alloca so we have something to point at (same for InlineOnStack).
if (instr->op == Oparc) {
arg->ref = newtmp("abi", Kl, func);
emit(Ostorel, 0, R, arg->ref, instr->to);
emit(Ocopy, instr->cls, arg->ref, from, R);
emit(Oalloc8, Kl, instr->to, getcon(arg->size, func), R);
} else {
emit(Ocopy, instr->cls, instr->to, from, R);
}
break;
}
case APS_InlineOnStack:
if (instr->op == Oparc) {
arg->ref = newtmp("abi", Kl, func);
emit(Ostorel, 0, R, arg->ref, instr->to);
emit(Ocopy, instr->cls, arg->ref, SLOT(-slot_offset), R);
emit(Oalloc8, Kl, instr->to, getcon(arg->size, func), R);
} else {
emit(Ocopy, Kl, instr->to, SLOT(-slot_offset), R);
}
slot_offset += 2;
break;
case APS_CopyAndPointerOnStack:
emit(Oload, Kl, instr->to, SLOT(-slot_offset), R);
slot_offset += 2;
break;
case APS_CopyAndPointerInRegister: {
// Because this has to be a copy (that we own), it is sufficient to just
// copy the register to the target.
Ref from = register_for_arg(Kl, reg_counter++);
emit(Ocopy, Kl, instr->to, from, R);
break;
}
case APS_EnvTag:
break;
case APS_VarargsTag:
case APS_Invalid:
die("unreachable");
}
}
// If there was an `env`, it was passed in RAX, so copy it into the env ref.
if (!req(R, env)) {
emit(Ocopy, Kl, env, TMP(RAX), R);
}
int num_created_instrs = &insb[NIns] - curi;
int num_other_after_instrs = (int)(start_block->nins - num_params);
int new_total_instrs = num_other_after_instrs + num_created_instrs;
Ins* new_instrs = vnew(new_total_instrs, sizeof(Ins), PFn);
Ins* instr_p = icpy(new_instrs, curi, num_created_instrs);
icpy(instr_p, end_of_params, num_other_after_instrs);
start_block->nins = new_total_instrs;
start_block->ins = new_instrs;
return reg_usage;
}
// The main job of this function is to lower generic instructions into the
// specific details of how arguments are passed, and parameters are
// interpreted for win x64. A useful reference is
// https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention .
//
// Some of the major differences from SysV if you're comparing the code
// (non-exhaustive):
// - only 4 int and 4 float regs are used
// - when an int register is assigned a value, its associated float register is
// left unused (and vice versa). i.e. there's only one counter as you assign
// arguments to registers.
// - any structs that aren't 1/2/4/8 bytes in size are passed by pointer, not
// by copying them into the stack. So e.g. if you pass something like
// `struct { void*, int64_t }` by value, it first needs to be copied to
// another alloca (in order to maintain value semantics at the language
// level), then the pointer to that copy is treated as a regular integer
// argument (which then itself may *also* be copied to the stack in the case
// there's no integer register remaining.)
// - when calling a varargs functions, floating point values must be duplicated
// integer registers. Along with the above restrictions, this makes varargs
// handling simpler for the callee than SysV.
void amd64_winabi_abi(Fn* func) {
// The first thing to do is lower incoming parameters to this function.
RegisterUsage param_reg_usage = lower_func_parameters(func);
// This is the second larger part of the job. We walk all blocks, and rewrite
// instructions returns, calls, and handling of varargs into their win x64
// specific versions. Any other instructions are just passed through unchanged
// by using `emiti`.
// Skip over the entry block, and do it at the end so that our later
// modifications can add allocations to the start block. In particular, we
// need to add stack allocas for copies when structs are passed or returned by
// value.
ExtraAlloc* extra_alloc = NULL;
for (Blk* block = func->start->link; block; block = block->link) {
lower_args_for_block(func, block, &param_reg_usage, &extra_alloc);
}
lower_args_for_block(func, func->start, &param_reg_usage, &extra_alloc);
if (debug['A']) {
fprintf(stderr, "\n> After ABI lowering:\n");
printfn(func, stderr);
}
}

View File

@ -90,7 +90,7 @@ isfloatv(Typ *t, char *cls)
static void
typclass(Class *c, Typ *t, int *gp, int *fp)
{
uint64_t sz;
uint64_t sz, hfasz;
uint n;
sz = (t->size + 7) & -8;
@ -103,7 +103,21 @@ typclass(Class *c, Typ *t, int *gp, int *fp)
if (t->align > 3)
err("alignments larger than 8 are not supported");
if (t->isdark || sz > 16 || sz == 0) {
c->size = sz;
c->hfa.base = Kx;
c->ishfa = isfloatv(t, &c->hfa.base);
hfasz = t->size/(KWIDE(c->hfa.base) ? 8 : 4);
c->ishfa &= !t->isdark && hfasz <= 4;
c->hfa.size = hfasz;
if (c->ishfa) {
for (n=0; n<hfasz; n++, c->nfp++) {
c->reg[n] = *fp++;
c->cls[n] = c->hfa.base;
}
c->nreg = n;
}
else if (t->isdark || sz > 16 || sz == 0) {
/* large structs are replaced by a
* pointer to some caller-allocated
* memory */
@ -112,26 +126,14 @@ typclass(Class *c, Typ *t, int *gp, int *fp)
c->ngp = 1;
*c->reg = *gp;
*c->cls = Kl;
return;
}
c->size = sz;
c->hfa.base = Kx;
c->ishfa = isfloatv(t, &c->hfa.base);
c->hfa.size = t->size/(KWIDE(c->hfa.base) ? 8 : 4);
if (c->ishfa)
for (n=0; n<c->hfa.size; n++, c->nfp++) {
c->reg[n] = *fp++;
c->cls[n] = c->hfa.base;
}
else
else {
for (n=0; n<sz/8; n++, c->ngp++) {
c->reg[n] = *gp++;
c->cls[n] = Kl;
}
c->nreg = n;
c->nreg = n;
}
}
static void
@ -427,7 +429,7 @@ selcall(Fn *fn, Ins *i0, Ins *i1, Insl **ilp)
for (i=i0, c=ca; i<i1; i++, c++) {
if ((c->class & Cstk) != 0)
continue;
if (i->op == Oarg || i->op == Oarge)
if (i->op == Oarg || i->op == Oarge || isargbh(i->op))
emit(Ocopy, *c->cls, TMP(*c->reg), i->arg[0], R);
if (i->op == Oargc)
ldregs(c->reg, c->cls, c->nreg, i->arg[1], fn);
@ -543,8 +545,7 @@ split(Fn *fn, Blk *b)
++fn->nblk;
bn = newblk();
bn->nins = &insb[NIns] - curi;
idup(&bn->ins, curi, bn->nins);
idup(bn, curi, &insb[NIns]-curi);
curi = &insb[NIns];
bn->visit = ++b->visit;
strf(bn->name, "%s.%d", b->name, b->visit);
@ -728,9 +729,9 @@ void
arm64_abi(Fn *fn)
{
Blk *b;
Ins *i, *i0, *ip;
Ins *i, *i0;
Insl *il;
int n;
int n0, n1, ioff;
Params p;
for (b=fn->start; b; b=b->link)
@ -741,12 +742,13 @@ arm64_abi(Fn *fn)
if (!ispar(i->op))
break;
p = selpar(fn, b->ins, i);
n = b->nins - (i - b->ins) + (&insb[NIns] - curi);
i0 = alloc(n * sizeof(Ins));
ip = icpy(ip = i0, curi, &insb[NIns] - curi);
ip = icpy(ip, i, &b->ins[b->nins] - i);
b->nins = n;
b->ins = i0;
n0 = &insb[NIns] - curi;
ioff = i - b->ins;
n1 = b->nins - ioff;
vgrow(&b->ins, n0+n1);
icpy(b->ins+n0, b->ins+ioff, n1);
icpy(b->ins, curi, n0);
b->nins = n0+n1;
/* lower calls, returns, and vararg instructions */
il = 0;
@ -789,8 +791,7 @@ arm64_abi(Fn *fn)
if (b == fn->start)
for (; il; il=il->link)
emiti(il->i);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
} while (b != fn->start);
if (debug['A']) {
@ -841,8 +842,7 @@ apple_extsb(Fn *fn)
emit(op, Kw, i->to, i->arg[0], R);
}
}
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
}
if (debug['A']) {

View File

@ -37,7 +37,7 @@ enum {
static struct {
short op;
short cls;
char *asm;
char *fmt;
} omap[] = {
{ Oadd, Ki, "add %=, %0, %1" },
{ Oadd, Ka, "fadd %=, %0, %1" },
@ -109,6 +109,10 @@ static struct {
{ NOp, 0, 0 }
};
enum {
V31 = 0x1fffffff, /* local name for V31 */
};
static char *
rname(int r, int k)
{
@ -132,6 +136,12 @@ rname(int r, int k)
case Kx:
case Kd: sprintf(buf, "d%d", r-V0); break;
}
else if (r == V31)
switch (k) {
default: die("invalid class");
case Ks: sprintf(buf, "s31"); break;
case Kd: sprintf(buf, "d31"); break;
}
else
die("invalid register");
return buf;
@ -160,7 +170,8 @@ emitf(char *s, Ins *i, E *e)
Ref r;
int k, c;
Con *pc;
uint n, sp;
uint64_t n;
uint sp;
fputc('\t', e->f);
@ -171,7 +182,7 @@ emitf(char *s, Ins *i, E *e)
if (c == ' ' && !sp) {
fputc('\t', e->f);
sp = 1;
} else if ( !c) {
} else if (!c) {
fputc('\n', e->f);
return;
} else
@ -194,14 +205,14 @@ emitf(char *s, Ins *i, E *e)
goto Switch;
case '?':
if (KBASE(k) == 0)
fputs(rname(R18, k), e->f);
fputs(rname(IP1, k), e->f);
else
fputs(k==Ks ? "s31" : "d31", e->f);
fputs(rname(V31, k), e->f);
break;
case '=':
case '0':
r = c == '=' ? i->to : i->arg[0];
assert(isreg(r));
assert(isreg(r) || req(r, TMP(V31)));
fputs(rname(r.val, k), e->f);
break;
case '1':
@ -217,10 +228,17 @@ emitf(char *s, Ins *i, E *e)
pc = &e->fn->con[r.val];
n = pc->bits.i;
assert(pc->type == CBits);
if (n & 0xfff000)
fprintf(e->f, "#%u, lsl #12", n>>12);
else
fprintf(e->f, "#%u", n);
if (n >> 24) {
assert(arm64_logimm(n, k));
fprintf(e->f, "#%"PRIu64, n);
} else if (n & 0xfff000) {
assert(!(n & ~0xfff000ull));
fprintf(e->f, "#%"PRIu64", lsl #12",
n>>12);
} else {
assert(!(n & ~0xfffull));
fprintf(e->f, "#%"PRIu64, n);
}
break;
}
break;
@ -304,6 +322,7 @@ loadcon(Con *c, int r, int k, E *e)
rn = rname(r, k);
n = c->bits.i;
if (c->type == CAddr) {
rn = rname(r, Kl);
loadaddr(c, rn, e);
return;
}
@ -326,8 +345,8 @@ loadcon(Con *c, int r, int k, E *e)
static void emitins(Ins *, E *);
static void
fixarg(Ref *pr, int sz, E *e)
static int
fixarg(Ref *pr, int sz, int t, E *e)
{
Ins *i;
Ref r;
@ -337,11 +356,14 @@ fixarg(Ref *pr, int sz, E *e)
if (rtype(r) == RSlot) {
s = slot(r, e);
if (s > sz * 4095u) {
i = &(Ins){Oaddr, Kl, TMP(IP0), {r}};
if (t < 0)
return 1;
i = &(Ins){Oaddr, Kl, TMP(t), {r}};
emitins(i, e);
*pr = TMP(IP0);
*pr = TMP(t);
}
}
return 0;
}
static void
@ -349,16 +371,28 @@ emitins(Ins *i, E *e)
{
char *l, *p, *rn;
uint64_t s;
int o;
int o, t;
Ref r;
Con *c;
switch (i->op) {
default:
if (isload(i->op))
fixarg(&i->arg[0], loadsz(i), e);
if (isstore(i->op))
fixarg(&i->arg[1], storesz(i), e);
fixarg(&i->arg[0], loadsz(i), IP1, e);
if (isstore(i->op)) {
t = T.apple ? -1 : R18;
if (fixarg(&i->arg[1], storesz(i), t, e)) {
if (req(i->arg[0], TMP(IP1))) {
fprintf(e->f,
"\tfmov\t%c31, %c17\n",
"ds"[i->cls == Kw],
"xw"[i->cls == Kw]);
i->arg[0] = TMP(V31);
i->op = Ostores + (i->cls-Kw);
}
fixarg(&i->arg[1], storesz(i), IP1, e);
}
}
Table:
/* most instructions are just pulled out of
* the table omap[], some special cases are
@ -374,7 +408,7 @@ emitins(Ins *i, E *e)
|| (omap[o].cls == Ki && KBASE(i->cls) == 0))
break;
}
emitf(omap[o].asm, i, e);
emitf(omap[o].fmt, i, e);
break;
case Onop:
break;
@ -384,7 +418,7 @@ emitins(Ins *i, E *e)
if (rtype(i->to) == RSlot) {
r = i->to;
if (!isreg(i->arg[0])) {
i->to = TMP(R18);
i->to = TMP(IP1);
emitins(i, e);
i->arg[0] = i->to;
}
@ -405,7 +439,7 @@ emitins(Ins *i, E *e)
emitins(i, e);
break;
default:
assert(i->to.val != R18);
assert(i->to.val != IP1);
goto Table;
}
break;
@ -514,6 +548,7 @@ arm64_emitfn(Fn *fn, FILE *out)
if (T.apple)
e->fn->lnk.align = 4;
emitfnlnk(e->fn->name, &e->fn->lnk, e->f);
fputs("\thint\t#34\n", e->f);
framelayout(e);
if (e->fn->vararg && !T.apple) {

View File

@ -24,7 +24,7 @@ imm(Con *c, int k, int64_t *pn)
i = Iplo12;
if (n < 0) {
i = Inlo12;
n = -n;
n = -(uint64_t)n;
}
*pn = n;
if ((n & 0x000fff) == n)
@ -109,7 +109,7 @@ fixarg(Ref *pr, int k, int phi, Fn *fn)
if (KBASE(k) == 0) {
emit(Ocopy, k, r1, r0, R);
} else {
n = stashbits(&c->bits, KWIDE(k) ? 8 : 4);
n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4);
vgrow(&fn->con, ++fn->ncon);
c = &fn->con[fn->ncon-1];
sprintf(buf, "\"%sfp%d\"", T.asloc, n);
@ -306,8 +306,7 @@ arm64_isel(Fn *fn)
seljmp(b, fn);
for (i=&b->ins[b->nins]; i!=b->ins;)
sel(*--i, fn);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
}
if (debug['I']) {

View File

@ -16,7 +16,7 @@ int arm64_rclob[] = {
-1
};
#define RGLOB (BIT(FP) | BIT(SP) | BIT(R18))
#define RGLOB (BIT(FP) | BIT(SP) | BIT(IP1) | BIT(R18))
static int
arm64_memargs(int op)
@ -31,7 +31,7 @@ arm64_memargs(int op)
.fpr0 = V0, \
.nfpr = NFPR, \
.rglob = RGLOB, \
.nrglob = 3, \
.nrglob = 4, \
.rsave = arm64_rsave, \
.nrsave = {NGPS, NFPS}, \
.retregs = arm64_retregs, \
@ -40,6 +40,7 @@ arm64_memargs(int op)
.isel = arm64_isel, \
.abi1 = arm64_abi, \
.emitfn = arm64_emitfn, \
.cansel = 0, \
Target T_arm64 = {
.name = "arm64",

354
cfg.c
View File

@ -8,66 +8,50 @@ newblk()
b = alloc(sizeof *b);
*b = z;
b->ins = vnew(0, sizeof b->ins[0], PFn);
b->pred = vnew(0, sizeof b->pred[0], PFn);
return b;
}
void
edgedel(Blk *bs, Blk **pbd)
static void
fixphis(Fn *f)
{
Blk *bd;
Blk *b, *bp;
Phi *p;
uint a;
int mult;
uint n, n0;
bd = *pbd;
mult = 1 + (bs->s1 == bs->s2);
*pbd = 0;
if (!bd || mult > 1)
return;
for (p=bd->phi; p; p=p->link) {
for (a=0; p->blk[a]!=bs; a++)
assert(a+1<p->narg);
p->narg--;
memmove(&p->blk[a], &p->blk[a+1],
sizeof p->blk[0] * (p->narg-a));
memmove(&p->arg[a], &p->arg[a+1],
sizeof p->arg[0] * (p->narg-a));
}
if (bd->npred != 0) {
for (a=0; bd->pred[a]!=bs; a++)
assert(a+1<bd->npred);
bd->npred--;
memmove(&bd->pred[a], &bd->pred[a+1],
sizeof bd->pred[0] * (bd->npred-a));
for (b=f->start; b; b=b->link) {
assert(b->id < f->nblk);
for (p=b->phi; p; p=p->link) {
for (n=n0=0; n<p->narg; n++) {
bp = p->blk[n];
if (bp->id != -1u)
if (bp->s1 == b || bp->s2 == b) {
p->blk[n0] = bp;
p->arg[n0] = p->arg[n];
n0++;
}
}
assert(n0 > 0);
p->narg = n0;
}
}
}
static void
addpred(Blk *bp, Blk *bc)
addpred(Blk *bp, Blk *b)
{
if (!bc->pred) {
bc->pred = alloc(bc->npred * sizeof bc->pred[0]);
bc->visit = 0;
}
bc->pred[bc->visit++] = bp;
vgrow(&b->pred, ++b->npred);
b->pred[b->npred-1] = bp;
}
/* fill predecessors information in blocks */
void
fillpreds(Fn *f)
{
Blk *b;
for (b=f->start; b; b=b->link) {
for (b=f->start; b; b=b->link)
b->npred = 0;
b->pred = 0;
}
for (b=f->start; b; b=b->link) {
if (b->s1)
b->s1->npred++;
if (b->s2 && b->s2 != b->s1)
b->s2->npred++;
}
for (b=f->start; b; b=b->link) {
if (b->s1)
addpred(b, b->s1);
@ -76,52 +60,55 @@ fillpreds(Fn *f)
}
}
static int
rporec(Blk *b, uint x)
static void
porec(Blk *b, uint *npo)
{
Blk *s1, *s2;
if (!b || b->id != -1u)
return x;
b->id = 1;
return;
b->id = 0; /* marker */
s1 = b->s1;
s2 = b->s2;
if (s1 && s2 && s1->loop > s2->loop) {
s1 = b->s2;
s2 = b->s1;
}
x = rporec(s1, x);
x = rporec(s2, x);
b->id = x;
assert(x != -1u);
return x - 1;
porec(s1, npo);
porec(s2, npo);
b->id = (*npo)++;
}
/* fill the rpo information */
void
static void
fillrpo(Fn *f)
{
uint n;
Blk *b, **p;
for (b=f->start; b; b=b->link)
b->id = -1u;
n = 1 + rporec(f->start, f->nblk-1);
f->nblk -= n;
f->rpo = alloc(f->nblk * sizeof f->rpo[0]);
f->nblk = 0;
porec(f->start, &f->nblk);
vgrow(&f->rpo, f->nblk);
for (p=&f->start; (b=*p);) {
if (b->id == -1u) {
edgedel(b, &b->s1);
edgedel(b, &b->s2);
*p = b->link;
} else {
b->id -= n;
b->id = f->nblk-b->id-1;
f->rpo[b->id] = b;
p = &b->link;
}
}
}
/* fill rpo, preds; prune dead blks */
void
fillcfg(Fn *f)
{
fillrpo(f);
fillpreds(f);
fixphis(f);
}
/* for dominators computation, read
* "A Simple, Fast Dominance Algorithm"
* by K. Cooper, T. Harvey, and K. Kennedy.
@ -262,6 +249,50 @@ loopiter(Fn *fn, void f(Blk *, Blk *))
}
}
/* dominator tree depth */
void
filldepth(Fn *fn)
{
Blk *b, *d;
int depth;
for (b=fn->start; b; b=b->link)
b->depth = -1;
fn->start->depth = 0;
for (b=fn->start; b; b=b->link) {
if (b->depth != -1)
continue;
depth = 1;
for (d=b->idom; d->depth==-1; d=d->idom)
depth++;
depth += d->depth;
b->depth = depth;
for (d=b->idom; d->depth==-1; d=d->idom)
d->depth = --depth;
}
}
/* least common ancestor in dom tree */
Blk *
lca(Blk *b1, Blk *b2)
{
if (!b1)
return b2;
if (!b2)
return b1;
while (b1->depth > b2->depth)
b1 = b1->idom;
while (b2->depth > b1->depth)
b2 = b2->idom;
while (b1 != b2) {
b1 = b1->idom;
b2 = b2->idom;
}
return b1;
}
void
multloop(Blk *hd, Blk *b)
{
@ -329,3 +360,208 @@ simpljmp(Fn *fn)
*p = ret;
free(uf);
}
static int
reachrec(Blk *b, Blk *to)
{
if (b == to)
return 1;
if (!b || b->visit)
return 0;
b->visit = 1;
if (reachrec(b->s1, to))
return 1;
if (reachrec(b->s2, to))
return 1;
return 0;
}
/* Blk.visit needs to be clear at entry */
int
reaches(Fn *fn, Blk *b, Blk *to)
{
int r;
assert(to);
r = reachrec(b, to);
for (b=fn->start; b; b=b->link)
b->visit = 0;
return r;
}
/* can b reach 'to' not through excl
* Blk.visit needs to be clear at entry */
int
reachesnotvia(Fn *fn, Blk *b, Blk *to, Blk *excl)
{
excl->visit = 1;
return reaches(fn, b, to);
}
int
ifgraph(Blk *ifb, Blk **pthenb, Blk **pelseb, Blk **pjoinb)
{
Blk *s1, *s2, **t;
if (ifb->jmp.type != Jjnz)
return 0;
s1 = ifb->s1;
s2 = ifb->s2;
if (s1->id > s2->id) {
s1 = ifb->s2;
s2 = ifb->s1;
t = pthenb;
pthenb = pelseb;
pelseb = t;
}
if (s1 == s2)
return 0;
if (s1->jmp.type != Jjmp || s1->npred != 1)
return 0;
if (s1->s1 == s2) {
/* if-then / if-else */
if (s2->npred != 2)
return 0;
*pthenb = s1;
*pelseb = ifb;
*pjoinb = s2;
return 1;
}
if (s2->jmp.type != Jjmp || s2->npred != 1)
return 0;
if (s1->s1 != s2->s1 || s1->s1->npred != 2)
return 0;
assert(s1->s1 != ifb);
*pthenb = s1;
*pelseb = s2;
*pjoinb = s1->s1;
return 1;
}
typedef struct Jmp Jmp;
struct Jmp {
int type;
Ref arg;
Blk *s1, *s2;
};
static int
jmpeq(Jmp *a, Jmp *b)
{
return a->type == b->type && req(a->arg, b->arg)
&& a->s1 == b->s1 && a->s2 == b->s2;
}
static int
jmpnophi(Jmp *j)
{
if (j->s1 && j->s1->phi)
return 0;
if (j->s2 && j->s2->phi)
return 0;
return 1;
}
/* require cfg rpo, breaks use */
void
simplcfg(Fn *fn)
{
Ins cpy, *i;
Blk *b, *bb, **pb;
Jmp *jmp, *j, *jj;
Phi *p;
int *empty, done;
uint n;
if (debug['C']) {
fprintf(stderr, "\n> Before CFG simplification:\n");
printfn(fn, stderr);
}
cpy = (Ins){.op = Ocopy};
for (b=fn->start; b; b=b->link)
if (b->npred == 1) {
bb = b->pred[0];
for (p=b->phi; p; p=p->link) {
cpy.cls = p->cls;
cpy.to = p->to;
cpy.arg[0] = phiarg(p, bb);
addins(&bb->ins, &bb->nins, &cpy);
}
b->phi = 0;
}
jmp = emalloc(fn->nblk * sizeof jmp[0]);
empty = emalloc(fn->nblk * sizeof empty[0]);
for (b=fn->start; b; b=b->link) {
jmp[b->id].type = b->jmp.type;
jmp[b->id].arg = b->jmp.arg;
jmp[b->id].s1 = b->s1;
jmp[b->id].s2 = b->s2;
empty[b->id] = !b->phi;
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (i->op != Onop && i->op != Odbgloc) {
empty[b->id] = 0;
break;
}
}
do {
done = 1;
for (b=fn->start; b; b=b->link) {
if (b->id == -1u)
continue;
j = &jmp[b->id];
if (j->type == Jjmp && j->s1->npred == 1) {
assert(!j->s1->phi);
addbins(&b->ins, &b->nins, j->s1);
empty[b->id] &= empty[j->s1->id];
jj = &jmp[j->s1->id];
pb = (Blk*[]){jj->s1, jj->s2, 0};
for (; (bb=*pb); pb++)
for (p=bb->phi; p; p=p->link) {
n = phiargn(p, j->s1);
p->blk[n] = b;
}
j->s1->id = -1u;
*j = *jj;
done = 0;
}
else if (j->type == Jjnz
&& empty[j->s1->id] && empty[j->s2->id]
&& jmpeq(&jmp[j->s1->id], &jmp[j->s2->id])
&& jmpnophi(&jmp[j->s1->id])) {
*j = jmp[j->s1->id];
done = 0;
}
}
} while (!done);
for (b=fn->start; b; b=b->link)
if (b->id != -1u) {
j = &jmp[b->id];
b->jmp.type = j->type;
b->jmp.arg = j->arg;
b->s1 = j->s1;
b->s2 = j->s2;
assert(!j->s1 || j->s1->id != -1u);
assert(!j->s2 || j->s2->id != -1u);
}
fillcfg(fn);
free(empty);
free(jmp);
if (debug['C']) {
fprintf(stderr, "\n> After CFG simplification:\n");
printfn(fn, stderr);
}
}

577
copy.c
View File

@ -1,16 +1,272 @@
#include "all.h"
typedef struct Ext Ext;
struct Ext {
char zext;
char nopw; /* is a no-op if arg width is <= nopw */
char usew; /* uses only the low usew bits of arg */
};
static int
iscon(Ref r, int64_t bits, Fn *fn)
ext(Ins *i, Ext *e)
{
return rtype(r) == RCon
&& fn->con[r.val].type == CBits
&& fn->con[r.val].bits.i == bits;
static Ext tbl[] = {
/*extsb*/ {0, 7, 8},
/*extub*/ {1, 8, 8},
/*extsh*/ {0, 15, 16},
/*extuh*/ {1, 16, 16},
/*extsw*/ {0, 31, 32},
/*extuw*/ {1, 32, 32},
};
if (!isext(i->op))
return 0;
*e = tbl[i->op - Oextsb];
return 1;
}
static int
iscopy(Ins *i, Ref r, Fn *fn)
bitwidth(uint64_t v)
{
int n;
n = 0;
if (v >> 32) { n += 32; v >>= 32; }
if (v >> 16) { n += 16; v >>= 16; }
if (v >> 8) { n += 8; v >>= 8; }
if (v >> 4) { n += 4; v >>= 4; }
if (v >> 2) { n += 2; v >>= 2; }
if (v >> 1) { n += 1; v >>= 1; }
return n+v;
}
/* no more than w bits are used */
static int
usewidthle(Fn *fn, Ref r, int w)
{
Ext e;
Tmp *t;
Use *u;
Phi *p;
Ins *i;
Ref rc;
int64_t v;
int b;
assert(rtype(r) == RTmp);
t = &fn->tmp[r.val];
for (u=t->use; u<&t->use[t->nuse]; u++) {
switch (u->type) {
case UPhi:
p = u->u.phi;
/* during gvn, phi nodes may be
* replaced by other temps; in
* this case, the replaced phi
* uses are added to the
* replacement temp uses and
* Phi.to is set to R */
if (p->visit || req(p->to, R))
continue;
p->visit = 1;
b = usewidthle(fn, p->to, w);
p->visit = 0;
if (b)
continue;
break;
case UIns:
i = u->u.ins;
assert(i != 0);
if (i->op == Ocopy)
if (usewidthle(fn, i->to, w))
continue;
if (ext(i, &e)) {
if (e.usew <= w)
continue;
if (usewidthle(fn, i->to, w))
continue;
}
if (i->op == Oand) {
if (req(r, i->arg[0]))
rc = i->arg[1];
else {
assert(req(r, i->arg[1]));
rc = i->arg[0];
}
if (isconbits(fn, rc, &v)
&& bitwidth(v) <= w)
continue;
break;
}
break;
default:
break;
}
return 0;
}
return 1;
}
static int
min(int v1, int v2)
{
return v1 < v2 ? v1 : v2;
}
/* is the ref narrower than w bits */
static int
defwidthle(Fn *fn, Ref r, int w)
{
Ext e;
Tmp *t;
Phi *p;
Ins *i;
uint n;
int64_t v;
int x;
if (isconbits(fn, r, &v)
&& bitwidth(v) <= w)
return 1;
if (rtype(r) != RTmp)
return 0;
t = &fn->tmp[r.val];
if (t->cls != Kw)
return 0;
if (!t->def) {
/* phi def */
for (p=fn->rpo[t->bid]->phi; p; p=p->link)
if (req(p->to, r))
break;
assert(p);
if (p->visit)
return 1;
p->visit = 1;
for (n=0; n<p->narg; n++)
if (!defwidthle(fn, p->arg[n], w)) {
p->visit = 0;
return 0;
}
p->visit = 0;
return 1;
}
i = t->def;
if (i->op == Ocopy)
return defwidthle(fn, i->arg[0], w);
if (i->op == Oshr || i->op == Osar) {
if (isconbits(fn, i->arg[1], &v))
if (0 < v && v <= 32) {
if (i->op == Oshr && w+v >= 32)
return 1;
if (w < 32) {
if (i->op == Osar)
w = min(31, w+v);
else
w = min(32, w+v);
}
}
return defwidthle(fn, i->arg[0], w);
}
if (iscmp(i->op, &x, &x))
return w >= 1;
if (i->op == Oand) {
if (defwidthle(fn, i->arg[0], w)
|| defwidthle(fn, i->arg[1], w))
return 1;
return 0;
}
if (i->op == Oor || i->op == Oxor) {
if (defwidthle(fn, i->arg[0], w)
&& defwidthle(fn, i->arg[1], w))
return 1;
return 0;
}
if (ext(i, &e)) {
if (e.zext && e.usew <= w)
return 1;
w = min(w, e.nopw);
return defwidthle(fn, i->arg[0], w);
}
return 0;
}
static int
isw1(Fn *fn, Ref r)
{
return defwidthle(fn, r, 1);
}
/* insert early extub/extuh instructions
* for pars used only narrowly; this
* helps factoring extensions out of
* loops
*
* needs use; breaks use
*/
void
narrowpars(Fn *fn)
{
Blk *b;
int loop;
Ins ext, *i, *ins;
uint npar, nins;
Ref r;
/* only useful for functions with loops */
loop = 0;
for (b=fn->start; b; b=b->link)
if (b->loop > 1) {
loop = 1;
break;
}
if (!loop)
return;
b = fn->start;
npar = 0;
for (i=b->ins; i<&b->ins[b->nins]; i++) {
if (!ispar(i->op))
break;
npar++;
}
if (npar == 0)
return;
nins = b->nins + npar;
ins = vnew(nins, sizeof ins[0], PFn);
icpy(ins, b->ins, npar);
icpy(ins + 2*npar, b->ins+npar, b->nins-npar);
b->ins = ins;
b->nins = nins;
for (i=b->ins; i<&b->ins[b->nins]; i++) {
if (!ispar(i->op))
break;
ext = (Ins){.op = Onop};
if (i->cls == Kw)
if (usewidthle(fn, i->to, 16)) {
ext.op = Oextuh;
if (usewidthle(fn, i->to, 8))
ext.op = Oextub;
r = newtmp("vw", i->cls, fn);
ext.cls = i->cls;
ext.to = i->to;
ext.arg[0] = r;
i->to = r;
}
*(i+npar) = ext;
}
}
Ref
copyref(Fn *fn, Blk *b, Ins *i)
{
/* which extensions are copies for a given
* argument width */
static bits extcpy[] = {
[WFull] = 0,
[Wsb] = BIT(Wsb) | BIT(Wsh) | BIT(Wsw),
@ -20,210 +276,133 @@ iscopy(Ins *i, Ref r, Fn *fn)
[Wsw] = BIT(Wsw),
[Wuw] = BIT(Wuw),
};
bits b;
Ext e;
Tmp *t;
int64_t v;
int w, z;
switch (i->op) {
case Ocopy:
return 1;
case Omul:
case Odiv:
case Oudiv:
return iscon(i->arg[1], 1, fn);
case Oadd:
case Osub:
case Oor:
case Oxor:
case Osar:
case Oshl:
case Oshr:
return iscon(i->arg[1], 0, fn);
default:
break;
if (i->op == Ocopy)
return i->arg[0];
/* op identity value */
if (optab[i->op].hasid
&& KBASE(i->cls) == 0 /* integer only - fp NaN! */
&& req(i->arg[1], con01[optab[i->op].idval])
&& (!optab[i->op].cmpeqwl || isw1(fn, i->arg[0])))
return i->arg[0];
/* idempotent op with identical args */
if (optab[i->op].idemp
&& req(i->arg[0], i->arg[1]))
return i->arg[0];
/* integer cmp with identical args */
if ((optab[i->op].cmpeqwl || optab[i->op].cmplgtewl)
&& req(i->arg[0], i->arg[1]))
return con01[optab[i->op].eqval];
/* cmpeq/ne 0 with 0/non-0 inference */
if (optab[i->op].cmpeqwl
&& req(i->arg[1], CON_Z)
&& zeroval(fn, b, i->arg[0], argcls(i, 0), &z))
return con01[optab[i->op].eqval^z^1];
/* redundant and mask */
if (i->op == Oand
&& isconbits(fn, i->arg[1], &v)
&& (v > 0 && ((v+1) & v) == 0)
&& defwidthle(fn, i->arg[0], bitwidth(v)))
return i->arg[0];
if (i->cls == Kw
&& (i->op == Oextsw || i->op == Oextuw))
return i->arg[0];
if (ext(i, &e) && rtype(i->arg[0]) == RTmp) {
t = &fn->tmp[i->arg[0].val];
assert(KBASE(t->cls) == 0);
/* do not break typing by returning
* a narrower temp */
if (KWIDE(i->cls) > KWIDE(t->cls))
return R;
w = Wsb + (i->op - Oextsb);
if (BIT(w) & extcpy[t->width])
return i->arg[0];
/* avoid eliding extensions of params
* inserted in the start block; their
* point is to make further extensions
* redundant */
if ((!t->def || !ispar(t->def->op))
&& usewidthle(fn, i->to, e.usew))
return i->arg[0];
if (defwidthle(fn, i->arg[0], e.nopw))
return i->arg[0];
}
if (!isext(i->op) || rtype(r) != RTmp)
return 0;
if (i->op == Oextsw || i->op == Oextuw)
if (i->cls == Kw)
return 1;
t = &fn->tmp[r.val];
assert(KBASE(t->cls) == 0);
if (i->cls == Kl && t->cls == Kw)
return 0;
b = extcpy[t->width];
return (BIT(Wsb + (i->op-Oextsb)) & b) != 0;
return R;
}
static Ref
copyof(Ref r, Ref *cpy)
static int
phieq(Phi *pa, Phi *pb)
{
if (rtype(r) == RTmp && !req(cpy[r.val], R))
return cpy[r.val];
return r;
Ref r;
uint n;
assert(pa->narg == pb->narg);
for (n=0; n<pa->narg; n++) {
r = phiarg(pb, pa->blk[n]);
if (!req(pa->arg[n], r))
return 0;
}
return 1;
}
/* detects a cluster of phis/copies redundant with 'r';
* the algorithm is inspired by Section 3.2 of "Simple
* and Efficient SSA Construction" by Braun M. et al.
*/
static void
phisimpl(Phi *p, Ref r, Ref *cpy, Use ***pstk, BSet *ts, BSet *as, Fn *fn)
Ref
phicopyref(Fn *fn, Blk *b, Phi *p)
{
Use **stk, *u, *u1;
uint nstk, a;
int t;
Ref r1;
Phi *p0;
Blk *d, **s;
Phi *p1;
uint n, c;
bszero(ts);
bszero(as);
p0 = &(Phi){.narg = 0};
stk = *pstk;
nstk = 1;
stk[0] = &(Use){.type = UPhi, .u.phi = p};
while (nstk) {
u = stk[--nstk];
if (u->type == UIns && iscopy(u->u.ins, r, fn)) {
p = p0;
t = u->u.ins->to.val;
}
else if (u->type == UPhi) {
p = u->u.phi;
t = p->to.val;
}
else
continue;
if (bshas(ts, t))
continue;
bsset(ts, t);
for (a=0; a<p->narg; a++) {
r1 = copyof(p->arg[a], cpy);
if (req(r1, r))
continue;
if (rtype(r1) != RTmp)
return;
bsset(as, r1.val);
}
u = fn->tmp[t].use;
u1 = &u[fn->tmp[t].nuse];
vgrow(pstk, nstk+(u1-u));
stk = *pstk;
for (; u<u1; u++)
stk[nstk++] = u;
/* identical args */
for (n=0; n<p->narg-1; n++)
if (!req(p->arg[n], p->arg[n+1]))
break;
if (n == p->narg-1)
return p->arg[n];
/* same as a previous phi */
for (p1=b->phi; p1!=p; p1=p1->link) {
assert(p1);
if (phieq(p1, p))
return p1->to;
}
bsdiff(as, ts);
if (!bscount(as))
for (t=0; bsiter(ts, &t); t++)
cpy[t] = r;
}
static void
subst(Ref *pr, Ref *cpy)
{
assert(rtype(*pr) != RTmp || !req(cpy[pr->val], R));
*pr = copyof(*pr, cpy);
}
/* requires use and dom, breaks use */
void
copy(Fn *fn)
{
BSet ts[1], as[1];
Use **stk;
Phi *p, **pp;
Ins *i;
Blk *b;
uint n, a, eq;
Ref *cpy, r, r1;
int t;
bsinit(ts, fn->ntmp);
bsinit(as, fn->ntmp);
cpy = emalloc(fn->ntmp * sizeof cpy[0]);
stk = vnew(10, sizeof stk[0], PHeap);
/* 1. build the copy-of map */
for (n=0; n<fn->nblk; n++) {
b = fn->rpo[n];
for (p=b->phi; p; p=p->link) {
assert(rtype(p->to) == RTmp);
if (!req(cpy[p->to.val], R))
continue;
eq = 0;
r = R;
for (a=0; a<p->narg; a++)
if (p->blk[a]->id < n) {
r1 = copyof(p->arg[a], cpy);
if (req(r, R) || req(r, UNDEF))
r = r1;
if (req(r1, r) || req(r1, UNDEF))
eq++;
}
assert(!req(r, R));
if (rtype(r) == RTmp
&& !dom(fn->rpo[fn->tmp[r.val].bid], b))
cpy[p->to.val] = p->to;
else if (eq == p->narg)
cpy[p->to.val] = r;
else {
cpy[p->to.val] = p->to;
phisimpl(p, r, cpy, &stk, ts, as, fn);
}
}
for (i=b->ins; i<&b->ins[b->nins]; i++) {
assert(rtype(i->to) <= RTmp);
if (!req(cpy[i->to.val], R))
continue;
r = copyof(i->arg[0], cpy);
if (iscopy(i, r, fn))
cpy[i->to.val] = r;
else
cpy[i->to.val] = i->to;
}
}
/* 2. remove redundant phis/copies
* and rewrite their uses */
for (b=fn->start; b; b=b->link) {
for (pp=&b->phi; (p=*pp);) {
r = cpy[p->to.val];
if (!req(r, p->to)) {
*pp = p->link;
continue;
}
for (a=0; a<p->narg; a++)
subst(&p->arg[a], cpy);
pp=&p->link;
}
for (i=b->ins; i<&b->ins[b->nins]; i++) {
r = cpy[i->to.val];
if (!req(r, i->to)) {
*i = (Ins){.op = Onop};
continue;
}
subst(&i->arg[0], cpy);
subst(&i->arg[1], cpy);
}
subst(&b->jmp.arg, cpy);
}
if (debug['C']) {
fprintf(stderr, "\n> Copy information:");
for (t=Tmp0; t<fn->ntmp; t++) {
if (req(cpy[t], R)) {
fprintf(stderr, "\n%10s not seen!",
fn->tmp[t].name);
}
else if (!req(cpy[t], TMP(t))) {
fprintf(stderr, "\n%10s copy of ",
fn->tmp[t].name);
printref(cpy[t], fn, stderr);
}
}
fprintf(stderr, "\n\n> After copy elimination:\n");
printfn(fn, stderr);
}
vfree(stk);
free(cpy);
/* can be replaced by a
* dominating jnz arg */
d = b->idom;
if (p->narg != 2
|| d->jmp.type != Jjnz
|| !isw1(fn, d->jmp.arg))
return R;
s = (Blk*[]){0, 0};
for (n=0; n<2; n++)
for (c=0; c<2; c++)
if (req(p->arg[n], con01[c]))
s[c] = p->blk[n];
/* if s1 ends with a jnz on either b
* or s2; the inference below is wrong
* without the jump type checks */
if (d->s1 == s[1] && d->s2 == s[0]
&& d->s1->jmp.type == Jjmp
&& d->s2->jmp.type == Jjmp)
return d->jmp.arg;
return R;
}

View File

@ -439,7 +439,7 @@ return type of the function. All return values of this
function must have this return type. If the return
type is missing, the function must not return any value.
The parameter list is a comma separated list of
The parameter list is a comma separated list of distinct
temporary names prefixed by types. The types are used
to correctly implement C compatibility. When an argument
has an aggregate type, a pointer to the aggregate is passed
@ -976,6 +976,7 @@ is possible to conservatively use the maximum size and
alignment required by all the targets.
type :valist = align 8 { 24 } # For amd64_sysv
type :valist = align 8 { 8 } # For amd64_win
type :valist = align 8 { 32 } # For arm64
type :valist = align 8 { 8 } # For rv64

15
doc/native_win.txt Normal file
View File

@ -0,0 +1,15 @@
There is an experimental amd64_win (native Windows ABI and calling
convention).
In tree, this is currently only tested via cross-compilation from a
Linux host, and using wine to run the tests.
You'll need something like:
sudo apt install mingw64-w64 dos2unix wine
and then
make check-amd64_win
should pass.

79
emit.c
View File

@ -61,11 +61,14 @@ emitfnlnk(char *n, Lnk *l, FILE *f)
void
emitdat(Dat *d, FILE *f)
{
static char *dtoa[] = {
[DB] = "\t.byte",
[DH] = "\t.short",
[DW] = "\t.int",
[DL] = "\t.quad"
static struct {
char decl[8];
int64_t mask;
} di[] = {
[DB] = {"\t.byte", 0xffL},
[DH] = {"\t.short", 0xffffL},
[DW] = {"\t.int", 0xffffffffL},
[DL] = {"\t.quad", -1L},
};
static int64_t zero;
char *p;
@ -75,7 +78,17 @@ emitdat(Dat *d, FILE *f)
zero = 0;
break;
case DEnd:
if (zero != -1) {
if (d->lnk->common) {
if (zero == -1)
die("invalid common data definition");
p = d->name[0] == '"' ? "" : T.assym;
fprintf(f, ".comm %s%s,%"PRId64,
p, d->name, zero);
if (d->lnk->align)
fprintf(f, ",%d", d->lnk->align);
fputc('\n', f);
}
else if (zero != -1) {
emitlnk(d->name, d->lnk, SecBss, f);
fprintf(f, "\t.fill %"PRId64",1,0\n", zero);
}
@ -101,12 +114,13 @@ emitdat(Dat *d, FILE *f)
else if (d->isref) {
p = d->u.ref.name[0] == '"' ? "" : T.assym;
fprintf(f, "%s %s%s%+"PRId64"\n",
dtoa[d->type], p, d->u.ref.name,
di[d->type].decl, p, d->u.ref.name,
d->u.ref.off);
}
else {
fprintf(f, "%s %"PRId64"\n",
dtoa[d->type], d->u.num);
di[d->type].decl,
d->u.num & di[d->type].mask);
}
break;
}
@ -115,7 +129,7 @@ emitdat(Dat *d, FILE *f)
typedef struct Asmbits Asmbits;
struct Asmbits {
char bits[16];
bits n;
int size;
Asmbits *link;
};
@ -123,18 +137,17 @@ struct Asmbits {
static Asmbits *stash;
int
stashbits(void *bits, int size)
stashbits(bits n, int size)
{
Asmbits **pb, *b;
int i;
assert(size == 4 || size == 8 || size == 16);
for (pb=&stash, i=0; (b=*pb); pb=&b->link, i++)
if (size <= b->size)
if (memcmp(bits, b->bits, size) == 0)
if (size <= b->size && b->n == n)
return i;
b = emalloc(sizeof *b);
memcpy(b->bits, bits, size);
b->n = n;
b->size = size;
b->link = 0;
*pb = b;
@ -145,9 +158,8 @@ static void
emitfin(FILE *f, char *sec[3])
{
Asmbits *b;
char *p;
int lg, i;
double d;
union { int32_t i; float f; } u;
if (!stash)
return;
@ -161,17 +173,24 @@ emitfin(FILE *f, char *sec[3])
"%sfp%d:",
sec[lg-2], lg, T.asloc, i
);
for (p=b->bits; p<&b->bits[b->size]; p+=4)
fprintf(f, "\n\t.int %"PRId32,
*(int32_t *)p);
if (lg <= 3) {
if (lg == 2)
d = *(float *)b->bits;
else
d = *(double *)b->bits;
fprintf(f, " /* %f */\n\n", d);
} else
fprintf(f, "\n\n");
if (lg == 4)
fprintf(f,
"\n\t.quad %"PRId64
"\n\t.quad 0\n\n",
(int64_t)b->n);
else if (lg == 3)
fprintf(f,
"\n\t.quad %"PRId64
" /* %f */\n\n",
(int64_t)b->n,
*(double *)&b->n);
else if (lg == 2) {
u.i = b->n;
fprintf(f,
"\n\t.int %"PRId32
" /* %f */\n\n",
u.i, (double)u.f);
}
}
}
while ((b=stash)) {
@ -208,6 +227,14 @@ macho_emitfin(FILE *f)
emitfin(f, sec);
}
void
pe_emitfin(FILE *f)
{
static char *sec[3] = { ".rodata", ".rodata", ".rodata" };
emitfin(f, sec);
}
static uint32_t *file;
static uint nfile;
static uint curfile;

349
fold.c
View File

@ -1,22 +1,6 @@
#include "all.h"
enum {
Bot = -1, /* lattice bottom */
Top = 0, /* lattice top (matches UNDEF) */
};
typedef struct Edge Edge;
struct Edge {
int dest;
int dead;
Edge *work;
};
static int *val;
static Edge *flowrk, (*edge)[2];
static Use **usewrk;
static uint nuse;
/* boring folding code */
static int
iscon(Con *c, int w, uint64_t k)
@ -29,305 +13,7 @@ iscon(Con *c, int w, uint64_t k)
return (uint32_t)c->bits.i == (uint32_t)k;
}
static int
latval(Ref r)
{
switch (rtype(r)) {
case RTmp:
return val[r.val];
case RCon:
return r.val;
default:
die("unreachable");
}
}
static int
latmerge(int v, int m)
{
return m == Top ? v : (v == Top || v == m) ? m : Bot;
}
static void
update(int t, int m, Fn *fn)
{
Tmp *tmp;
uint u;
m = latmerge(val[t], m);
if (m != val[t]) {
tmp = &fn->tmp[t];
for (u=0; u<tmp->nuse; u++) {
vgrow(&usewrk, ++nuse);
usewrk[nuse-1] = &tmp->use[u];
}
val[t] = m;
}
}
static int
deadedge(int s, int d)
{
Edge *e;
e = edge[s];
if (e[0].dest == d && !e[0].dead)
return 0;
if (e[1].dest == d && !e[1].dead)
return 0;
return 1;
}
static void
visitphi(Phi *p, int n, Fn *fn)
{
int v;
uint a;
v = Top;
for (a=0; a<p->narg; a++)
if (!deadedge(p->blk[a]->id, n))
v = latmerge(v, latval(p->arg[a]));
update(p->to.val, v, fn);
}
static int opfold(int, int, Con *, Con *, Fn *);
static void
visitins(Ins *i, Fn *fn)
{
int v, l, r;
if (rtype(i->to) != RTmp)
return;
if (optab[i->op].canfold) {
l = latval(i->arg[0]);
if (!req(i->arg[1], R))
r = latval(i->arg[1]);
else
r = CON_Z.val;
if (l == Bot || r == Bot)
v = Bot;
else if (l == Top || r == Top)
v = Top;
else
v = opfold(i->op, i->cls, &fn->con[l], &fn->con[r], fn);
} else
v = Bot;
/* fprintf(stderr, "\nvisiting %s (%p)", optab[i->op].name, (void *)i); */
update(i->to.val, v, fn);
}
static void
visitjmp(Blk *b, int n, Fn *fn)
{
int l;
switch (b->jmp.type) {
case Jjnz:
l = latval(b->jmp.arg);
if (l == Bot) {
edge[n][1].work = flowrk;
edge[n][0].work = &edge[n][1];
flowrk = &edge[n][0];
}
else if (iscon(&fn->con[l], 0, 0)) {
assert(edge[n][0].dead);
edge[n][1].work = flowrk;
flowrk = &edge[n][1];
}
else {
assert(edge[n][1].dead);
edge[n][0].work = flowrk;
flowrk = &edge[n][0];
}
break;
case Jjmp:
edge[n][0].work = flowrk;
flowrk = &edge[n][0];
break;
case Jhlt:
break;
default:
if (isret(b->jmp.type))
break;
die("unreachable");
}
}
static void
initedge(Edge *e, Blk *s)
{
if (s)
e->dest = s->id;
else
e->dest = -1;
e->dead = 1;
e->work = 0;
}
static int
renref(Ref *r)
{
int l;
if (rtype(*r) == RTmp)
if ((l=val[r->val]) != Bot) {
*r = CON(l);
return 1;
}
return 0;
}
/* require rpo, use, pred */
void
fold(Fn *fn)
{
Edge *e, start;
Use *u;
Blk *b, **pb;
Phi *p, **pp;
Ins *i;
int t, d;
uint n, a;
val = emalloc(fn->ntmp * sizeof val[0]);
edge = emalloc(fn->nblk * sizeof edge[0]);
usewrk = vnew(0, sizeof usewrk[0], PHeap);
for (t=0; t<fn->ntmp; t++)
val[t] = Top;
for (n=0; n<fn->nblk; n++) {
b = fn->rpo[n];
b->visit = 0;
initedge(&edge[n][0], b->s1);
initedge(&edge[n][1], b->s2);
}
initedge(&start, fn->start);
flowrk = &start;
nuse = 0;
/* 1. find out constants and dead cfg edges */
for (;;) {
e = flowrk;
if (e) {
flowrk = e->work;
e->work = 0;
if (e->dest == -1 || !e->dead)
continue;
e->dead = 0;
n = e->dest;
b = fn->rpo[n];
for (p=b->phi; p; p=p->link)
visitphi(p, n, fn);
if (b->visit == 0) {
for (i=b->ins; i<&b->ins[b->nins]; i++)
visitins(i, fn);
visitjmp(b, n, fn);
}
b->visit++;
assert(b->jmp.type != Jjmp
|| !edge[n][0].dead
|| flowrk == &edge[n][0]);
}
else if (nuse) {
u = usewrk[--nuse];
n = u->bid;
b = fn->rpo[n];
if (b->visit == 0)
continue;
switch (u->type) {
case UPhi:
visitphi(u->u.phi, u->bid, fn);
break;
case UIns:
visitins(u->u.ins, fn);
break;
case UJmp:
visitjmp(b, n, fn);
break;
default:
die("unreachable");
}
}
else
break;
}
if (debug['F']) {
fprintf(stderr, "\n> SCCP findings:");
for (t=Tmp0; t<fn->ntmp; t++) {
if (val[t] == Bot)
continue;
fprintf(stderr, "\n%10s: ", fn->tmp[t].name);
if (val[t] == Top)
fprintf(stderr, "Top");
else
printref(CON(val[t]), fn, stderr);
}
fprintf(stderr, "\n dead code: ");
}
/* 2. trim dead code, replace constants */
d = 0;
for (pb=&fn->start; (b=*pb);) {
if (b->visit == 0) {
d = 1;
if (debug['F'])
fprintf(stderr, "%s ", b->name);
edgedel(b, &b->s1);
edgedel(b, &b->s2);
*pb = b->link;
continue;
}
for (pp=&b->phi; (p=*pp);)
if (val[p->to.val] != Bot)
*pp = p->link;
else {
for (a=0; a<p->narg; a++)
if (!deadedge(p->blk[a]->id, b->id))
renref(&p->arg[a]);
pp = &p->link;
}
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (renref(&i->to))
*i = (Ins){.op = Onop};
else {
for (n=0; n<2; n++)
renref(&i->arg[n]);
if (isstore(i->op))
if (req(i->arg[0], UNDEF))
*i = (Ins){.op = Onop};
}
renref(&b->jmp.arg);
if (b->jmp.type == Jjnz && rtype(b->jmp.arg) == RCon) {
if (iscon(&fn->con[b->jmp.arg.val], 0, 0)) {
edgedel(b, &b->s1);
b->s1 = b->s2;
b->s2 = 0;
} else
edgedel(b, &b->s2);
b->jmp.type = Jjmp;
b->jmp.arg = R;
}
pb = &b->link;
}
if (debug['F']) {
if (!d)
fprintf(stderr, "(none)");
fprintf(stderr, "\n\n> After constant folding:\n");
printfn(fn, stderr);
}
free(val);
free(edge);
vfree(usewrk);
}
/* boring folding code */
static int
int
foldint(Con *res, int op, int w, Con *cl, Con *cr)
{
union {
@ -516,7 +202,7 @@ foldflt(Con *res, int op, int w, Con *cl, Con *cr)
}
}
static int
static Ref
opfold(int op, int cls, Con *cl, Con *cr, Fn *fn)
{
Ref r;
@ -524,12 +210,37 @@ opfold(int op, int cls, Con *cl, Con *cr, Fn *fn)
if (cls == Kw || cls == Kl) {
if (foldint(&c, op, cls == Kl, cl, cr))
return Bot;
return R;
} else
foldflt(&c, op, cls == Kd, cl, cr);
if (!KWIDE(cls))
c.bits.i &= 0xffffffff;
r = newcon(&c, fn);
assert(!(cls == Ks || cls == Kd) || c.flt);
return r.val;
return r;
}
/* used by GVN */
Ref
foldref(Fn *fn, Ins *i)
{
Ref rr;
Con *cl, *cr;
if (rtype(i->to) != RTmp)
return R;
if (optab[i->op].canfold) {
if (rtype(i->arg[0]) != RCon)
return R;
cl = &fn->con[i->arg[0].val];
rr = i->arg[1];
if (req(rr, R))
rr = CON_Z;
if (rtype(rr) != RCon)
return R;
cr = &fn->con[rr.val];
return opfold(i->op, i->cls, cl, cr, fn);
}
return R;
}

460
gcm.c Normal file
View File

@ -0,0 +1,460 @@
#include "all.h"
#define NOBID (-1u)
static int
isdivwl(Ins *i)
{
switch (i->op) {
case Odiv:
case Orem:
case Oudiv:
case Ourem:
return KBASE(i->cls) == 0;
default:
return 0;
}
}
int
pinned(Ins *i)
{
return optab[i->op].pinned || isdivwl(i);
}
/* pinned ins that can be eliminated if unused */
static int
canelim(Ins *i)
{
return isload(i->op) || isalloc(i->op) || isdivwl(i);
}
static uint earlyins(Fn *, Blk *, Ins *);
static uint
schedearly(Fn *fn, Ref r)
{
Tmp *t;
Blk *b;
if (rtype(r) != RTmp)
return 0;
t = &fn->tmp[r.val];
if (t->gcmbid != NOBID)
return t->gcmbid;
b = fn->rpo[t->bid];
if (t->def) {
assert(b->ins <= t->def && t->def < &b->ins[b->nins]);
t->gcmbid = 0; /* mark as visiting */
t->gcmbid = earlyins(fn, b, t->def);
} else {
/* phis do not move */
t->gcmbid = t->bid;
}
return t->gcmbid;
}
static uint
earlyins(Fn *fn, Blk *b, Ins *i)
{
uint b0, b1;
b0 = schedearly(fn, i->arg[0]);
assert(b0 != NOBID);
b1 = schedearly(fn, i->arg[1]);
assert(b1 != NOBID);
if (fn->rpo[b0]->depth < fn->rpo[b1]->depth) {
assert(dom(fn->rpo[b0], fn->rpo[b1]));
b0 = b1;
}
return pinned(i) ? b->id : b0;
}
static void
earlyblk(Fn *fn, uint bid)
{
Blk *b;
Phi *p;
Ins *i;
uint n;
b = fn->rpo[bid];
for (p=b->phi; p; p=p->link)
for (n=0; n<p->narg; n++)
schedearly(fn, p->arg[n]);
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (pinned(i)) {
schedearly(fn, i->arg[0]);
schedearly(fn, i->arg[1]);
}
schedearly(fn, b->jmp.arg);
}
/* least common ancestor in dom tree */
static uint
lcabid(Fn *fn, uint bid1, uint bid2)
{
Blk *b;
if (bid1 == NOBID)
return bid2;
if (bid2 == NOBID)
return bid1;
b = lca(fn->rpo[bid1], fn->rpo[bid2]);
assert(b);
return b->id;
}
static uint
bestbid(Fn *fn, uint earlybid, uint latebid)
{
Blk *curb, *earlyb, *bestb;
if (latebid == NOBID)
return NOBID; /* unused */
assert(earlybid != NOBID);
earlyb = fn->rpo[earlybid];
bestb = curb = fn->rpo[latebid];
assert(dom(earlyb, curb));
while (curb != earlyb) {
curb = curb->idom;
if (curb->loop < bestb->loop)
bestb = curb;
}
return bestb->id;
}
static uint lateins(Fn *, Blk *, Ins *, Ref r);
static uint latephi(Fn *, Phi *, Ref r);
static uint latejmp(Blk *, Ref r);
/* return lca bid of ref uses */
static uint
schedlate(Fn *fn, Ref r)
{
Tmp *t;
Blk *b;
Use *u;
uint earlybid;
uint latebid;
uint uselatebid;
if (rtype(r) != RTmp)
return NOBID;
t = &fn->tmp[r.val];
if (t->visit)
return t->gcmbid;
t->visit = 1;
earlybid = t->gcmbid;
if (earlybid == NOBID)
return NOBID; /* not used */
/* reuse gcmbid for late bid */
t->gcmbid = t->bid;
latebid = NOBID;
for (u=t->use; u<&t->use[t->nuse]; u++) {
assert(u->bid < fn->nblk);
b = fn->rpo[u->bid];
switch (u->type) {
case UXXX:
die("unreachable");
break;
case UPhi:
uselatebid = latephi(fn, u->u.phi, r);
break;
case UIns:
uselatebid = lateins(fn, b, u->u.ins, r);
break;
case UJmp:
uselatebid = latejmp(b, r);
break;
}
latebid = lcabid(fn, latebid, uselatebid);
}
/* latebid may be NOBID if the temp is used
* in fixed instructions that may be eliminated
* and are themselves unused transitively */
if (t->def && !pinned(t->def))
t->gcmbid = bestbid(fn, earlybid, latebid);
/* else, keep the early one */
/* now, gcmbid is the best bid */
return t->gcmbid;
}
/* returns lca bid of uses or NOBID if
* the definition can be eliminated */
static uint
lateins(Fn *fn, Blk *b, Ins *i, Ref r)
{
uint latebid;
assert(b->ins <= i && i < &b->ins[b->nins]);
assert(req(i->arg[0], r) || req(i->arg[1], r));
latebid = schedlate(fn, i->to);
if (pinned(i)) {
if (latebid == NOBID)
if (canelim(i))
return NOBID;
return b->id;
}
return latebid;
}
static uint
latephi(Fn *fn, Phi *p, Ref r)
{
uint n;
uint latebid;
if (!p->narg)
return NOBID; /* marked as unused */
latebid = NOBID;
for (n = 0; n < p->narg; n++)
if (req(p->arg[n], r))
latebid = lcabid(fn, latebid, p->blk[n]->id);
assert(latebid != NOBID);
return latebid;
}
static uint
latejmp(Blk *b, Ref r)
{
if (req(b->jmp.arg, R))
return NOBID;
else {
assert(req(b->jmp.arg, r));
return b->id;
}
}
static void
lateblk(Fn *fn, uint bid)
{
Blk *b;
Phi **pp;
Ins *i;
b = fn->rpo[bid];
for (pp=&b->phi; *(pp);)
if (schedlate(fn, (*pp)->to) == NOBID) {
(*pp)->narg = 0; /* mark unused */
*pp = (*pp)->link; /* remove phi */
} else
pp = &(*pp)->link;
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (pinned(i))
schedlate(fn, i->to);
}
static void
addgcmins(Fn *fn, Ins *vins, uint nins)
{
Ins *i;
Tmp *t;
Blk *b;
for (i=vins; i<&vins[nins]; i++) {
assert(rtype(i->to) == RTmp);
t = &fn->tmp[i->to.val];
b = fn->rpo[t->gcmbid];
addins(&b->ins, &b->nins, i);
}
}
/* move live instructions to the
* end of their target block; use-
* before-def errors are fixed by
* schedblk */
static void
gcmmove(Fn *fn)
{
Tmp *t;
Ins *vins, *i;
uint nins;
nins = 0;
vins = vnew(nins, sizeof vins[0], PFn);
for (t=fn->tmp; t<&fn->tmp[fn->ntmp]; t++) {
if (t->def == 0)
continue;
if (t->bid == t->gcmbid)
continue;
i = t->def;
if (pinned(i) && !canelim(i))
continue;
assert(rtype(i->to) == RTmp);
assert(t == &fn->tmp[i->to.val]);
if (t->gcmbid != NOBID)
addins(&vins, &nins, i);
*i = (Ins){.op = Onop};
}
addgcmins(fn, vins, nins);
}
/* dfs ordering */
static Ins *
schedins(Fn *fn, Blk *b, Ins *i, Ins **pvins, uint *pnins)
{
Ins *i0, *i1;
Tmp *t;
uint n;
igroup(b, i, &i0, &i1);
for (i=i0; i<i1; i++)
for (n=0; n<2; n++) {
if (rtype(i->arg[n]) != RTmp)
continue;
t = &fn->tmp[i->arg[n].val];
if (t->bid != b->id || !t->def)
continue;
schedins(fn, b, t->def, pvins, pnins);
}
for (i=i0; i<i1; i++) {
addins(pvins, pnins, i);
*i = (Ins){.op = Onop};
}
return i1;
}
/* order ins within a block */
static void
schedblk(Fn *fn)
{
Blk *b;
Ins *i, *vins;
uint nins;
vins = vnew(0, sizeof vins[0], PHeap);
for (b=fn->start; b; b=b->link) {
nins = 0;
for (i=b->ins; i<&b->ins[b->nins];)
i = schedins(fn, b, i, &vins, &nins);
idup(b, vins, nins);
}
vfree(vins);
}
static int
cheap(Ins *i)
{
int x;
if (KBASE(i->cls) != 0)
return 0;
switch (i->op) {
case Oneg:
case Oadd:
case Osub:
case Omul:
case Oand:
case Oor:
case Oxor:
case Osar:
case Oshr:
case Oshl:
return 1;
default:
return iscmp(i->op, &x, &x);
}
}
static void
sinkref(Fn *fn, Blk *b, Ref *pr)
{
Ins i;
Tmp *t;
Ref r;
if (rtype(*pr) != RTmp)
return;
t = &fn->tmp[pr->val];
if (!t->def
|| t->bid == b->id
|| pinned(t->def)
|| !cheap(t->def))
return;
/* sink t->def to b */
i = *t->def;
r = newtmp("snk", t->cls, fn);
t = 0; /* invalidated */
*pr = r;
i.to = r;
fn->tmp[r.val].gcmbid = b->id;
emiti(i);
sinkref(fn, b, &i.arg[0]);
sinkref(fn, b, &i.arg[1]);
}
/* redistribute trivial ops to point of
* use to reduce register pressure
* requires rpo, use; breaks use
*/
static void
sink(Fn *fn)
{
Blk *b;
Ins *i;
for (b=fn->start; b; b=b->link) {
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (isload(i->op))
sinkref(fn, b, &i->arg[0]);
else if (isstore(i->op))
sinkref(fn, b, &i->arg[1]);
sinkref(fn, b, &b->jmp.arg);
}
addgcmins(fn, curi, &insb[NIns] - curi);
}
/* requires use dom
* maintains rpo pred dom
* breaks use
*/
void
gcm(Fn *fn)
{
Tmp *t;
uint bid;
filldepth(fn);
fillloop(fn);
for (t=fn->tmp; t<&fn->tmp[fn->ntmp]; t++) {
t->visit = 0;
t->gcmbid = NOBID;
}
for (bid=0; bid<fn->nblk; bid++)
earlyblk(fn, bid);
for (bid=0; bid<fn->nblk; bid++)
lateblk(fn, bid);
gcmmove(fn);
filluse(fn);
curi = &insb[NIns];
sink(fn);
filluse(fn);
schedblk(fn);
if (debug['G']) {
fprintf(stderr, "\n> After GCM:\n");
printfn(fn, stderr);
}
}

508
gvn.c Normal file
View File

@ -0,0 +1,508 @@
#include "all.h"
Ref con01[2];
static inline uint
mix(uint x0, uint x1)
{
return x0 + 17*x1;
}
static inline uint
rhash(Ref r)
{
return mix(r.type, r.val);
}
static uint
ihash(Ins *i)
{
uint h;
h = mix(i->op, i->cls);
h = mix(h, rhash(i->arg[0]));
h = mix(h, rhash(i->arg[1]));
return h;
}
static int
ieq(Ins *ia, Ins *ib)
{
if (ia->op == ib->op)
if (ia->cls == ib->cls)
if (req(ia->arg[0], ib->arg[0]))
if (req(ia->arg[1], ib->arg[1]))
return 1;
return 0;
}
static Ins **gvntbl;
static uint gvntbln;
static Ins *
gvndup(Ins *i, int insert)
{
uint idx;
Ins *ii;
idx = ihash(i) % gvntbln;
for (;;) {
ii = gvntbl[idx];
if (!ii)
break;
if (ieq(i, ii))
return ii;
idx++;
if (gvntbln <= idx)
idx = 0;
}
if (insert)
gvntbl[idx] = i;
return 0;
}
static void
replaceuse(Fn *fn, Use *u, Ref r1, Ref r2)
{
Blk *b;
Ins *i;
Phi *p;
Ref *pr;
Tmp *t2;
int n;
t2 = 0;
if (rtype(r2) == RTmp)
t2 = &fn->tmp[r2.val];
b = fn->rpo[u->bid];
switch (u->type) {
case UPhi:
p = u->u.phi;
for (pr=p->arg; pr<&p->arg[p->narg]; pr++)
if (req(*pr, r1))
*pr = r2;
if (t2)
adduse(t2, UPhi, b, p);
break;
case UIns:
i = u->u.ins;
for (n=0; n<2; n++)
if (req(i->arg[n], r1))
i->arg[n] = r2;
if (t2)
adduse(t2, UIns, b, i);
break;
case UJmp:
if (req(b->jmp.arg, r1))
b->jmp.arg = r2;
if (t2)
adduse(t2, UJmp, b);
break;
case UXXX:
die("unreachable");
}
}
static void
replaceuses(Fn *fn, Ref r1, Ref r2)
{
Tmp *t1;
Use *u;
assert(rtype(r1) == RTmp);
t1 = &fn->tmp[r1.val];
for (u=t1->use; u<&t1->use[t1->nuse]; u++)
replaceuse(fn, u, r1, r2);
t1->nuse = 0;
}
static void
dedupphi(Fn *fn, Blk *b)
{
Phi *p, **pp;
Ref r;
for (pp=&b->phi; (p=*pp);) {
r = phicopyref(fn, b, p);
if (!req(r, R)) {
replaceuses(fn, p->to, r);
p->to = R;
*pp = p->link;
} else
pp = &p->link;
}
}
static int
rcmp(Ref a, Ref b)
{
if (rtype(a) != rtype(b))
return rtype(a) - rtype(b);
return a.val - b.val;
}
static void
normins(Fn *fn, Ins *i)
{
uint n;
int64_t v;
Ref r;
/* truncate constant bits to
* 32 bits for s/w uses */
for (n=0; n<2; n++) {
if (!KWIDE(argcls(i, n)))
if (isconbits(fn, i->arg[n], &v))
if ((v & 0xffffffff) != v)
i->arg[n] = getcon(v & 0xffffffff, fn);
}
/* order arg[0] <= arg[1] for
* commutative ops, preferring
* RTmp in arg[0] */
if (optab[i->op].commutes)
if (rcmp(i->arg[0], i->arg[1]) > 0) {
r = i->arg[1];
i->arg[1] = i->arg[0];
i->arg[0] = r;
}
}
static int
negcon(int cls, Con *c)
{
static Con z = {.type = CBits, .bits.i = 0};
return foldint(c, Osub, cls, &z, c);
}
static void
assoccon(Fn *fn, Blk *b, Ins *i1)
{
Tmp *t2;
Ins *i2;
int op, fail;
Con c, c1, c2;
op = i1->op;
if (op == Osub)
op = Oadd;
if (!optab[op].assoc
|| KBASE(i1->cls) != 0
|| rtype(i1->arg[0]) != RTmp
|| rtype(i1->arg[1]) != RCon)
return;
c1 = fn->con[i1->arg[1].val];
t2 = &fn->tmp[i1->arg[0].val];
if (t2->def == 0)
return;
i2 = t2->def;
if (op != (i2->op == Osub ? Oadd : i2->op)
|| rtype(i2->arg[1]) != RCon)
return;
c2 = fn->con[i2->arg[1].val];
assert(KBASE(i2->cls) == 0);
assert(KWIDE(i2->cls) >= KWIDE(i1->cls));
if (i1->op == Osub && negcon(i1->cls, &c1))
return;
if (i2->op == Osub && negcon(i2->cls, &c2))
return;
if (foldint(&c, op, i1->cls, &c1, &c2))
return;
if (op == Oadd && c.type == CBits)
if ((i1->cls == Kl && c.bits.i < 0)
|| (i1->cls == Kw && (int32_t)c.bits.i < 0)) {
fail = negcon(i1->cls, &c);
assert(fail == 0);
op = Osub;
}
i1->op = op;
i1->arg[0] = i2->arg[0];
i1->arg[1] = newcon(&c, fn);
adduse(&fn->tmp[i1->arg[0].val], UIns, b, i1);
}
static void
killins(Fn *fn, Ins *i, Ref r)
{
replaceuses(fn, i->to, r);
*i = (Ins){.op = Onop};
}
static void
dedupins(Fn *fn, Blk *b, Ins *i)
{
Ref r;
Ins *i1;
normins(fn, i);
if (i->op == Onop || pinned(i))
return;
/* when sel instructions are inserted
* before gvn, we may want to optimize
* them here */
assert(i->op != Osel0);
assert(!req(i->to, R));
assoccon(fn, b, i);
r = copyref(fn, b, i);
if (!req(r, R)) {
killins(fn, i, r);
return;
}
r = foldref(fn, i);
if (!req(r, R)) {
killins(fn, i, r);
return;
}
i1 = gvndup(i, 1);
if (i1) {
killins(fn, i, i1->to);
return;
}
}
int
cmpeqz(Fn *fn, Ref r, Ref *arg, int *cls, int *eqval)
{
Ins *i;
if (rtype(r) != RTmp)
return 0;
i = fn->tmp[r.val].def;
if (i)
if (optab[i->op].cmpeqwl)
if (req(i->arg[1], CON_Z)) {
*arg = i->arg[0];
*cls = argcls(i, 0);
*eqval = optab[i->op].eqval;
return 1;
}
return 0;
}
static int
branchdom(Fn *fn, Blk *bif, Blk *bbr1, Blk *bbr2, Blk *b)
{
assert(bif->jmp.type == Jjnz);
if (b != bif
&& dom(bbr1, b)
&& !reachesnotvia(fn, bbr2, b, bif))
return 1;
return 0;
}
static int
domzero(Fn *fn, Blk *d, Blk *b, int *z)
{
if (branchdom(fn, d, d->s1, d->s2, b)) {
*z = 0;
return 1;
}
if (branchdom(fn, d, d->s2, d->s1, b)) {
*z = 1;
return 1;
}
return 0;
}
/* infer 0/non-0 value from dominating jnz */
int
zeroval(Fn *fn, Blk *b, Ref r, int cls, int *z)
{
Blk *d;
Ref arg;
int cls1, eqval;
for (d=b->idom; d; d=d->idom) {
if (d->jmp.type != Jjnz)
continue;
if (req(r, d->jmp.arg)
&& cls == Kw
&& domzero(fn, d, b, z)) {
return 1;
}
if (cmpeqz(fn, d->jmp.arg, &arg, &cls1, &eqval)
&& req(r, arg)
&& cls == cls1
&& domzero(fn, d, b, z)) {
*z ^= eqval;
return 1;
}
}
return 0;
}
static int
usecls(Use *u, Ref r, int cls)
{
int k;
switch (u->type) {
case UIns:
k = Kx; /* widest use */
if (req(u->u.ins->arg[0], r))
k = argcls(u->u.ins, 0);
if (req(u->u.ins->arg[1], r))
if (k == Kx || !KWIDE(k))
k = argcls(u->u.ins, 1);
return k == Kx ? cls : k;
case UPhi:
if (req(u->u.phi->to, R))
return cls; /* eliminated */
return u->u.phi->cls;
case UJmp:
return Kw;
default:
break;
}
die("unreachable");
}
static void
propjnz0(Fn *fn, Blk *bif, Blk *s0, Blk *snon0, Ref r, int cls)
{
Blk *b;
Tmp *t;
Use *u;
if (s0->npred != 1 || rtype(r) != RTmp)
return;
t = &fn->tmp[r.val];
for (u=t->use; u<&t->use[t->nuse]; u++) {
b = fn->rpo[u->bid];
/* we may compare an l temp with a w
* comparison; so check that the use
* does not involve high bits */
if (usecls(u, r, cls) == cls)
if (branchdom(fn, bif, s0, snon0, b))
replaceuse(fn, u, r, CON_Z);
}
}
static void
dedupjmp(Fn *fn, Blk *b)
{
Blk **ps;
int64_t v;
Ref arg;
int cls, eqval, z;
if (b->jmp.type != Jjnz)
return;
/* propagate jmp arg as 0 through s2 */
propjnz0(fn, b, b->s2, b->s1, b->jmp.arg, Kw);
/* propagate cmp eq/ne 0 def of jmp arg as 0 */
if (cmpeqz(fn, b->jmp.arg, &arg, &cls, &eqval)) {
ps = (Blk*[]){b->s1, b->s2};
propjnz0(fn, b, ps[eqval^1], ps[eqval], arg, cls);
}
/* collapse trivial/constant jnz to jmp */
v = 1;
z = 0;
if (b->s1 == b->s2
|| isconbits(fn, b->jmp.arg, &v)
|| zeroval(fn, b, b->jmp.arg, Kw, &z)) {
if (v == 0 || z)
b->s1 = b->s2;
/* we later move active ins out of dead blks */
b->s2 = 0;
b->jmp.type = Jjmp;
b->jmp.arg = R;
}
}
static void
rebuildcfg(Fn *fn)
{
uint n, nblk;
Blk *b, *s, **rpo;
Ins *i;
nblk = fn->nblk;
rpo = emalloc(nblk * sizeof rpo[0]);
memcpy(rpo, fn->rpo, nblk * sizeof rpo[0]);
fillcfg(fn);
/* move instructions that were in
* killed blocks and may be active
* in the computation in the start
* block */
s = fn->start;
for (n=0; n<nblk; n++) {
b = rpo[n];
if (b->id != -1u)
continue;
/* blk unreachable after GVN */
assert(b != s);
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (!optab[i->op].pinned)
if (gvndup(i, 0) == i)
addins(&s->ins, &s->nins, i);
}
free(rpo);
}
/* requires rpo pred ssa use
* recreates rpo preds
* breaks pred use dom ssa (GCM fixes ssa)
*/
void
gvn(Fn *fn)
{
Blk *b;
Phi *p;
Ins *i;
uint n, nins;
con01[0] = getcon(0, fn);
con01[1] = getcon(1, fn);
/* copy.c uses the visit bit */
for (b=fn->start; b; b=b->link)
for (p=b->phi; p; p=p->link)
p->visit = 0;
fillloop(fn);
narrowpars(fn);
filluse(fn);
ssacheck(fn);
nins = 0;
for (b=fn->start; b; b=b->link) {
b->visit = 0;
nins += b->nins;
}
gvntbln = nins + nins/2;
gvntbl = emalloc(gvntbln * sizeof gvntbl[0]);
for (n=0; n<fn->nblk; n++) {
b = fn->rpo[n];
dedupphi(fn, b);
for (i=b->ins; i<&b->ins[b->nins]; i++)
dedupins(fn, b, i);
dedupjmp(fn, b);
}
rebuildcfg(fn);
free(gvntbl);
gvntbl = 0;
if (debug['G']) {
fprintf(stderr, "\n> After GVN:\n");
printfn(fn, stderr);
}
}

121
ifopt.c Normal file
View File

@ -0,0 +1,121 @@
#include "all.h"
enum {
MaxIns = 2,
MaxPhis = 2,
};
static int
okbranch(Blk *b)
{
Ins *i;
int n;
n = 0;
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (i->op != Odbgloc) {
if (pinned(i))
return 0;
if (i->op != Onop)
n++;
}
return n <= MaxIns;
}
static int
okjoin(Blk *b)
{
Phi *p;
int n;
n = 0;
for (p=b->phi; p; p=p->link) {
if (KBASE(p->cls) != 0)
return 0;
n++;
}
return n <= MaxPhis;
}
static int
okgraph(Blk *ifb, Blk *thenb, Blk *elseb, Blk *joinb)
{
if (joinb->npred != 2 || !okjoin(joinb))
return 0;
assert(thenb != elseb);
if (thenb != ifb && !okbranch(thenb))
return 0;
if (elseb != ifb && !okbranch(elseb))
return 0;
return 1;
}
static void
convert(Blk *ifb, Blk *thenb, Blk *elseb, Blk *joinb)
{
Ins *ins, sel;
Phi *p;
uint nins;
ins = vnew(0, sizeof ins[0], PHeap);
nins = 0;
addbins(&ins, &nins, ifb);
if (thenb != ifb)
addbins(&ins, &nins, thenb);
if (elseb != ifb)
addbins(&ins, &nins, elseb);
assert(joinb->npred == 2);
if (joinb->phi) {
sel = (Ins){
.op = Osel0, .cls = Kw,
.arg = {ifb->jmp.arg},
};
addins(&ins, &nins, &sel);
}
sel = (Ins){.op = Osel1};
for (p=joinb->phi; p; p=p->link) {
sel.to = p->to;
sel.cls = p->cls;
sel.arg[0] = phiarg(p, thenb);
sel.arg[1] = phiarg(p, elseb);
addins(&ins, &nins, &sel);
}
idup(ifb, ins, nins);
ifb->jmp.type = Jjmp;
ifb->jmp.arg = R;
ifb->s1 = joinb;
ifb->s2 = 0;
joinb->npred = 1;
joinb->pred[0] = ifb;
joinb->phi = 0;
vfree(ins);
}
/* eliminate if-then[-else] graphlets
* using sel instructions
* needs rpo pred use; breaks cfg use
*/
void
ifconvert(Fn *fn)
{
Blk *ifb, *thenb, *elseb, *joinb;
if (debug['K'])
fputs("\n> If-conversion:\n", stderr);
for (ifb=fn->start; ifb; ifb=ifb->link)
if (ifgraph(ifb, &thenb, &elseb, &joinb))
if (okgraph(ifb, thenb, elseb, joinb)) {
if (debug['K'])
fprintf(stderr,
" @%s -> @%s, @%s -> @%s\n",
ifb->name, thenb->name, elseb->name,
joinb->name);
convert(ifb, thenb, elseb, joinb);
}
if (debug['K']) {
fprintf(stderr, "\n> After if-conversion:\n");
printfn(fn, stderr);
}
}

4
load.c
View File

@ -380,6 +380,7 @@ def(Slice sl, bits msk, Blk *b, Ins *i, Loc *il)
goto Load;
p->arg[np] = r1;
p->blk[np] = bp;
/* XXX - multiplicity in predecessors!!! */
}
if (msk != msks)
mask(cls, &r, msk, il);
@ -481,8 +482,7 @@ loadopt(Fn *fn)
vgrow(&ib, ++nt);
ib[nt-1] = *i;
}
b->nins = nt;
idup(&b->ins, ib, nt);
idup(b, ib, nt);
}
vfree(ib);
vfree(ilog);

32
main.c
View File

@ -11,6 +11,7 @@ char debug['Z'+1] = {
['N'] = 0, /* ssa construction */
['C'] = 0, /* copy elimination */
['F'] = 0, /* constant folding */
['K'] = 0, /* if-conversion */
['A'] = 0, /* abi lowering */
['I'] = 0, /* instruction selection */
['L'] = 0, /* liveness */
@ -20,6 +21,7 @@ char debug['Z'+1] = {
extern Target T_amd64_sysv;
extern Target T_amd64_apple;
extern Target T_amd64_win;
extern Target T_arm64;
extern Target T_arm64_apple;
extern Target T_rv64;
@ -27,6 +29,7 @@ extern Target T_rv64;
static Target *tlist[] = {
&T_amd64_sysv,
&T_amd64_apple,
&T_amd64_win,
&T_arm64,
&T_arm64_apple,
&T_rv64,
@ -59,8 +62,7 @@ func(Fn *fn)
printfn(fn, stderr);
}
T.abi0(fn);
fillrpo(fn);
fillpreds(fn);
fillcfg(fn);
filluse(fn);
promote(fn);
filluse(fn);
@ -73,25 +75,37 @@ func(Fn *fn)
fillalias(fn);
coalesce(fn);
filluse(fn);
filldom(fn);
ssacheck(fn);
copy(fn);
gvn(fn);
fillcfg(fn);
simplcfg(fn);
filluse(fn);
fold(fn);
filldom(fn);
gcm(fn);
filluse(fn);
ssacheck(fn);
if (T.cansel) {
ifconvert(fn);
fillcfg(fn);
filluse(fn);
filldom(fn);
ssacheck(fn);
}
T.abi1(fn);
simpl(fn);
fillpreds(fn);
fillcfg(fn);
filluse(fn);
T.isel(fn);
fillrpo(fn);
fillcfg(fn);
filllive(fn);
fillloop(fn);
fillcost(fn);
spill(fn);
rega(fn);
fillrpo(fn);
fillcfg(fn);
simpljmp(fn);
fillpreds(fn);
fillrpo(fn);
fillcfg(fn);
assert(fn->rpo[0] == fn->start);
for (n=0;; n++)
if (n == fn->nblk-1) {

15
mem.c
View File

@ -296,7 +296,7 @@ coalesce(Fn *fn)
if (s->l) {
radd(&s->r, ip);
if (b->loop != -1) {
assert(b->loop > n);
assert(b->loop >= n);
radd(&s->r, br[b->loop].b - 1);
}
}
@ -447,10 +447,15 @@ coalesce(Fn *fn)
if (i->op == Oblit0)
if (slot(&s, &off0, i->arg[0], fn, sl))
if (slot(&s0, &off1, i->arg[1], fn, sl))
if (s->s == s0->s && off0 < off1) {
sz = rsval((i+1)->arg[0]);
assert(sz >= 0);
(i+1)->arg[0] = INT(-sz);
if (s->s == s0->s) {
if (off0 < off1) {
sz = rsval((i+1)->arg[0]);
assert(sz >= 0);
(i+1)->arg[0] = INT(-sz);
} else if (off0 == off1) {
*i = (Ins){.op = Onop};
*(i+1) = (Ins){.op = Onop};
}
}
}
vfree(bl);

View File

@ -70,7 +70,7 @@ struct Stmt {
int yylex(void), yyerror(char *);
Symb expr(Node *), lval(Node *);
void bool(Node *, int, int);
void branch(Node *, int, int);
FILE *of;
int line;
@ -329,7 +329,7 @@ expr(Node *n)
case 'a':
l = lbl;
lbl += 3;
bool(n, l, l+1);
branch(n, l, l+1);
fprintf(of, "@l%d\n", l);
fprintf(of, "\tjmp @l%d\n", l+2);
fprintf(of, "@l%d\n", l+1);
@ -468,7 +468,7 @@ lval(Node *n)
}
void
bool(Node *n, int lt, int lf)
branch(Node *n, int lt, int lf)
{
Symb s;
int l;
@ -483,16 +483,16 @@ bool(Node *n, int lt, int lf)
case 'o':
l = lbl;
lbl += 1;
bool(n->l, lt, l);
branch(n->l, lt, l);
fprintf(of, "@l%d\n", l);
bool(n->r, lt, lf);
branch(n->r, lt, lf);
break;
case 'a':
l = lbl;
lbl += 1;
bool(n->l, l, lf);
branch(n->l, l, lf);
fprintf(of, "@l%d\n", l);
bool(n->r, lt, lf);
branch(n->r, lt, lf);
break;
}
}
@ -526,7 +526,7 @@ stmt(Stmt *s, int b)
case If:
l = lbl;
lbl += 3;
bool(s->p1, l, l+1);
branch(s->p1, l, l+1);
fprintf(of, "@l%d\n", l);
if (!(r=stmt(s->p2, b)))
if (s->p3)
@ -540,7 +540,7 @@ stmt(Stmt *s, int b)
l = lbl;
lbl += 3;
fprintf(of, "@l%d\n", l);
bool(s->p1, l+1, l+2);
branch(s->p1, l+1, l+2);
fprintf(of, "@l%d\n", l+1);
if (!stmt(s->p2, l+2))
fprintf(of, "\tjmp @l%d\n", l);

311
ops.h
View File

@ -6,184 +6,217 @@
#define V(Imm)
#endif
#ifndef F
#define F(a,b,c,d,e,f,g,h,i,j)
#endif
#define T(a,b,c,d,e,f,g,h) { \
{[Kw]=K##a, [Kl]=K##b, [Ks]=K##c, [Kd]=K##d}, \
{[Kw]=K##e, [Kl]=K##f, [Ks]=K##g, [Kd]=K##h} \
}
/*********************/
/* PUBLIC OPERATIONS */
/*********************/
/* Arithmetic and Bits */
O(add, T(w,l,s,d, w,l,s,d), 1) X(2, 1, 0) V(1)
O(sub, T(w,l,s,d, w,l,s,d), 1) X(2, 1, 0) V(0)
O(neg, T(w,l,s,d, x,x,x,x), 1) X(1, 1, 0) V(0)
O(div, T(w,l,s,d, w,l,s,d), 1) X(0, 0, 0) V(0)
O(rem, T(w,l,e,e, w,l,e,e), 1) X(0, 0, 0) V(0)
O(udiv, T(w,l,e,e, w,l,e,e), 1) X(0, 0, 0) V(0)
O(urem, T(w,l,e,e, w,l,e,e), 1) X(0, 0, 0) V(0)
O(mul, T(w,l,s,d, w,l,s,d), 1) X(2, 0, 0) V(0)
O(and, T(w,l,e,e, w,l,e,e), 1) X(2, 1, 0) V(1)
O(or, T(w,l,e,e, w,l,e,e), 1) X(2, 1, 0) V(1)
O(xor, T(w,l,e,e, w,l,e,e), 1) X(2, 1, 0) V(1)
O(sar, T(w,l,e,e, w,w,e,e), 1) X(1, 1, 0) V(1)
O(shr, T(w,l,e,e, w,w,e,e), 1) X(1, 1, 0) V(1)
O(shl, T(w,l,e,e, w,w,e,e), 1) X(1, 1, 0) V(1)
/* can fold */
/* | has identity */
/* | | identity value for arg[1] */
/* | | | commutative */
/* | | | | associative */
/* | | | | | idempotent */
/* | | | | | | c{eq,ne}[wl] */
/* | | | | | | | c[us][gl][et][wl] */
/* | | | | | | | | value if = args */
/* | | | | | | | | | pinned */
/* Arithmetic and Bits v v v v v v v v v v */
O(add, T(w,l,s,d, w,l,s,d), F(1,1,0,1,1,0,0,0,0,0)) X(2,1,0) V(1)
O(sub, T(w,l,s,d, w,l,s,d), F(1,1,0,0,0,0,0,0,0,0)) X(2,1,0) V(0)
O(neg, T(w,l,s,d, x,x,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(1,1,0) V(0)
O(div, T(w,l,s,d, w,l,s,d), F(1,1,1,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(rem, T(w,l,e,e, w,l,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(udiv, T(w,l,e,e, w,l,e,e), F(1,1,1,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(urem, T(w,l,e,e, w,l,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(mul, T(w,l,s,d, w,l,s,d), F(1,1,1,1,0,0,0,0,0,0)) X(2,0,0) V(0)
O(and, T(w,l,e,e, w,l,e,e), F(1,0,0,1,1,1,0,0,0,0)) X(2,1,0) V(1)
O(or, T(w,l,e,e, w,l,e,e), F(1,1,0,1,1,1,0,0,0,0)) X(2,1,0) V(1)
O(xor, T(w,l,e,e, w,l,e,e), F(1,1,0,1,1,0,0,0,0,0)) X(2,1,0) V(1)
O(sar, T(w,l,e,e, w,w,e,e), F(1,1,0,0,0,0,0,0,0,0)) X(1,1,0) V(1)
O(shr, T(w,l,e,e, w,w,e,e), F(1,1,0,0,0,0,0,0,0,0)) X(1,1,0) V(1)
O(shl, T(w,l,e,e, w,w,e,e), F(1,1,0,0,0,0,0,0,0,0)) X(1,1,0) V(1)
/* Comparisons */
O(ceqw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(cnew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(csgew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(csgtw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(cslew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(csltw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(1)
O(cugew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(cugtw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(culew, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(0)
O(cultw, T(w,w,e,e, w,w,e,e), 1) X(0, 1, 0) V(1)
O(ceqw, T(w,w,e,e, w,w,e,e), F(1,1,1,1,0,0,1,0,1,0)) X(0,1,0) V(0)
O(cnew, T(w,w,e,e, w,w,e,e), F(1,1,0,1,0,0,1,0,0,0)) X(0,1,0) V(0)
O(csgew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(csgtw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0)
O(cslew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(csltw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1)
O(cugew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(cugtw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0)
O(culew, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(cultw, T(w,w,e,e, w,w,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1)
O(ceql, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(cnel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(csgel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(csgtl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(cslel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(csltl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(1)
O(cugel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(cugtl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(culel, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(0)
O(cultl, T(l,l,e,e, l,l,e,e), 1) X(0, 1, 0) V(1)
O(ceql, T(l,l,e,e, l,l,e,e), F(1,0,0,1,0,0,1,0,1,0)) X(0,1,0) V(0)
O(cnel, T(l,l,e,e, l,l,e,e), F(1,0,0,1,0,0,1,0,0,0)) X(0,1,0) V(0)
O(csgel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(csgtl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0)
O(cslel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(csltl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1)
O(cugel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(cugtl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(0)
O(culel, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,1,0)) X(0,1,0) V(0)
O(cultl, T(l,l,e,e, l,l,e,e), F(1,0,0,0,0,0,0,1,0,0)) X(0,1,0) V(1)
O(ceqs, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(cges, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(cgts, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(cles, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(clts, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(cnes, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(cos, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(cuos, T(s,s,e,e, s,s,e,e), 1) X(0, 1, 0) V(0)
O(ceqs, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cges, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cgts, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cles, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(clts, T(s,s,e,e, s,s,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cnes, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cos, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cuos, T(s,s,e,e, s,s,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(ceqd, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cged, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cgtd, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cled, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cltd, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cned, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cod, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(cuod, T(d,d,e,e, d,d,e,e), 1) X(0, 1, 0) V(0)
O(ceqd, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cged, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cgtd, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cled, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cltd, T(d,d,e,e, d,d,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cned, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cod, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
O(cuod, T(d,d,e,e, d,d,e,e), F(1,0,0,1,0,0,0,0,0,0)) X(0,1,0) V(0)
/* Memory */
O(storeb, T(w,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0)
O(storeh, T(w,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0)
O(storew, T(w,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0)
O(storel, T(l,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0)
O(stores, T(s,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0)
O(stored, T(d,e,e,e, m,e,e,e), 0) X(0, 0, 1) V(0)
O(storeb, T(w,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(storeh, T(w,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(storew, T(w,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(storel, T(l,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(stores, T(s,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(stored, T(d,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(loadsb, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(loadub, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(loadsh, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(loaduh, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(loadsw, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(loaduw, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(load, T(m,m,m,m, x,x,x,x), 0) X(0, 0, 1) V(0)
O(loadsb, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(loadub, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(loadsh, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(loaduh, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(loadsw, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(loaduw, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
O(load, T(m,m,m,m, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
/* Extensions and Truncations */
O(extsb, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(extub, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(extsh, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(extuh, T(w,w,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(extsw, T(e,w,e,e, e,x,e,e), 1) X(0, 0, 1) V(0)
O(extuw, T(e,w,e,e, e,x,e,e), 1) X(0, 0, 1) V(0)
O(extsb, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(extub, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(extsh, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(extuh, T(w,w,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(extsw, T(e,w,e,e, e,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(extuw, T(e,w,e,e, e,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(exts, T(e,e,e,s, e,e,e,x), 1) X(0, 0, 1) V(0)
O(truncd, T(e,e,d,e, e,e,x,e), 1) X(0, 0, 1) V(0)
O(stosi, T(s,s,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(stoui, T(s,s,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(dtosi, T(d,d,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(dtoui, T(d,d,e,e, x,x,e,e), 1) X(0, 0, 1) V(0)
O(swtof, T(e,e,w,w, e,e,x,x), 1) X(0, 0, 1) V(0)
O(uwtof, T(e,e,w,w, e,e,x,x), 1) X(0, 0, 1) V(0)
O(sltof, T(e,e,l,l, e,e,x,x), 1) X(0, 0, 1) V(0)
O(ultof, T(e,e,l,l, e,e,x,x), 1) X(0, 0, 1) V(0)
O(cast, T(s,d,w,l, x,x,x,x), 1) X(0, 0, 1) V(0)
O(exts, T(e,e,e,s, e,e,e,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(truncd, T(e,e,d,e, e,e,x,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(stosi, T(s,s,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(stoui, T(s,s,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(dtosi, T(d,d,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(dtoui, T(d,d,e,e, x,x,e,e), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(swtof, T(e,e,w,w, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(uwtof, T(e,e,w,w, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(sltof, T(e,e,l,l, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(ultof, T(e,e,l,l, e,e,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(cast, T(s,d,w,l, x,x,x,x), F(1,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
/* Stack Allocation */
O(alloc4, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(alloc8, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(alloc16, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(alloc4, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(alloc8, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(alloc16, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
/* Variadic Function Helpers */
O(vaarg, T(m,m,m,m, x,x,x,x), 0) X(0, 0, 0) V(0)
O(vastart, T(m,e,e,e, x,e,e,e), 0) X(0, 0, 0) V(0)
O(vaarg, T(m,m,m,m, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(vastart, T(m,e,e,e, x,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(copy, T(w,l,s,d, x,x,x,x), 0) X(0, 0, 1) V(0)
O(copy, T(w,l,s,d, x,x,x,x), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
/* Debug */
O(dbgloc, T(w,e,e,e, w,e,e,e), 0) X(0, 0, 1) V(0)
O(dbgloc, T(w,e,e,e, w,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,1) V(0)
/****************************************/
/* INTERNAL OPERATIONS (keep nop first) */
/****************************************/
/* Miscellaneous and Architecture-Specific Operations */
O(nop, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 1) V(0)
O(addr, T(m,m,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(blit0, T(m,e,e,e, m,e,e,e), 0) X(0, 1, 0) V(0)
O(blit1, T(w,e,e,e, x,e,e,e), 0) X(0, 1, 0) V(0)
O(swap, T(w,l,s,d, w,l,s,d), 0) X(1, 0, 0) V(0)
O(sign, T(w,l,e,e, x,x,e,e), 0) X(0, 0, 0) V(0)
O(salloc, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(xidiv, T(w,l,e,e, x,x,e,e), 0) X(1, 0, 0) V(0)
O(xdiv, T(w,l,e,e, x,x,e,e), 0) X(1, 0, 0) V(0)
O(xcmp, T(w,l,s,d, w,l,s,d), 0) X(1, 1, 0) V(0)
O(xtest, T(w,l,e,e, w,l,e,e), 0) X(1, 1, 0) V(0)
O(acmp, T(w,l,e,e, w,l,e,e), 0) X(0, 0, 0) V(0)
O(acmn, T(w,l,e,e, w,l,e,e), 0) X(0, 0, 0) V(0)
O(afcmp, T(e,e,s,d, e,e,s,d), 0) X(0, 0, 0) V(0)
O(reqz, T(w,l,e,e, x,x,e,e), 0) X(0, 0, 0) V(0)
O(rnez, T(w,l,e,e, x,x,e,e), 0) X(0, 0, 0) V(0)
O(nop, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(addr, T(m,m,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(blit0, T(m,e,e,e, m,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,1,0) V(0)
O(blit1, T(w,e,e,e, x,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,1,0) V(0)
O(sel0, T(w,e,e,e, x,e,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(sel1, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(swap, T(w,l,s,d, w,l,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(1,0,0) V(0)
O(sign, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(salloc, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xidiv, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(1,0,0) V(0)
O(xdiv, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(1,0,0) V(0)
O(xcmp, T(w,l,s,d, w,l,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(1,1,0) V(0)
O(xtest, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(1,1,0) V(0)
O(acmp, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(acmn, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(afcmp, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(reqz, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(rnez, T(w,l,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
/* Arguments, Parameters, and Calls */
O(par, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0)
O(parsb, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0)
O(parub, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0)
O(parsh, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0)
O(paruh, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0)
O(parc, T(e,x,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(pare, T(e,x,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(arg, T(w,l,s,d, x,x,x,x), 0) X(0, 0, 0) V(0)
O(argsb, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0)
O(argub, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0)
O(argsh, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0)
O(arguh, T(w,e,e,e, x,x,x,x), 0) X(0, 0, 0) V(0)
O(argc, T(e,x,e,e, e,l,e,e), 0) X(0, 0, 0) V(0)
O(arge, T(e,l,e,e, e,x,e,e), 0) X(0, 0, 0) V(0)
O(argv, T(x,x,x,x, x,x,x,x), 0) X(0, 0, 0) V(0)
O(call, T(m,m,m,m, x,x,x,x), 0) X(0, 0, 0) V(0)
O(par, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(parsb, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(parub, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(parsh, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(paruh, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(parc, T(e,x,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(pare, T(e,x,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(arg, T(w,l,s,d, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(argsb, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(argub, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(argsh, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(arguh, T(w,e,e,e, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(argc, T(e,x,e,e, e,l,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(arge, T(e,l,e,e, e,x,e,e), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(argv, T(x,x,x,x, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
O(call, T(m,m,m,m, x,x,x,x), F(0,0,0,0,0,0,0,0,0,1)) X(0,0,0) V(0)
/* Flags Setting */
O(flagieq, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagine, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagisge, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagisgt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagisle, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagislt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagiuge, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagiugt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagiule, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagiult, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfeq, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfge, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfgt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfle, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagflt, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfne, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfo, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagfuo, T(x,x,e,e, x,x,e,e), 0) X(0, 0, 1) V(0)
O(flagieq, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagine, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagisge, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagisgt, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagisle, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagislt, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagiuge, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagiugt, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagiule, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagiult, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfeq, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfge, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfgt, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfle, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagflt, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfne, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfo, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
O(flagfuo, T(x,x,e,e, x,x,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,1) V(0)
/* Backend Flag Select (Condition Move) */
O(xselieq, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseline, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselisge, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselisgt, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselisle, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselislt, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliuge, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliugt, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliule, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xseliult, T(w,l,e,e, w,l,e,e), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfeq, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfge, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfgt, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfle, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselflt, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfne, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfo, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
O(xselfuo, T(e,e,s,d, e,e,s,d), F(0,0,0,0,0,0,0,0,0,0)) X(0,0,0) V(0)
#undef T
#undef X

72
parse.c
View File

@ -15,8 +15,17 @@ enum {
};
Op optab[NOp] = {
#define O(op, t, cf) [O##op]={#op, t, cf},
#undef F
#define F(cf, hi, id, co, as, im, ic, lg, cv, pn) \
.canfold = cf, \
.hasid = hi, .idval = id, \
.commutes = co, .assoc = as, \
.idemp = im, \
.cmpeqwl = ic, .cmplgtewl = lg, .eqval = cv, \
.pinned = pn
#define O(op, k, flags) [O##op]={.name = #op, .argcls = k, flags},
#include "ops.h"
#undef F
};
typedef enum {
@ -48,6 +57,7 @@ enum Token {
Thlt,
Texport,
Tthread,
Tcommon,
Tfunc,
Ttype,
Tdata,
@ -106,6 +116,7 @@ static char *kwmap[Ntok] = {
[Thlt] = "hlt",
[Texport] = "export",
[Tthread] = "thread",
[Tcommon] = "common",
[Tfunc] = "function",
[Ttype] = "type",
[Tdata] = "data",
@ -132,7 +143,7 @@ enum {
TMask = 16383, /* for temps hash */
BMask = 8191, /* for blocks hash */
K = 9583425, /* found using tools/lexh.c */
K = 11183273, /* found using tools/lexh.c */
M = 23,
};
@ -150,7 +161,8 @@ static struct {
static int lnum;
static Fn *curf;
static int tmph[TMask+1];
static int *tmph;
static int tmphcap;
static Phi **plink;
static Blk *curb;
static Blk **blink;
@ -203,12 +215,15 @@ getint()
n = 0;
c = fgetc(inf);
m = (c == '-');
if (m || c == '+')
if (m) {
c = fgetc(inf);
if (!isdigit(c))
err("integer expected");
}
do {
n = 10*n + (c - '0');
c = fgetc(inf);
} while ('0' <= c && c <= '9');
} while (isdigit(c));
ungetc(c, inf);
if (m)
n = 1 + ~n;
@ -277,7 +292,7 @@ lex()
lnum++;
return Tnl;
}
if (isdigit(c) || c == '-' || c == '+') {
if (isdigit(c) || c == '-') {
ungetc(c, inf);
tokval.num = getint();
return Tint;
@ -382,19 +397,27 @@ expect(int t)
static Ref
tmpref(char *v)
{
int t, *h;
int t, i;
h = &tmph[hash(v) & TMask];
t = *h;
if (t) {
if (tmphcap/2 <= curf->ntmp-Tmp0) {
free(tmph);
tmphcap = tmphcap ? tmphcap*2 : TMask+1;
tmph = emalloc(tmphcap * sizeof tmph[0]);
for (t=Tmp0; t<curf->ntmp; t++) {
i = hash(curf->tmp[t].name) & (tmphcap-1);
for (; tmph[i]; i=(i+1) & (tmphcap-1))
;
tmph[i] = t;
}
}
i = hash(v) & (tmphcap-1);
for (; tmph[i]; i=(i+1) & (tmphcap-1)) {
t = tmph[i];
if (strcmp(curf->tmp[t].name, v) == 0)
return TMP(t);
for (t=curf->ntmp-1; t>=Tmp0; t--)
if (strcmp(curf->tmp[t].name, v) == 0)
return TMP(t);
}
t = curf->ntmp;
*h = t;
tmph[i] = t;
newtmp(0, Kx, curf);
strcpy(curf->tmp[t].name, v);
return TMP(t);
@ -568,8 +591,7 @@ findblk(char *name)
static void
closeblk()
{
curb->nins = curi - insb;
idup(&curb->ins, insb, curb->nins);
idup(curb, insb, curi-insb);
blink = &curb->link;
curi = insb;
}
@ -680,6 +702,7 @@ parseline(PState ps)
goto Ins;
}
if (op == Tcall) {
curf->leaf = 0;
arg[0] = parseref();
parserefl(1);
op = Ocall;
@ -896,6 +919,7 @@ parsefn(Lnk *lnk)
curf->con[0].bits.i = 0xdeaddead; /* UNDEF */
curf->con[1].type = CBits;
curf->lnk = *lnk;
curf->leaf = 1;
blink = &curf->start;
curf->retty = Kx;
if (peek() != Tglo)
@ -919,12 +943,12 @@ parsefn(Lnk *lnk)
curf->mem = vnew(0, sizeof curf->mem[0], PFn);
curf->nmem = 0;
curf->nblk = nblk;
curf->rpo = 0;
for (b=0; b; b=b->link)
curf->rpo = vnew(nblk, sizeof curf->rpo[0], PFn);
for (b=curf->start; b; b=b->link)
b->dlink = 0; /* was trashed by findblk() */
for (i=0; i<BMask+1; ++i)
blkh[i] = 0;
memset(tmph, 0, sizeof tmph);
memset(tmph, 0, tmphcap * sizeof tmph[0]);
typecheck(curf);
return curf;
}
@ -1091,6 +1115,9 @@ parsedat(void cb(Dat *), Lnk *lnk)
if (t == Talign) {
if (nextnl() != Tint)
err("alignment expected");
if (tokval.num <= 0 || tokval.num > CHAR_MAX
|| (tokval.num & (tokval.num-1)) != 0)
err("invalid alignment");
lnk->align = tokval.num;
t = nextnl();
}
@ -1156,6 +1183,9 @@ parselnk(Lnk *lnk)
case Tthread:
lnk->thread = 1;
break;
case Tcommon:
lnk->common = 1;
break;
case Tsection:
if (lnk->sec)
err("only one section allowed");
@ -1199,6 +1229,7 @@ parse(FILE *f, char *path, void dbgfile(char *), void data(Dat *), void func(Fn
dbgfile(tokval.str);
break;
case Tfunc:
lnk.align = 16;
func(parsefn(&lnk));
break;
case Tdata:
@ -1294,6 +1325,9 @@ printref(Ref r, Fn *fn, FILE *f)
case RInt:
fprintf(f, "%d", rsval(r));
break;
case -1:
fprintf(f, "R");
break;
}
}

8
rega.c
View File

@ -439,8 +439,7 @@ doblk(Blk *b, RMap *cur)
* the above loop must be changed */
}
}
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
}
/* qsort() comparison function to peel
@ -671,10 +670,9 @@ rega(Fn *fn)
blist = b1;
fn->nblk++;
strf(b1->name, "%s_%s", b->name, s->name);
b1->nins = &insb[NIns] - curi;
stmov += b1->nins;
stmov += &insb[NIns]-curi;
stblk += 1;
idup(&b1->ins, curi, b1->nins);
idup(b1, curi, &insb[NIns]-curi);
b1->jmp.type = Jjmp;
b1->s1 = s;
**ps = b1;

View File

@ -587,9 +587,9 @@ void
rv64_abi(Fn *fn)
{
Blk *b;
Ins *i, *i0, *ip;
Ins *i, *i0;
Insl *il;
int n;
int n0, n1, ioff;
Params p;
for (b=fn->start; b; b=b->link)
@ -600,12 +600,13 @@ rv64_abi(Fn *fn)
if (!ispar(i->op))
break;
p = selpar(fn, b->ins, i);
n = b->nins - (i - b->ins) + (&insb[NIns] - curi);
i0 = alloc(n * sizeof(Ins));
ip = icpy(ip = i0, curi, &insb[NIns] - curi);
ip = icpy(ip, i, &b->ins[b->nins] - i);
b->nins = n;
b->ins = i0;
n0 = &insb[NIns] - curi;
ioff = i - b->ins;
n1 = b->nins - ioff;
vgrow(&b->ins, n0+n1);
icpy(b->ins+n0, b->ins+ioff, n1);
icpy(b->ins, curi, n0);
b->nins = n0+n1;
/* lower calls, returns, and vararg instructions */
il = 0;
@ -642,8 +643,7 @@ rv64_abi(Fn *fn)
if (b == fn->start)
for (; il; il=il->link)
emiti(il->i);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
} while (b != fn->start);
if (debug['A']) {

View File

@ -8,7 +8,7 @@ enum {
static struct {
short op;
short cls;
char *asm;
char *fmt;
} omap[] = {
{ Oadd, Ki, "add%k %=, %0, %1" },
{ Oadd, Ka, "fadd.%k %=, %0, %1" },
@ -326,7 +326,7 @@ emitins(Ins *i, Fn *fn, FILE *f)
|| (omap[o].cls == Ki && KBASE(i->cls) == 0))
break;
}
emitf(omap[o].asm, i, fn, f);
emitf(omap[o].fmt, i, fn, f);
break;
case Ocopy:
if (req(i->to, i->arg[0]))
@ -444,7 +444,7 @@ rv64_emitfn(Fn *fn, FILE *f)
static int id0;
int lbl, neg, off, frame, *pr, r;
Blk *b, *s;
Ins *i;
Ins *i, ii;
emitfnlnk(fn->name, &fn->lnk, f);
@ -549,6 +549,11 @@ rv64_emitfn(Fn *fn, FILE *f)
b->s2 = s;
neg = 1;
}
if (rtype(b->jmp.arg) == RSlot) {
ii.arg[0] = b->jmp.arg;
emitf("lw t6, %M0", &ii, fn, f);
b->jmp.arg = TMP(T6);
}
assert(isreg(b->jmp.arg));
fprintf(f,
"\tb%sz %s, .L%d\n",

View File

@ -31,6 +31,7 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
c = &fn->con[r0.val];
if (c->type == CAddr && memarg(r, op, i))
break;
if (KBASE(k) == 0)
if (c->type == CBits && immarg(r, op, i))
if (-2048 <= c->bits.i && c->bits.i < 2048)
break;
@ -41,7 +42,7 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
* immediates
*/
assert(c->type == CBits);
n = stashbits(&c->bits, KWIDE(k) ? 8 : 4);
n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4);
vgrow(&fn->con, ++fn->ncon);
c = &fn->con[fn->ncon-1];
sprintf(buf, "\"%sfp%d\"", T.asloc, n);
@ -244,8 +245,7 @@ rv64_isel(Fn *fn)
seljmp(b, fn);
for (i=&b->ins[b->nins]; i!=b->ins;)
sel(*--i, fn);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
}
if (debug['I']) {

View File

@ -50,6 +50,7 @@ Target T_rv64 = {
.emitfn = rv64_emitfn,
.emitfin = elf_emitfin,
.asloc = ".L",
.cansel = 0,
};
MAKESURE(rsave_size_ok, sizeof rv64_rsave == (NGPS+NFPS+1) * sizeof(int));

62
simpl.c
View File

@ -30,16 +30,43 @@ blit(Ref sd[2], int sz, Fn *fn)
}
}
static int
ulog2_tab64[64] = {
63, 0, 1, 41, 37, 2, 16, 42,
38, 29, 32, 3, 12, 17, 43, 55,
39, 35, 30, 53, 33, 21, 4, 23,
13, 9, 18, 6, 25, 44, 48, 56,
62, 40, 36, 15, 28, 31, 11, 54,
34, 52, 20, 22, 8, 5, 24, 47,
61, 14, 27, 10, 51, 19, 7, 46,
60, 26, 50, 45, 59, 49, 58, 57,
};
static int
ulog2(uint64_t pow2)
{
return ulog2_tab64[(pow2 * 0x5b31ab928877a7e) >> 58];
}
static int
ispow2(uint64_t v)
{
return v && (v & (v - 1)) == 0;
}
static void
ins(Ins **pi, int *new, Blk *b, Fn *fn)
{
ulong ni;
Con *c;
Ins *i;
Ref r;
int n;
i = *pi;
/* simplify more instructions here;
* copy 0 into xor, mul 2^n into shift,
* bit rotations, ... */
* copy 0 into xor, bit rotations,
* etc. */
switch (i->op) {
case Oblit1:
assert(i > b->ins);
@ -53,12 +80,29 @@ ins(Ins **pi, int *new, Blk *b, Fn *fn)
}
blit((i-1)->arg, rsval(i->arg[0]), fn);
*pi = i-1;
break;
default:
if (*new)
emiti(*i);
return;
case Oudiv:
case Ourem:
r = i->arg[1];
if (KBASE(i->cls) == 0)
if (rtype(r) == RCon) {
c = &fn->con[r.val];
if (c->type == CBits)
if (ispow2(c->bits.i)) {
n = ulog2(c->bits.i);
if (i->op == Ourem) {
i->op = Oand;
i->arg[1] = getcon((1ull<<n) - 1, fn);
} else {
i->op = Oshr;
i->arg[1] = getcon(n, fn);
}
}
}
break;
}
if (*new)
emiti(*i);
}
void
@ -74,9 +118,7 @@ simpl(Fn *fn)
--i;
ins(&i, &new, b, fn);
}
if (new) {
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
}
if (new)
idup(b, curi, &insb[NIns]-curi);
}
}

25
spill.c
View File

@ -406,26 +406,20 @@ spill(Fn *fn)
if (rtype(b->jmp.arg) == RCall)
v->t[0] |= T.retregs(b->jmp.arg, 0);
}
if (rtype(b->jmp.arg) == RTmp) {
t = b->jmp.arg.val;
assert(KBASE(tmp[t].cls) == 0);
bsset(v, t);
limit2(v, 0, 0, NULL);
if (!bshas(v, t))
b->jmp.arg = slot(t);
}
for (t=Tmp0; bsiter(b->out, &t); t++)
if (!bshas(v, t))
slot(t);
bscopy(b->out, v);
/* 2. process the block instructions */
if (rtype(b->jmp.arg) == RTmp) {
t = b->jmp.arg.val;
assert(KBASE(tmp[t].cls) == 0);
lvarg[0] = bshas(v, t);
bsset(v, t);
bscopy(u, v);
limit2(v, 0, 0, NULL);
if (!bshas(v, t)) {
if (!lvarg[0])
bsclr(u, t);
b->jmp.arg = slot(t);
}
reloads(u, v);
}
curi = &insb[NIns];
for (i=&b->ins[b->nins]; i!=b->ins;) {
i--;
@ -517,8 +511,7 @@ spill(Fn *fn)
p->to = slot(p->to.val);
}
bscopy(b->in, v);
b->nins = &insb[NIns] - curi;
idup(&b->ins, curi, b->nins);
idup(b, curi, &insb[NIns]-curi);
}
/* align the locals to a 16 byte boundary */

9
ssa.c
View File

@ -1,7 +1,7 @@
#include "all.h"
#include <stdarg.h>
static void
void
adduse(Tmp *tmp, int ty, Blk *b, ...)
{
Use *u;
@ -40,11 +40,10 @@ filluse(Fn *fn)
Blk *b;
Phi *p;
Ins *i;
int m, t, tp, w;
int m, t, tp, w, x;
uint a;
Tmp *tmp;
/* todo, is this the correct file? */
tmp = fn->tmp;
for (t=Tmp0; t<fn->ntmp; t++) {
tmp[t].def = 0;
@ -84,6 +83,8 @@ filluse(Fn *fn)
w = Wsb + (i->op - Oloadsb);
if (isext(i->op))
w = Wsb + (i->op - Oextsb);
if (iscmp(i->op, &x, &x))
w = Wub;
if (w == Wsw || w == Wuw)
if (i->cls == Kw)
w = WFull;
@ -143,7 +144,7 @@ phiins(Fn *fn)
continue;
}
bszero(u);
k = -1;
k = Kx;
bp = be;
for (b=fn->start; b; b=b->link) {
b->visit = 0;

48
test/_gcm1.ssa Normal file
View File

@ -0,0 +1,48 @@
export
function w $ifmv(w %p1, w %p2, w %p3) {
@start
@entry
%rt =w add %p2, %p3 # gcm moves to @true
%rf =w sub %p2, %p3 # gcm moves to @false
jnz %p1, @true, @false
@true
%r =w copy %rt
jmp @exit
@false
%r =w copy %rf
jmp @exit
@exit
ret %r
}
export
function w $hoist1(w %p1, w %p2, w %p3) {
@start
@entry
%n =w copy 0
%i =w copy %p1
@loop
%base =w add %p2, %p3 # gcm moves to @exit
%i =w sub %i, 1
%n =w add %n, 1
jnz %i, @loop, @exit
@exit
%r =w add %base, %n
ret %r
}
export
function w $hoist2(w %p1, w %p2, w %p3) {
@start
@entry
%n =w copy 0
%i =w copy %p1
@loop
%base =w add %p2, %p3 # gcm moves to @entry
%i =w sub %i, 1
%n =w add %n, %base
jnz %i, @loop, @exit
@exit
%r =w add %base, %n
ret %r
}

43
test/_gcm2.ssa Normal file
View File

@ -0,0 +1,43 @@
# Programs from "Global Code Motion Global Value Numbering" by Cliff Click
# https://courses.cs.washington.edu/courses/cse501/06wi/reading/click-pldi95.pdf
# GCM program in Figure 1
function w $gcm_test(w %a){
@start
%i.0 =w copy 0
@loop
%i.1 =w phi @start %i.0, @loop %i.2
%b =w add %a, 1 # early schedule moves to @start
%i.2 =w add %i.1, %b
%c =w mul %i.2, 2 # late schedule moves to @end
%x =w csltw %i.2, 10
jnz %x, @loop, @end
@end
ret %c
}
# GCM program in "Figure 3 x's definition does not dominate it's use"
#
# SSA contruction will insert phi instruction for "x" in @if_false
# preventing the "add" in @if_false from being moved to @if_true
function $gcm_test2 (w %a){
@start
%f =w copy 1
%x =w copy 0
%s.0 =w copy 0
@loop
%s.1 = w phi @start %s.0, @if_false %s.2
jnz %a, @if, @end
@if
jnz %f, @if_true, @if_false
@if_true
%f =w copy 0
%x =w add %x, 1
@if_false
%s.2 =w add %s.1, %x
jmp @loop
@end
ret
}

17
test/_load-elim.ssa Normal file
View File

@ -0,0 +1,17 @@
# GCM can eliminate unused add/load instructions
export
function w $f(l %p, w %c) {
@start
jnz %c, @true, @false
@true
%p1 =l add %p, 4
%v1 =w loaduw %p1
jmp @end
@false
%p2 =l add %p, 4
%v2 =w loaduw %p2
jmp @end
@end
ret 0
}

View File

@ -28,7 +28,7 @@ function $test() {
# >>> driver
# #include <stdio.h>
# struct four { long l; char c; int i; };
# struct four { long long l; char c; int i; };
# extern void test(void);
# int F(int a0, int a1, int a2, int a3, struct four s, int a6) {
# printf("%d %d %d %d %d %d %d\n",

View File

@ -107,7 +107,7 @@ function $test() {
# typedef struct { int i; } st2;
# typedef struct { float f; int i; } st3;
# typedef struct { int i; double d; } st4;
# typedef struct { float f; long l; } st5;
# typedef struct { float f; long long l; } st5;
# typedef struct { char t[16]; } st6;
# typedef struct { float f; double d; } st7;
# typedef struct { int i[4]; } st8;

View File

@ -10,6 +10,7 @@ type :sd = { s, d } # ditto
type :ww = { w, w } # in a single gp reg
type :lb = { l, b } # in two gp regs
type :big = { b 17 } # by reference
type :ddd = { d, d, d} # big hfa on arm64
data $ctoqbestr = { b "c->qbe(%d)", b 0 }
data $emptystr = { b 0 }
@ -112,6 +113,14 @@ function $qfn10(w %p0, w %p1, w %p2, w %p3, w %p4, w %p5, w %p6, w %p7, :big %p8
%r1 =w call $puts(l $emptystr)
ret
}
export
function $qfn11(:ddd %p0) {
@start
%r0 =w call $printf(l $ctoqbestr, ..., w 11)
call $pddd(l %p0)
%r1 =w call $puts(l $emptystr)
ret
}
export
function w $main() {
@ -128,6 +137,7 @@ function w $main() {
call $cfn8(w 0, w 0, w 0, w 0, w 0, w 0, w 0, w 0, :lb $lb)
call $cfn9(:big $big)
call $cfn10(w 0, w 0, w 0, w 0, w 0, w 0, w 0, w 0, :big $big, s s_10.10, l 11)
call $cfn11(:ddd $ddd)
ret 0
}
@ -140,8 +150,9 @@ function w $main() {
# typedef struct { float s0, s1; } Sss;
# typedef struct { float s; double d; } Ssd;
# typedef struct { int w0, w1; } Sww;
# typedef struct { long l; char b; } Slb;
# typedef struct { long long l; char b; } Slb;
# typedef struct { char b[17]; } Sbig;
# typedef struct { double d0, d1, d2; } Sddd;
# Sfi1 zfi1, fi1 = { -123, 4.56 };
# Sfi2 zfi2, fi2 = { 1.23, 456 };
# Sfi3 zfi3, fi3 = { 3.45, 567 };
@ -150,16 +161,18 @@ function w $main() {
# Sww zww, ww = { -123, -456 };
# Slb zlb, lb = { 123, 'z' };
# Sbig zbig, big = { "abcdefhijklmnopqr" };
# Sddd zddd, ddd = { 1.23, 45.6, 7.89 };
# void pfi1(Sfi1 *s) { printf(" { %d, %g }", s->h, s->s); }
# void pfi2(Sfi2 *s) { printf(" { %g, %d }", s->s, s->w); }
# void pfi3(Sfi3 *s) { printf(" { %g, %d }", s->s, s->u.w); }
# void pss(Sss *s) { printf(" { %g, %g }", s->s0, s->s1); }
# void psd(Ssd *s) { printf(" { %g, %g }", s->s, s->d); }
# void pww(Sww *s) { printf(" { %d, %d }", s->w0, s->w1); }
# void plb(Slb *s) { printf(" { %ld, '%c' }", s->l, s->b); }
# void plb(Slb *s) { printf(" { %lld, '%c' }", s->l, s->b); }
# void pbig(Sbig *s) { printf(" \"%.17s\"", s->b); }
# void pddd(Sddd *s) { printf(" { %g, %g, %g }", s->d0, s->d1, s->d2); }
# void pw(int w) { printf(" %d", w); }
# void pl(long l) { printf(" %ld", l); }
# void pl(long long l) { printf(" %lld", l); }
# void ps(float s) { printf(" %g", s); }
# void pd(double d) { printf(" %g", d); }
# /* --------------------------- */
@ -193,8 +206,8 @@ function w $main() {
# pss(&p0); puts("");
# qfn4(p0);
# }
# extern void qfn5(double, double, double, double, double, double, double, Sss, float, long);
# void cfn5(double p0, double p1, double p2, double p3, double p4, double p5, double p6, Sss p7, float p8, long p9) {
# extern void qfn5(double, double, double, double, double, double, double, Sss, float, long long);
# void cfn5(double p0, double p1, double p2, double p3, double p4, double p5, double p6, Sss p7, float p8, long long p9) {
# printf("qbe->c(%d)", 5);
# pss(&p7); ps(p8); pl(p9); puts("");
# qfn5(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
@ -223,12 +236,18 @@ function w $main() {
# pbig(&p0); puts("");
# qfn9(p0);
# }
# extern void qfn10(int, int, int, int, int, int, int, int, Sbig, float, long);
# void cfn10(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, Sbig p8, float p9, long p10) {
# extern void qfn10(int, int, int, int, int, int, int, int, Sbig, float, long long);
# void cfn10(int p0, int p1, int p2, int p3, int p4, int p5, int p6, int p7, Sbig p8, float p9, long long p10) {
# printf("qbe->c(%d)", 10);
# pbig(&p8); ps(p9); pl(p10); puts("");
# qfn10(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
# }
# extern void qfn11(Sddd);
# void cfn11(Sddd p0) {
# printf("qbe->c(%d)", 11);
# pddd(&p0); puts("");
# qfn11(p0);
# }
# <<<
# >>> output
@ -254,4 +273,6 @@ function w $main() {
# c->qbe(9) "abcdefhijklmnopqr"
# qbe->c(10) "abcdefhijklmnopqr" 10.1 11
# c->qbe(10) "abcdefhijklmnopqr" 10.1 11
# qbe->c(11) { 1.23, 45.6, 7.89 }
# c->qbe(11) { 1.23, 45.6, 7.89 }
# <<<

20
test/abi9.ssa Normal file
View File

@ -0,0 +1,20 @@
type :obj = { l, l, l, l }
export
function :obj $f(l %self) {
@_0
%_1 =l alloc8 16
storel 77, %_1
ret %_1
}
# >>> driver
# #include <stdio.h>
# typedef struct { long long a, b, c, d; } obj;
# extern obj f();
# int main() { obj ret = f(); printf("%lld\n", ret.a); return 0; }
# <<<
# >>> output
# 77
# <<<

21
test/alias1.ssa Normal file
View File

@ -0,0 +1,21 @@
export function w $main() {
@start
%a =l alloc4 4
%b =l alloc4 4
storew 4, %a
storew 5, %b
@loop
# %mem will be %a and %b successively,
# but we do not know it when processing
# the phi because %b goes through a cpy
%mem =l phi @start %a, @loop %bcpy
%w =w load %mem
%eq5 =w ceqw %w, 5
%bcpy =l copy %b
jnz %eq5, @exit, @loop
@exit
ret 0
}

View File

@ -1,3 +1,4 @@
# skip amd64_win (no signals on win32)
# test amd64 addressing modes
export

15
test/copy.ssa Normal file
View File

@ -0,0 +1,15 @@
export function w $f() {
@start
%x0 =w loadsb $a
# the extension must not be eliminated
# even though the load already extended
%x1 =l extsb %x0
%c =w ceql %x1, -1
ret %c
}
# >>> driver
# char a = -1;
# extern int f();
# int main() { return !(f() == 1); }
# <<<

View File

@ -1,4 +1,4 @@
# skip arm64 arm64_apple rv64
# skip arm64 arm64_apple rv64 amd64_win
# a hack example,
# we use a dark type to get
# a pointer to the stack.

View File

@ -72,6 +72,7 @@ function l $dtol(d %f) {
# >>> driver
# #include <float.h>
# #include <limits.h>
#
# extern float fneg(float);
@ -113,15 +114,19 @@ function l $dtol(d %f) {
# return 7;
# }
# for (i=0; i<sizeof(fin)/sizeof(fin[0]); i++) {
# if (stol((float)fin[i]) != (unsigned long long)(float)fin[i])
# return 8;
# if (fin[i] >= 1LL << DBL_MANT_DIG)
# break;
# if (dtol(fin[i]) != (unsigned long long)fin[i])
# return 9;
# return 8;
# if((unsigned long long)fin[i] > UINT_MAX)
# continue;
# if (stow((float)fin[i]) != (unsigned int)(float)fin[i])
# return 10;
# if (dtow(fin[i]) != (unsigned int)fin[i])
# return 9;
# if (fin[i] >= 1LL << FLT_MANT_DIG)
# continue;
# if (stol((float)fin[i]) != (unsigned long long)(float)fin[i])
# return 10;
# if (stow((float)fin[i]) != (unsigned int)(float)fin[i])
# return 11;
# }
# return 0;

19
test/gvn1.ssa Normal file
View File

@ -0,0 +1,19 @@
export
function w $test(w %p1, w %p2) {
@start
@entry
%t1 =w copy 1
jnz %t1, @live, @dead1
@live
%t2 =w add %p1, %p2
ret %t2
@dead1
%t2 =w add %p1, %p2 # live ins in dead blk
@dead2
jnz %t1, @live, @dead1
}
# >>> driver
# extern int test(int p1, int p2);
# int main() { return test(1, 2) != 3; }
# <<<

31
test/gvn2.ssa Normal file
View File

@ -0,0 +1,31 @@
# GVN 0/non-0 inference removes @yesyes, @yesno, @noyes, @nono
export
function w $test(w %c) {
@start
jnz %c, @yes, @no
@yes
%c0 =w cnew %c, 0
jnz %c0, @yesyes, @yesno
@yesyes
%rc =w copy 1
jmp @end
@yesno
%rc =w copy 111
jmp @end
@no
%c1 =w cnew %c, 0
jnz %c1, @noyes, @nono
@noyes
%rc =w copy 222
jmp @end
@nono
%rc =w copy 0
@end
ret %rc
}
# >>> driver
# int test(int);
# int main(void) { return test(0); }
# <<<

238
test/ifc.ssa Normal file
View File

@ -0,0 +1,238 @@
export
function l $ifc1(l %v0, l %v1, w %c) {
@start
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifc2(l %v0, l %v1, w %p) {
@start
%c =w cnew %p, 42
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifc3(l %v0, l %v1, w %p) {
@start
%c =w cugtw %p, 42
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifclts(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w clts %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcles(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cles %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcgts(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cgts %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcges(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cges %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifceqs(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w ceqs %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcnes(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cnes %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcos(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cos %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
export
function l $ifcuos(s %s0, s %s1, l %v0, l %v1) {
@start
%c =w cuos %s0, %s1
jnz %c, @true, @false
@true
%v =l copy %v1
jmp @end
@false
%v =l copy %v0
jmp @end
@end
ret %v
}
# >>> driver
# extern long ifc1(long, long, int);
# extern long ifc2(long, long, int);
# extern long ifc3(long, long, int);
# extern long ifclts(float, float, long, long);
# extern long ifcles(float, float, long, long);
# extern long ifcgts(float, float, long, long);
# extern long ifcges(float, float, long, long);
# extern long ifceqs(float, float, long, long);
# extern long ifcnes(float, float, long, long);
# extern long ifcos(float, float, long, long);
# extern long ifcuos(float, float, long, long);
# int main() {
# return
# ifc1(7, 5, 0) != 7
# || ifc1(7, 5, 1) != 5
# || ifc1(7, 5, 33) != 5
# || ifc2(7, 5, 42) != 7
# || ifc2(7, 5, 41) != 5
# || ifc2(7, 5, 43) != 5
# || ifc3(7, 5, 42) != 7
# || ifc3(7, 5, 41) != 7
# || ifc3(7, 5, 43) != 5
# || ifclts(5.0f, 6.0f, 7, 5) != 5
# || ifclts(5.0f, 5.0f, 7, 5) != 7
# || ifclts(5.0f, 4.0f, 7, 5) != 7
# || ifclts(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifclts(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifclts(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcles(5.0f, 6.0f, 7, 5) != 5
# || ifcles(5.0f, 5.0f, 7, 5) != 5
# || ifcles(5.0f, 4.0f, 7, 5) != 7
# || ifcles(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcles(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcles(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcgts(5.0f, 6.0f, 7, 5) != 7
# || ifcgts(5.0f, 5.0f, 7, 5) != 7
# || ifcgts(5.0f, 4.0f, 7, 5) != 5
# || ifcgts(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcgts(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcgts(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcges(5.0f, 6.0f, 7, 5) != 7
# || ifcges(5.0f, 5.0f, 7, 5) != 5
# || ifcges(5.0f, 4.0f, 7, 5) != 5
# || ifcges(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcges(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcges(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifceqs(5.0f, 6.0f, 7, 5) != 7
# || ifceqs(5.0f, 5.0f, 7, 5) != 5
# || ifceqs(5.0f, 4.0f, 7, 5) != 7
# || ifceqs(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifceqs(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifceqs(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcnes(5.0f, 6.0f, 7, 5) != 5
# || ifcnes(5.0f, 5.0f, 7, 5) != 7
# || ifcnes(5.0f, 4.0f, 7, 5) != 5
# || ifcnes(5.0f, 0.0f/0.0f, 7, 5) != 5
# || ifcnes(0.0f/0.0f, 5.0f, 7, 5) != 5
# || ifcnes(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 5
# || ifcos(5.0f, 6.0f, 7, 5) != 5
# || ifcos(5.0f, 5.0f, 7, 5) != 5
# || ifcos(5.0f, 4.0f, 7, 5) != 5
# || ifcos(5.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcos(0.0f/0.0f, 5.0f, 7, 5) != 7
# || ifcos(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 7
# || ifcuos(5.0f, 6.0f, 7, 5) != 7
# || ifcuos(5.0f, 5.0f, 7, 5) != 7
# || ifcuos(5.0f, 4.0f, 7, 5) != 7
# || ifcuos(5.0f, 0.0f/0.0f, 7, 5) != 5
# || ifcuos(0.0f/0.0f, 5.0f, 7, 5) != 5
# || ifcuos(0.0f/0.0f, 0.0f/0.0f, 7, 5) != 5
# ;
# }
# <<<

64
test/isel4.ssa Normal file
View File

@ -0,0 +1,64 @@
# amd64 address-folding stress
export function w $f0(l %a, l %b) {
@start
%c =l add %b, 2
%d =l mul %c, 4
%e =l add %a, %d
%q =l loadw %e
ret %q
}
export function w $f1(l %a, l %b) {
@start
%c =l add 1, %b
%f =l add %c, 1
%d =l mul %f, 4
%e =l add %d, %a
%q =l loadw %e
ret %q
}
export function w $f2(l %a, l %b) {
@start
%l =l mul %b, 4
%d =l add 8, %l
%e =l add %a, %d
%q =l loadw %e
ret %q
}
# fixme: folding is not good here
export function w $f3(l %a, l %b) {
@start
%l =l mul %b, 4
%d =l add 4, %l
%f =l add 4, %d
%e =l add %a, %f
%q =l loadw %e
ret %q
}
export function w $f4(l %a, l %b) {
@start
%c =l add 1, %b
%d =l mul %c, 4
%e =l add 4, %d
%f =l add %e, %a
%q =l loadw %f
ret %q
}
# >>> driver
# int a[] = {1, 2, 3, 4};
# typedef int loadf(int *, long long);
# extern loadf f0, f1, f2, f3, f4;
# loadf *fns[] = {&f0, &f1, &f2, &f3, &f4, 0};
# int main() {
# loadf **f;
# int n;
# for (n=1,f=fns; *f; f++,n++)
# if ((*f)(a, 1) != 4) return n;
# return 0;
# }
# <<<

16
test/isel5.ssa Normal file
View File

@ -0,0 +1,16 @@
# make sure the local symbols used for
# fp constants do not get a _ prefix
# on apple arm hardware
export function w $main() {
@start
%r =d copy d_1.2
%x =w call $printf(l $fmt, ..., d %r)
ret 0
}
data $fmt = { b "%.06f\n", b 0 }
# >>> output
# 1.200000
# <<<

38
test/isel6.ssa Normal file
View File

@ -0,0 +1,38 @@
# make sure large consts are lowered
# without an offset
# i.e. not movq $9223372036854775807, 64(%rax)
export function w $main() {
@_0
%_1 =w call $myfunc(l 1, l 2, l 3, l 4, l 5, l 6, l 7, l 8, l 9223372036854775807)
ret 0
}
# >>> driver
# #include <stdio.h>
# #include <stdint.h>
# #include <inttypes.h>
# void myfunc(int64_t a, int64_t b, int64_t c, int64_t d, int64_t e, int64_t f, int64_t g, int64_t h, int64_t i) {
# printf("%" PRId64 "\n", a);
# printf("%" PRId64 "\n", b);
# printf("%" PRId64 "\n", c);
# printf("%" PRId64 "\n", d);
# printf("%" PRId64 "\n", e);
# printf("%" PRId64 "\n", f);
# printf("%" PRId64 "\n", g);
# printf("%" PRId64 "\n", h);
# printf("%" PRId64 "\n", i);
# }
# <<<
# >>> output
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9223372036854775807
# <<<

View File

@ -1,3 +1,4 @@
# skip amd64_win (pthread and tls not implemented)
thread data $i = align 4 {w 42}
data $fmti = align 1 {b "i%d==%d\n", b 0}

View File

@ -29,7 +29,7 @@ char *tok[] = {
"function", "type", "data", "section", "align", "dbgfile",
"blit", "l", "w", "sh", "uh", "h", "sb", "ub", "b",
"d", "s", "z", "loadw", "loadl", "loads", "loadd",
"alloc1", "alloc2",
"alloc1", "alloc2", "thread", "common",
};
enum {

64
tools/log2.c Normal file
View File

@ -0,0 +1,64 @@
#include <assert.h>
#include <stdio.h>
typedef unsigned long long ullong;
char seen[64];
ullong rbg = 0x1e0298f7a7e;
int
bit()
{
int bit;
bit = rbg & 1;
rbg >>= 1;
return bit;
}
int
search(ullong n, int b, ullong *out)
{
int i, x;
ullong y, z;
if (b == 64) {
*out = n;
return 1;
}
x = 63 & ((n << (63 - b)) >> 58);
assert(!(x & 0) && x <= 62);
y = bit();
for (i=0; i<2; i++) {
z = x | (y << 5);
if (!seen[z]) {
seen[z] = (63-b)+1;
if (search(n | (y << b), b+1, out))
return 1;
seen[z] = 0;
}
y ^= 1;
}
return 0;
}
int
main()
{
ullong out;
int i;
if (search(0, 0, &out)) {
printf("0x%llx\n", out);
for (i=0; i<64; i++) {
printf((i&7) == 0 ? "\t" : " ");
printf("%2d,", seen[i]-1);
if ((i&7) == 7)
printf("\n");
}
} else
puts("not found");
}

3
tools/mgen/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.cm[iox]
*.o
mgen

1
tools/mgen/.ocp-indent Normal file
View File

@ -0,0 +1 @@
match_clause=4

16
tools/mgen/Makefile Normal file
View File

@ -0,0 +1,16 @@
BIN = mgen
SRC = \
match.ml \
fuzz.ml \
cgen.ml \
sexp.ml \
test.ml \
main.ml
$(BIN): $(SRC)
ocamlopt -o $(BIN) -g str.cmxa $(SRC)
clean:
rm -f *.cm? *.o $(BIN)
.PHONY: clean

420
tools/mgen/cgen.ml Normal file
View File

@ -0,0 +1,420 @@
open Match
type options =
{ pfx: string
; static: bool
; oc: out_channel }
type side = L | R
type id_pred =
| InBitSet of Int64.t
| Ge of int
| Eq of int
and id_test =
| Pred of (side * id_pred)
| And of id_test * id_test
type case_code =
| Table of ((int * int) * int) list
| IfThen of
{ test: id_test
; cif: case_code
; cthen: case_code option }
| Return of int
type case =
{ swap: bool
; code: case_code }
let cgen_case tmp nstates map =
let cgen_test ids =
match ids with
| [id] -> Eq id
| _ ->
let min_id =
List.fold_left min max_int ids in
if List.length ids = nstates - min_id
then Ge min_id
else begin
assert (nstates <= 64);
InBitSet
(List.fold_left (fun bs id ->
Int64.logor bs
(Int64.shift_left 1L id))
0L ids)
end
in
let symmetric =
let inverse ((l, r), x) = ((r, l), x) in
setify map = setify (List.map inverse map) in
let map =
let ordered ((l, r), _) = r <= l in
if symmetric then
List.filter ordered map
else map
in
let exception BailToTable in
try
let st =
match setify (List.map snd map) with
| [st] -> st
| _ -> raise BailToTable
in
(* the operation considered can only
* generate a single state *)
let pairs = List.map fst map in
let ls, rs = List.split pairs in
let ls = setify ls and rs = setify rs in
if List.length ls > 1 && List.length rs > 1 then
raise BailToTable;
{ swap = symmetric
; code =
let pl = Pred (L, cgen_test ls)
and pr = Pred (R, cgen_test rs) in
IfThen
{ test = And (pl, pr)
; cif = Return st
; cthen = Some (Return tmp) } }
with BailToTable ->
{ swap = symmetric
; code = Table map }
let show_op (_cls, op) =
"O" ^ show_op_base op
let indent oc i =
Printf.fprintf oc "%s" (String.sub "\t\t\t\t\t" 0 i)
let emit_swap oc i =
let pf m = Printf.fprintf oc m in
let pfi n m = indent oc n; pf m in
pfi i "if (l < r)\n";
pfi (i+1) "t = l, l = r, r = t;\n"
let gen_tables oc tmp pfx nstates (op, c) =
let i = 1 in
let pf m = Printf.fprintf oc m in
let pfi n m = indent oc n; pf m in
let ntables = ref 0 in
(* we must follow the order in which
* we visit code in emit_case, or
* else ntables goes out of sync *)
let base = pfx ^ show_op op in
let swap = c.swap in
let rec gen c =
match c with
| Table map ->
let name =
if !ntables = 0 then base else
base ^ string_of_int !ntables
in
assert (nstates <= 256);
if swap then
let n = nstates * (nstates + 1) / 2 in
pfi i "static uchar %stbl[%d] = {\n" name n
else
pfi i "static uchar %stbl[%d][%d] = {\n"
name nstates nstates;
for l = 0 to nstates - 1 do
pfi (i+1) "";
for r = 0 to nstates - 1 do
if not swap || r <= l then
begin
pf "%d"
(try List.assoc (l,r) map
with Not_found -> tmp);
pf ",";
end
done;
pf "\n";
done;
pfi i "};\n"
| IfThen {cif; cthen} ->
gen cif;
Option.iter gen cthen
| Return _ -> ()
in
gen c.code
let emit_case oc pfx no_swap (op, c) =
let fpf = Printf.fprintf in
let pf m = fpf oc m in
let pfi n m = indent oc n; pf m in
let rec side oc = function
| L -> fpf oc "l"
| R -> fpf oc "r"
in
let pred oc (s, pred) =
match pred with
| InBitSet bs -> fpf oc "BIT(%a) & %#Lx" side s bs
| Eq id -> fpf oc "%a == %d" side s id
| Ge id -> fpf oc "%d <= %a" id side s
in
let base = pfx ^ show_op op in
let swap = c.swap in
let ntables = ref 0 in
let rec code i c =
match c with
| Return id -> pfi i "return %d;\n" id
| Table map ->
let name =
if !ntables = 0 then base else
base ^ string_of_int !ntables
in
incr ntables;
if swap then
pfi i "return %stbl[(l + l*l)/2 + r];\n" name
else pfi i "return %stbl[l][r];\n" name
| IfThen ({test = And (And (t1, t2), t3)} as r) ->
code i @@ IfThen
{r with test = And (t1, And (t2, t3))}
| IfThen {test = And (Pred p, t); cif; cthen} ->
pfi i "if (%a)\n" pred p;
code i (IfThen {test = t; cif; cthen})
| IfThen {test = Pred p; cif; cthen} ->
pfi i "if (%a) {\n" pred p;
code (i+1) cif;
pfi i "}\n";
Option.iter (code i) cthen
in
pfi 1 "case %s:\n" (show_op op);
if not no_swap && c.swap then
emit_swap oc 2;
code 2 c.code
let emit_list
?(limit=60) ?(cut_before_sep=false)
~col ~indent:i ~sep ~f oc l =
let sl = String.length sep in
let rstripped_sep, rssl =
if sep.[sl - 1] = ' ' then
String.sub sep 0 (sl - 1), sl - 1
else sep, sl
in
let lstripped_sep, lssl =
if sep.[0] = ' ' then
String.sub sep 1 (sl - 1), sl - 1
else sep, sl
in
let rec line col acc = function
| [] -> (List.rev acc, [])
| s :: l ->
let col = col + sl + String.length s in
let no_space =
if cut_before_sep || l = [] then
col > limit
else
col + rssl > limit
in
if no_space then
(List.rev acc, s :: l)
else
line col (s :: acc) l
in
let rec go col l =
if l = [] then () else
let ll, l = line col [] l in
Printf.fprintf oc "%s" (String.concat sep ll);
if l <> [] && cut_before_sep then begin
Printf.fprintf oc "\n";
indent oc i;
Printf.fprintf oc "%s" lstripped_sep;
go (8*i + lssl) l
end else if l <> [] then begin
Printf.fprintf oc "%s\n" rstripped_sep;
indent oc i;
go (8*i) l
end else ()
in
go col (List.map f l)
let emit_numberer opts n =
let pf m = Printf.fprintf opts.oc m in
let tmp = (atom_state n Tmp).id in
let con = (atom_state n AnyCon).id in
let nst = Array.length n.states in
let cases =
StateMap.by_ops n.statemap |>
List.map (fun (op, map) ->
(op, cgen_case tmp nst map))
in
let all_swap =
List.for_all (fun (_, c) -> c.swap) cases in
(* opn() *)
if opts.static then pf "static ";
pf "int\n";
pf "%sopn(int op, int l, int r)\n" opts.pfx;
pf "{\n";
cases |> List.iter
(gen_tables opts.oc tmp opts.pfx nst);
if List.exists (fun (_, c) -> c.swap) cases then
pf "\tint t;\n\n";
if all_swap then emit_swap opts.oc 1;
pf "\tswitch (op) {\n";
cases |> List.iter
(emit_case opts.oc opts.pfx all_swap);
pf "\tdefault:\n";
pf "\t\treturn %d;\n" tmp;
pf "\t}\n";
pf "}\n\n";
(* refn() *)
if opts.static then pf "static ";
pf "int\n";
pf "%srefn(Ref r, Num *tn, Con *con)\n" opts.pfx;
pf "{\n";
let cons =
List.filter_map (function
| (Con c, s) -> Some (c, s.id)
| _ -> None)
n.atoms
in
if cons <> [] then
pf "\tint64_t n;\n\n";
pf "\tswitch (rtype(r)) {\n";
pf "\tcase RTmp:\n";
if tmp <> 0 then begin
assert
(List.exists (fun (_, s) ->
s.id = 0
) n.atoms &&
(* no temp should ever get state 0 *)
List.for_all (fun (a, s) ->
s.id <> 0 ||
match a with
| AnyCon | Con _ -> true
| _ -> false
) n.atoms);
pf "\t\tif (!tn[r.val].n)\n";
pf "\t\t\ttn[r.val].n = %d;\n" tmp;
end;
pf "\t\treturn tn[r.val].n;\n";
pf "\tcase RCon:\n";
if cons <> [] then begin
pf "\t\tif (con[r.val].type != CBits)\n";
pf "\t\t\treturn %d;\n" con;
pf "\t\tn = con[r.val].bits.i;\n";
cons |> inverse |> group_by_fst
|> List.iter (fun (id, cs) ->
pf "\t\tif (";
emit_list ~cut_before_sep:true
~col:20 ~indent:2 ~sep:" || "
~f:(fun c -> "n == " ^ Int64.to_string c)
opts.oc cs;
pf ")\n";
pf "\t\t\treturn %d;\n" id
);
end;
pf "\t\treturn %d;\n" con;
pf "\tdefault:\n";
pf "\t\treturn INT_MIN;\n";
pf "\t}\n";
pf "}\n\n";
(* match[]: patterns per state *)
if opts.static then pf "static ";
pf "bits %smatch[%d] = {\n" opts.pfx nst;
n.states |> Array.iteri (fun sn s ->
let tops =
List.filter_map (function
| Top ("$" | "%") -> None
| Top r -> Some ("BIT(P" ^ r ^ ")")
| _ -> None) s.point |> setify
in
if tops <> [] then
pf "\t[%d] = %s,\n"
sn (String.concat " | " tops);
);
pf "};\n\n"
let var_id vars f =
List.mapi (fun i x -> (x, i)) vars |>
List.assoc f
let compile_action vars act =
let pcs = Hashtbl.create 100 in
let rec gen pc (act: Action.t) =
try
[10 + Hashtbl.find pcs act.id]
with Not_found ->
let code =
match act.node with
| Action.Stop ->
[0]
| Action.Push (sym, k) ->
let c = if sym then 1 else 2 in
[c] @ gen (pc + 1) k
| Action.Set (v, {node = Action.Pop k; _})
| Action.Set (v, ({node = Action.Stop; _} as k)) ->
let v = var_id vars v in
[3; v] @ gen (pc + 2) k
| Action.Set _ ->
(* for now, only atomic patterns can be
* tied to a variable, so Set must be
* followed by either Pop or Stop *)
assert false
| Action.Pop k ->
[4] @ gen (pc + 1) k
| Action.Switch cases ->
let cases =
inverse cases |> group_by_fst |>
List.sort (fun (_, cs1) (_, cs2) ->
let n1 = List.length cs1
and n2 = List.length cs2 in
compare n2 n1)
in
(* the last case is the one with
* the max number of entries *)
let cases = List.rev (List.tl cases)
and last = fst (List.hd cases) in
let ncases =
List.fold_left (fun n (_, cs) ->
List.length cs + n)
0 cases
in
let body_off = 2 + 2 * ncases + 1 in
let pc, tbl, body =
List.fold_left
(fun (pc, tbl, body) (a, cs) ->
let ofs = body_off + List.length body in
let case = gen pc a in
let pc = pc + List.length case in
let body = body @ case in
let tbl =
List.fold_left (fun tbl c ->
tbl @ [c; ofs]
) tbl cs
in
(pc, tbl, body))
(pc + body_off, [], [])
cases
in
let ofs = body_off + List.length body in
let tbl = tbl @ [ofs] in
assert (2 + List.length tbl = body_off);
[5; ncases] @ tbl @ body @ gen pc last
in
if act.node <> Action.Stop then
Hashtbl.replace pcs act.id pc;
code
in
gen 0 act
let emit_matchers opts ms =
let pf m = Printf.fprintf opts.oc m in
if opts.static then pf "static ";
pf "uchar *%smatcher[] = {\n" opts.pfx;
List.iter (fun (vars, pname, m) ->
pf "\t[P%s] = (uchar[]){\n" pname;
pf "\t\t";
let bytes = compile_action vars m in
emit_list
~col:16 ~indent:2 ~sep:","
~f:string_of_int opts.oc bytes;
pf "\n";
pf "\t},\n")
ms;
pf "};\n\n"
let emit_c opts n =
emit_numberer opts n

413
tools/mgen/fuzz.ml Normal file
View File

@ -0,0 +1,413 @@
(* fuzz the tables and matchers generated *)
open Match
module Buffer: sig
type 'a t
val create: ?capacity:int -> unit -> 'a t
val reset: 'a t -> unit
val size: 'a t -> int
val get: 'a t -> int -> 'a
val set: 'a t -> int -> 'a -> unit
val push: 'a t -> 'a -> unit
end = struct
type 'a t =
{ mutable size: int
; mutable data: 'a array }
let mk_array n = Array.make n (Obj.magic 0)
let create ?(capacity = 10) () =
if capacity < 0 then invalid_arg "Buffer.make";
{size = 0; data = mk_array capacity}
let reset b = b.size <- 0
let size b = b.size
let get b n =
if n >= size b then invalid_arg "Buffer.get";
b.data.(n)
let set b n x =
if n >= size b then invalid_arg "Buffer.set";
b.data.(n) <- x
let push b x =
let cap = Array.length b.data in
if size b = cap then begin
let data = mk_array (2 * cap + 1) in
Array.blit b.data 0 data 0 cap;
b.data <- data
end;
let sz = size b in
b.size <- sz + 1;
set b sz x
end
let binop_state n op s1 s2 =
let key = K (op, s1, s2) in
try StateMap.find key n.statemap
with Not_found -> atom_state n Tmp
type id = int
type term_data =
| Binop of op * id * id
| Leaf of atomic_pattern
type term =
{ id: id
; data: term_data
; state: p state }
let pp_term fmt (ta, id) =
let fpf x = Format.fprintf fmt x in
let rec pp _fmt id =
match ta.(id).data with
| Leaf (Con c) -> fpf "%Ld" c
| Leaf AnyCon -> fpf "$%d" id
| Leaf Tmp -> fpf "%%%d" id
| Binop (op, id1, id2) ->
fpf "@[(%s@%d:%d @[<hov>%a@ %a@])@]"
(show_op op) id ta.(id).state.id
pp id1 pp id2
in pp fmt id
(* A term pool is a deduplicated set of term
* that maintains nodes numbering using the
* statemap passed at creation time *)
module TermPool = struct
type t =
{ terms: term Buffer.t
; hcons: (term_data, id) Hashtbl.t
; numbr: numberer }
let create numbr =
{ terms = Buffer.create ()
; hcons = Hashtbl.create 100
; numbr }
let reset tp =
Buffer.reset tp.terms;
Hashtbl.clear tp.hcons
let size tp = Buffer.size tp.terms
let term tp id = Buffer.get tp.terms id
let mk_leaf tp atm =
let data = Leaf atm in
match Hashtbl.find tp.hcons data with
| id -> term tp id
| exception Not_found ->
let id = Buffer.size tp.terms in
let state = atom_state tp.numbr atm in
Buffer.push tp.terms {id; data; state};
Hashtbl.add tp.hcons data id;
term tp id
let mk_binop tp op t1 t2 =
let data = Binop (op, t1.id, t2.id) in
match Hashtbl.find tp.hcons data with
| id -> term tp id
| exception Not_found ->
let id = Buffer.size tp.terms in
let state =
binop_state tp.numbr op t1.state t2.state
in
Buffer.push tp.terms {id; data; state};
Hashtbl.add tp.hcons data id;
term tp id
let rec add_pattern tp = function
| Bnr (op, p1, p2) ->
let t1 = add_pattern tp p1 in
let t2 = add_pattern tp p2 in
mk_binop tp op t1 t2
| Atm atm -> mk_leaf tp atm
| Var (_, atm) -> add_pattern tp (Atm atm)
let explode_term tp id =
let rec aux tms n id =
let t = term tp id in
match t.data with
| Leaf _ -> (n, {t with id = n} :: tms)
| Binop (op, id1, id2) ->
let n1, tms = aux tms n id1 in
let n = n1 + 1 in
let n2, tms = aux tms n id2 in
let n = n2 + 1 in
(n, { t with data = Binop (op, n1, n2)
; id = n } :: tms)
in
let n, tms = aux [] 0 id in
Array.of_list (List.rev tms), n
end
module R = Random
(* uniform pick in a list *)
let list_pick l =
let rec aux n l x =
match l with
| [] -> x
| y :: l ->
if R.int (n + 1) = 0 then
aux (n + 1) l y
else
aux (n + 1) l x
in
match l with
| [] -> invalid_arg "list_pick"
| x :: l -> aux 1 l x
let term_pick ~numbr =
let ops =
if numbr.ops = [] then
numbr.ops <-
(StateMap.fold (fun k _ ops ->
match k with
| K (op, _, _) -> op :: ops)
numbr.statemap [] |> setify);
numbr.ops
in
let rec gen depth =
(* exponential probability for leaves to
* avoid skewing towards shallow terms *)
let atm_prob = 0.75 ** float_of_int depth in
if R.float 1.0 <= atm_prob || ops = [] then
let atom, st = list_pick numbr.atoms in
(st, Atm atom)
else
let op = list_pick ops in
let s1, t1 = gen (depth - 1) in
let s2, t2 = gen (depth - 1) in
( binop_state numbr op s1 s2
, Bnr (op, t1, t2) )
in fun ~depth -> gen depth
exception FuzzError
let rec pattern_depth = function
| Bnr (_, p1, p2) ->
1 + max (pattern_depth p1) (pattern_depth p2)
| Atm _ -> 0
| Var (_, atm) -> pattern_depth (Atm atm)
let ( %% ) a b =
1e2 *. float_of_int a /. float_of_int b
let progress ?(width = 50) msg pct =
Format.eprintf "\x1b[2K\r%!";
let progress_bar fmt =
let n =
let fwidth = float_of_int width in
1 + int_of_float (pct *. fwidth /. 1e2)
in
Format.fprintf fmt " %s%s %.0f%%@?"
(String.concat "" (List.init n (fun _ -> "")))
(String.make (max 0 (width - n)) '-')
pct
in
Format.kfprintf progress_bar
Format.err_formatter msg
let fuzz_numberer rules numbr =
(* pick twice the max pattern depth so we
* have a chance to find non-trivial numbers
* for the atomic patterns in the rules *)
let depth =
List.fold_left (fun depth r ->
max depth (pattern_depth r.pattern))
0 rules * 2
in
(* fuzz until the term pool we are constructing
* is no longer growing fast enough; or we just
* went through sufficiently many iterations *)
let max_iter = 1_000_000 in
let low_insert_rate = 1e-2 in
let tp = TermPool.create numbr in
let rec loop new_stats i =
let (_, _, insert_rate) = new_stats in
if insert_rate <= low_insert_rate then () else
if i >= max_iter then () else
(* periodically update stats *)
let new_stats =
let (num, cnt, rate) = new_stats in
if num land 1023 = 0 then
let rate =
0.5 *. (rate +. float_of_int cnt /. 1023.)
in
progress " insert_rate=%.1f%%"
(i %% max_iter) (rate *. 1e2);
(num + 1, 0, rate)
else new_stats
in
(* create a term and check that its number is
* accurate wrt the rules *)
let st, term = term_pick ~numbr ~depth in
let state_matched =
List.filter_map (fun cu ->
match cu with
| Top ("$" | "%") -> None
| Top name -> Some name
| _ -> None)
st.point |> setify
in
let rule_matched =
List.filter_map (fun r ->
if pattern_match r.pattern term then
Some r.name
else None)
rules |> setify
in
if state_matched <> rule_matched then begin
let open Format in
let pp_str_list =
let pp_sep fmt () = fprintf fmt ",@ " in
pp_print_list ~pp_sep pp_print_string
in
eprintf "@.@[<v2>fuzz error for %s"
(show_pattern term);
eprintf "@ @[state matched: %a@]"
pp_str_list state_matched;
eprintf "@ @[rule matched: %a@]"
pp_str_list rule_matched;
eprintf "@]@.";
raise FuzzError;
end;
if state_matched = [] then
loop new_stats (i + 1)
else
(* add to the term pool *)
let old_size = TermPool.size tp in
let _ = TermPool.add_pattern tp term in
let new_stats =
let (num, cnt, rate) = new_stats in
if TermPool.size tp <> old_size then
(num + 1, cnt + 1, rate)
else
(num + 1, cnt, rate)
in
loop new_stats (i + 1)
in
loop (1, 0, 1.0) 0;
Format.eprintf
"@.@[ generated %.3fMiB of test terms@]@."
(float_of_int (Obj.reachable_words (Obj.repr tp))
/. 128. /. 1024.);
tp
let rec run_matcher stk m (ta, id as t) =
let state id = ta.(id).state.id in
match m.Action.node with
| Action.Switch cases ->
let m =
try List.assoc (state id) cases
with Not_found -> failwith "no switch case"
in
run_matcher stk m t
| Action.Push (sym, m) ->
let l, r =
match ta.(id).data with
| Leaf _ -> failwith "push on leaf"
| Binop (_, l, r) -> (l, r)
in
if sym && state l > state r
then run_matcher (l :: stk) m (ta, r)
else run_matcher (r :: stk) m (ta, l)
| Action.Pop m -> begin
match stk with
| id :: stk -> run_matcher stk m (ta, id)
| [] -> failwith "pop on empty stack"
end
| Action.Set (v, m) ->
(v, id) :: run_matcher stk m t
| Action.Stop -> []
let rec term_match p (ta, id) =
let (|>>) x f =
match x with None -> None | Some x -> f x
in
let atom_match a =
match ta.(id).data with
| Leaf a' -> pattern_match (Atm a) (Atm a')
| Binop _ -> pattern_match (Atm a) (Atm Tmp)
in
match p with
| Var (v, a) when atom_match a ->
Some [(v, id)]
| Atm a when atom_match a -> Some []
| (Atm _ | Var _) -> None
| Bnr (op, pl, pr) -> begin
match ta.(id).data with
| Binop (op', idl, idr) when op' = op ->
term_match pl (ta, idl) |>> fun l1 ->
term_match pr (ta, idr) |>> fun l2 ->
Some (l1 @ l2)
| _ -> None
end
let test_matchers tp numbr rules =
let {statemap = sm; states = sa; _} = numbr in
let total = ref 0 in
let matchers =
let htbl = Hashtbl.create (Array.length sa) in
List.map (fun r -> (r.name, r.pattern)) rules |>
group_by_fst |>
List.iter (fun (r, ps) ->
total := !total + List.length ps;
let pm = (ps, lr_matcher sm sa rules r) in
sa |> Array.iter (fun s ->
if List.mem (Top r) s.point then
Hashtbl.add htbl s.id pm));
htbl
in
let seen = Hashtbl.create !total in
for id = 0 to TermPool.size tp - 1 do
if id land 1023 = 0 ||
id = TermPool.size tp - 1 then begin
progress
" coverage=%.1f%%"
(id %% TermPool.size tp)
(Hashtbl.length seen %% !total)
end;
let t = TermPool.explode_term tp id in
Hashtbl.find_all matchers
(TermPool.term tp id).state.id |>
List.iter (fun (ps, m) ->
let norm = List.fast_sort compare in
let ok =
match norm (run_matcher [] m t) with
| asn -> `Match (List.exists (fun p ->
match term_match p t with
| None -> false
| Some asn' ->
if asn = norm asn' then begin
Hashtbl.replace seen p ();
true
end else false) ps)
| exception e -> `RunFailure e
in
if ok <> `Match true then begin
let open Format in
let pp_asn fmt asn =
fprintf fmt "@[<h>";
pp_print_list
~pp_sep:(fun fmt () -> fprintf fmt ";@ ")
(fun fmt (v, d) ->
fprintf fmt "@[%s←%d@]" v d)
fmt asn;
fprintf fmt "@]"
in
eprintf "@.@[<v2>matcher error for";
eprintf "@ @[%a@]" pp_term t;
begin match ok with
| `RunFailure e ->
eprintf "@ @[exception: %s@]"
(Printexc.to_string e)
| `Match (* false *) _ ->
let asn = run_matcher [] m t in
eprintf "@ @[assignment: %a@]"
pp_asn asn;
eprintf "@ @[<v2>could not match";
List.iter (fun p ->
eprintf "@ + @[%s@]"
(show_pattern p)) ps;
eprintf "@]"
end;
eprintf "@]@.";
raise FuzzError
end)
done;
Format.eprintf "@."

214
tools/mgen/main.ml Normal file
View File

@ -0,0 +1,214 @@
open Cgen
open Match
let mgen ~verbose ~fuzz path lofs input oc =
let info ?(level = 1) fmt =
if level <= verbose then
Printf.eprintf fmt
else
Printf.ifprintf stdout fmt
in
let rules =
match Sexp.(run_parser ppats) input with
| `Error (ps, err, loc) ->
Printf.eprintf "%s:%d:%d %s\n"
path (lofs + ps.Sexp.line) ps.Sexp.coln err;
Printf.eprintf "%s" loc;
exit 1
| `Ok rules -> rules
in
info "adding ac variants...%!";
let nparsed =
List.fold_left
(fun npats (_, _, ps) ->
npats + List.length ps)
0 rules
in
let varsmap = Hashtbl.create 10 in
let rules =
List.concat_map (fun (name, vars, patterns) ->
(try assert (Hashtbl.find varsmap name = vars)
with Not_found -> ());
Hashtbl.replace varsmap name vars;
List.map
(fun pattern -> {name; vars; pattern})
(List.concat_map ac_equiv patterns)
) rules
in
info " %d -> %d patterns\n"
nparsed (List.length rules);
let rnames =
setify (List.map (fun r -> r.name) rules) in
info "generating match tables...%!";
let sa, am, sm = generate_table rules in
let numbr = make_numberer sa am sm in
info " %d states, %d rules\n"
(Array.length sa) (StateMap.cardinal sm);
if verbose >= 2 then begin
info "-------------\nstates:\n";
Array.iteri (fun i s ->
info " state %d: %s\n"
i (show_pattern s.seen)) sa;
info "-------------\nstatemap:\n";
Test.print_sm stderr sm;
info "-------------\n";
end;
info "generating matchers...\n";
let matchers =
List.map (fun rname ->
info "+ %s...%!" rname;
let m = lr_matcher sm sa rules rname in
let vars = Hashtbl.find varsmap rname in
info " %d nodes\n" (Action.size m);
info ~level:2 " -------------\n";
info ~level:2 " automaton:\n";
info ~level:2 "%s\n"
(Format.asprintf " @[%a@]" Action.pp m);
info ~level:2 " ----------\n";
(vars, rname, m)
) rnames
in
if fuzz then begin
info ~level:0 "fuzzing statemap...\n";
let tp = Fuzz.fuzz_numberer rules numbr in
info ~level:0 "testing %d patterns...\n"
(List.length rules);
Fuzz.test_matchers tp numbr rules
end;
info "emitting C...\n";
flush stderr;
let cgopts =
{ pfx = ""; static = true; oc = oc } in
emit_c cgopts numbr;
emit_matchers cgopts matchers;
()
let read_all ic =
let bufsz = 4096 in
let buf = Bytes.create bufsz in
let data = Buffer.create bufsz in
let read = ref 0 in
while
read := input ic buf 0 bufsz;
!read <> 0
do
Buffer.add_subbytes data buf 0 !read
done;
Buffer.contents data
let split_c src =
let begin_re, eoc_re, end_re =
let re = Str.regexp in
( re "mgen generated code"
, re "\\*/"
, re "end of generated code" )
in
let str_match regexp str =
try
let _: int =
Str.search_forward regexp str 0
in true
with Not_found -> false
in
let rec go st lofs pfx rules lines =
let line, lines =
match lines with
| [] ->
failwith (
match st with
| `Prefix -> "could not find mgen section"
| `Rules -> "mgen rules not terminated"
| `Skip -> "mgen section not terminated"
)
| l :: ls -> (l, ls)
in
match st with
| `Prefix ->
let pfx = line :: pfx in
if str_match begin_re line
then
let lofs = List.length pfx in
go `Rules lofs pfx rules lines
else go `Prefix 0 pfx rules lines
| `Rules ->
let pfx = line :: pfx in
if str_match eoc_re line
then go `Skip lofs pfx rules lines
else go `Rules lofs pfx (line :: rules) lines
| `Skip ->
if str_match end_re line then
let join = String.concat "\n" in
let pfx = join (List.rev pfx) ^ "\n\n"
and rules = join (List.rev rules)
and sfx = join (line :: lines)
in (lofs, pfx, rules, sfx)
else go `Skip lofs pfx rules lines
in
let lines = String.split_on_char '\n' src in
go `Prefix 0 [] [] lines
let () =
let usage_msg =
"mgen [--fuzz] [--verbose <N>] <file>" in
let fuzz_arg = ref false in
let verbose_arg = ref 0 in
let input_paths = ref [] in
let anon_fun filename =
input_paths := filename :: !input_paths in
let speclist =
[ ( "--fuzz", Arg.Set fuzz_arg
, " Fuzz tables and matchers" )
; ( "--verbose", Arg.Set_int verbose_arg
, "<N> Set verbosity level" )
; ( "--", Arg.Rest_all (List.iter anon_fun)
, " Stop argument parsing" ) ]
in
Arg.parse speclist anon_fun usage_msg;
let input_paths = !input_paths in
let verbose = !verbose_arg in
let fuzz = !fuzz_arg in
let input_path, input =
match input_paths with
| ["-"] -> ("-", read_all stdin)
| [path] -> (path, read_all (open_in path))
| _ ->
Printf.eprintf
"%s: single input file expected\n"
Sys.argv.(0);
Arg.usage speclist usage_msg; exit 1
in
let mgen = mgen ~verbose ~fuzz in
if Str.last_chars input_path 2 <> ".c"
then mgen input_path 0 input stdout
else
let tmp_path = input_path ^ ".tmp" in
Fun.protect
~finally:(fun () ->
try Sys.remove tmp_path with _ -> ())
(fun () ->
let lofs, pfx, rules, sfx = split_c input in
let oc = open_out tmp_path in
output_string oc pfx;
mgen input_path lofs rules oc;
output_string oc sfx;
close_out oc;
Sys.rename tmp_path input_path;
());
()

651
tools/mgen/match.ml Normal file
View File

@ -0,0 +1,651 @@
type cls = Kw | Kl | Ks | Kd
type op_base =
| Oadd
| Osub
| Omul
| Oor
| Oshl
| Oshr
type op = cls * op_base
let op_bases =
[Oadd; Osub; Omul; Oor; Oshl; Oshr]
let commutative = function
| (_, (Oadd | Omul | Oor)) -> true
| (_, _) -> false
let associative = function
| (_, (Oadd | Omul | Oor)) -> true
| (_, _) -> false
type atomic_pattern =
| Tmp
| AnyCon
| Con of int64
(* Tmp < AnyCon < Con k *)
type pattern =
| Bnr of op * pattern * pattern
| Atm of atomic_pattern
| Var of string * atomic_pattern
let is_atomic = function
| (Atm _ | Var _) -> true
| _ -> false
let show_op_base o =
match o with
| Oadd -> "add"
| Osub -> "sub"
| Omul -> "mul"
| Oor -> "or"
| Oshl -> "shl"
| Oshr -> "shr"
let show_op (k, o) =
show_op_base o ^
(match k with
| Kw -> "w"
| Kl -> "l"
| Ks -> "s"
| Kd -> "d")
let rec show_pattern p =
match p with
| Atm Tmp -> "%"
| Atm AnyCon -> "$"
| Atm (Con n) -> Int64.to_string n
| Var (v, p) ->
show_pattern (Atm p) ^ "'" ^ v
| Bnr (o, pl, pr) ->
"(" ^ show_op o ^
" " ^ show_pattern pl ^
" " ^ show_pattern pr ^ ")"
let get_atomic p =
match p with
| (Atm a | Var (_, a)) -> Some a
| _ -> None
let rec pattern_match p w =
match p with
| Var (_, p) ->
pattern_match (Atm p) w
| Atm Tmp ->
begin match get_atomic w with
| Some (Con _ | AnyCon) -> false
| _ -> true
end
| Atm (Con _) -> w = p
| Atm (AnyCon) ->
not (pattern_match (Atm Tmp) w)
| Bnr (o, pl, pr) ->
begin match w with
| Bnr (o', wl, wr) ->
o' = o &&
pattern_match pl wl &&
pattern_match pr wr
| _ -> false
end
type +'a cursor = (* a position inside a pattern *)
| Bnrl of op * 'a cursor * pattern
| Bnrr of op * pattern * 'a cursor
| Top of 'a
let rec fold_cursor c p =
match c with
| Bnrl (o, c', p') -> fold_cursor c' (Bnr (o, p, p'))
| Bnrr (o, p', c') -> fold_cursor c' (Bnr (o, p', p))
| Top _ -> p
let peel p x =
let once out (p, c) =
match p with
| Var (_, p) -> (Atm p, c) :: out
| Atm _ -> (p, c) :: out
| Bnr (o, pl, pr) ->
(pl, Bnrl (o, c, pr)) ::
(pr, Bnrr (o, pl, c)) :: out
in
let rec go l =
let l' = List.fold_left once [] l in
if List.length l' = List.length l
then l'
else go l'
in go [(p, Top x)]
let fold_pairs l1 l2 ini f =
let rec go acc = function
| [] -> acc
| a :: l1' ->
go (List.fold_left
(fun acc b -> f (a, b) acc)
acc l2) l1'
in go ini l1
let iter_pairs l f =
fold_pairs l l () (fun x () -> f x)
let inverse l =
List.map (fun (a, b) -> (b, a)) l
type 'a state =
{ id: int
; seen: pattern
; point: ('a cursor) list }
let rec binops side {point; _} =
List.filter_map (fun c ->
match c, side with
| Bnrl (o, c, r), `L -> Some ((o, c), r)
| Bnrr (o, l, c), `R -> Some ((o, c), l)
| _ -> None)
point
let group_by_fst l =
List.fast_sort (fun (a, _) (b, _) ->
compare a b) l |>
List.fold_left (fun (oo, l, res) (o', c) ->
match oo with
| None -> (Some o', [c], [])
| Some o when o = o' -> (oo, c :: l, res)
| Some o -> (Some o', [c], (o, l) :: res))
(None, [], []) |>
(function
| (None, _, _) -> []
| (Some o, l, res) -> (o, l) :: res)
let sort_uniq cmp l =
List.fast_sort cmp l |>
List.fold_left (fun (eo, l) e' ->
match eo with
| None -> (Some e', l)
| Some e when cmp e e' = 0 -> (eo, l)
| Some e -> (Some e', e :: l))
(None, []) |>
(function
| (None, _) -> []
| (Some e, l) -> List.rev (e :: l))
let setify l =
sort_uniq compare l
let normalize (point: ('a cursor) list) =
setify point
let next_binary tmp s1 s2 =
let pm w (_, p) = pattern_match p w in
let o1 = binops `L s1 |>
List.filter (pm s2.seen) |>
List.map fst in
let o2 = binops `R s2 |>
List.filter (pm s1.seen) |>
List.map fst in
List.map (fun (o, l) ->
o,
{ id = -1
; seen = Bnr (o, s1.seen, s2.seen)
; point = normalize (l @ tmp) })
(group_by_fst (o1 @ o2))
type p = string
module StateSet : sig
type t
val create: unit -> t
val add: t -> p state ->
[> `Added | `Found ] * p state
val iter: t -> (p state -> unit) -> unit
val elems: t -> (p state) list
end = struct
open Hashtbl.Make(struct
type t = p state
let equal s1 s2 = s1.point = s2.point
let hash s = Hashtbl.hash s.point
end)
type nonrec t =
{ h: int t
; mutable next_id: int }
let create () =
{ h = create 500; next_id = 0 }
let add set s =
assert (s.point = normalize s.point);
try
let id = find set.h s in
`Found, {s with id}
with Not_found -> begin
let id = set.next_id in
set.next_id <- id + 1;
add set.h s id;
`Added, {s with id}
end
let iter set f =
let f s id = f {s with id} in
iter f set.h
let elems set =
let res = ref [] in
iter set (fun s -> res := s :: !res);
!res
end
type table_key =
| K of op * p state * p state
module StateMap = struct
include Map.Make(struct
type t = table_key
let compare ka kb =
match ka, kb with
| K (o, sl, sr), K (o', sl', sr') ->
compare (o, sl.id, sr.id)
(o', sl'.id, sr'.id)
end)
let invert n sm =
let rmap = Array.make n [] in
iter (fun k {id; _} ->
match k with
| K (o, sl, sr) ->
rmap.(id) <-
(o, (sl.id, sr.id)) :: rmap.(id)
) sm;
Array.map group_by_fst rmap
let by_ops sm =
fold (fun tk s ops ->
match tk with
| K (op, l, r) ->
(op, ((l.id, r.id), s.id)) :: ops)
sm [] |> group_by_fst
end
type rule =
{ name: string
; vars: string list
; pattern: pattern }
let generate_table rl =
let states = StateSet.create () in
let rl =
(* these atomic patterns must occur in
* rules so that we are able to number
* all possible refs *)
[ { name = "$"; vars = []
; pattern = Atm AnyCon }
; { name = "%"; vars = []
; pattern = Atm Tmp } ] @ rl
in
(* initialize states *)
let ground =
List.concat_map
(fun r -> peel r.pattern r.name) rl |>
group_by_fst
in
let tmp = List.assoc (Atm Tmp) ground in
let con = List.assoc (Atm AnyCon) ground in
let atoms = ref [] in
let () =
List.iter (fun (seen, l) ->
let point =
if pattern_match (Atm Tmp) seen
then normalize (tmp @ l)
else normalize (con @ l)
in
let s = {id = -1; seen; point} in
let _, s = StateSet.add states s in
match get_atomic seen with
| Some atm -> atoms := (atm, s) :: !atoms
| None -> ()
) ground
in
(* setup loop state *)
let map = ref StateMap.empty in
let map_add k s' =
map := StateMap.add k s' !map
in
let flag = ref `Added in
let flagmerge = function
| `Added -> flag := `Added
| _ -> ()
in
(* iterate until fixpoint *)
while !flag = `Added do
flag := `Stop;
let statel = StateSet.elems states in
iter_pairs statel (fun (sl, sr) ->
next_binary tmp sl sr |>
List.iter (fun (o, s') ->
let flag', s' =
StateSet.add states s' in
flagmerge flag';
map_add (K (o, sl, sr)) s';
));
done;
let states =
StateSet.elems states |>
List.sort (fun s s' -> compare s.id s'.id) |>
Array.of_list
in
(states, !atoms, !map)
let intersperse x l =
let rec go left right out =
let out =
(List.rev left @ [x] @ right) ::
out in
match right with
| x :: right' ->
go (x :: left) right' out
| [] -> out
in go [] l []
let rec permute = function
| [] -> [[]]
| x :: l ->
List.concat (List.map
(intersperse x) (permute l))
(* build all binary trees with ordered
* leaves l *)
let rec bins build l =
let rec go l r out =
match r with
| [] -> out
| x :: r' ->
go (l @ [x]) r'
(fold_pairs
(bins build l)
(bins build r)
out (fun (l, r) out ->
build l r :: out))
in
match l with
| [] -> []
| [x] -> [x]
| x :: l -> go [x] l []
let products l ini f =
let rec go acc la = function
| [] -> f (List.rev la) acc
| xs :: l ->
List.fold_left (fun acc x ->
go acc (x :: la) l)
acc xs
in go ini [] l
(* combinatorial nuke... *)
let rec ac_equiv =
let rec alevel o = function
| Bnr (o', l, r) when o' = o ->
alevel o l @ alevel o r
| x -> [x]
in function
| Bnr (o, _, _) as p
when associative o ->
products
(List.map ac_equiv (alevel o p)) []
(fun choice out ->
List.concat_map
(bins (fun l r -> Bnr (o, l, r)))
(if commutative o
then permute choice
else [choice]) @ out)
| Bnr (o, l, r)
when commutative o ->
fold_pairs
(ac_equiv l) (ac_equiv r) []
(fun (l, r) out ->
Bnr (o, l, r) ::
Bnr (o, r, l) :: out)
| Bnr (o, l, r) ->
fold_pairs
(ac_equiv l) (ac_equiv r) []
(fun (l, r) out ->
Bnr (o, l, r) :: out)
| x -> [x]
module Action: sig
type node =
| Switch of (int * t) list
| Push of bool * t
| Pop of t
| Set of string * t
| Stop
and t = private
{ id: int; node: node }
val equal: t -> t -> bool
val size: t -> int
val stop: t
val mk_push: sym:bool -> t -> t
val mk_pop: t -> t
val mk_set: string -> t -> t
val mk_switch: int list -> (int -> t) -> t
val pp: Format.formatter -> t -> unit
end = struct
type node =
| Switch of (int * t) list
| Push of bool * t
| Pop of t
| Set of string * t
| Stop
and t =
{ id: int; node: node }
let equal a a' = a.id = a'.id
let size a =
let seen = Hashtbl.create 10 in
let rec node_size = function
| Switch l ->
List.fold_left
(fun n (_, a) -> n + size a) 0 l
| (Push (_, a) | Pop a | Set (_, a)) ->
size a
| Stop -> 0
and size {id; node} =
if Hashtbl.mem seen id
then 0
else begin
Hashtbl.add seen id ();
1 + node_size node
end
in
size a
let mk =
let hcons = Hashtbl.create 100 in
let fresh = ref 0 in
fun node ->
let id =
try Hashtbl.find hcons node
with Not_found ->
let id = !fresh in
Hashtbl.add hcons node id;
fresh := id + 1;
id
in
{id; node}
let stop = mk Stop
let mk_push ~sym a = mk (Push (sym, a))
let mk_pop a =
match a.node with
| Stop -> a
| _ -> mk (Pop a)
let mk_set v a = mk (Set (v, a))
let mk_switch ids f =
match List.map f ids with
| [] -> failwith "empty switch";
| c :: cs as cases ->
if List.for_all (equal c) cs then c
else
let cases = List.combine ids cases in
mk (Switch cases)
open Format
let rec pp_node fmt = function
| Switch l ->
fprintf fmt "@[<v>@[<v2>switch{";
let pp_case (c, a) =
let pp_sep fmt () = fprintf fmt "," in
fprintf fmt "@,@[<2>→%a:@ @[%a@]@]"
(pp_print_list ~pp_sep pp_print_int)
c pp a
in
inverse l |> group_by_fst |> inverse |>
List.iter pp_case;
fprintf fmt "@]@,}@]"
| Push (true, a) -> fprintf fmt "pushsym@ %a" pp a
| Push (false, a) -> fprintf fmt "push@ %a" pp a
| Pop a -> fprintf fmt "pop@ %a" pp a
| Set (v, a) -> fprintf fmt "set(%s)@ %a" v pp a
| Stop -> fprintf fmt ""
and pp fmt a = pp_node fmt a.node
end
(* a state is commutative if (a op b) enters
* it iff (b op a) enters it as well *)
let symmetric rmap id =
List.for_all (fun (_, l) ->
let l1, l2 =
List.filter (fun (a, b) -> a <> b) l |>
List.partition (fun (a, b) -> a < b)
in
setify l1 = setify (inverse l2))
rmap.(id)
(* left-to-right matching of a set of patterns;
* may raise if there is no lr matcher for the
* input rule *)
let lr_matcher statemap states rules name =
let rmap =
let nstates = Array.length states in
StateMap.invert nstates statemap
in
let exception Stuck in
(* the list of ids represents a class of terms
* whose root ends up being labelled with one
* such id; the gen function generates a matcher
* that will, given any such term, assign values
* for the Var nodes of one pattern in pats *)
let rec gen
: 'a. int list -> (pattern * 'a) list
-> (int -> (pattern * 'a) list -> Action.t)
-> Action.t
= fun ids pats k ->
Action.mk_switch (setify ids) @@ fun id_top ->
let sym = symmetric rmap id_top in
let id_ops =
if sym then
let ordered (a, b) = a <= b in
List.map (fun (o, l) ->
(o, List.filter ordered l))
rmap.(id_top)
else rmap.(id_top)
in
(* consider only the patterns that are
* compatible with the current id *)
let atm_pats, bin_pats =
List.filter (function
| Bnr (o, _, _), _ ->
List.exists
(fun (o', _) -> o' = o)
id_ops
| _ -> true) pats |>
List.partition
(fun (pat, _) -> is_atomic pat)
in
try
if bin_pats = [] then raise Stuck;
let pats_l =
List.map (function
| (Bnr (o, l, r), x) ->
(l, (o, x, r))
| _ -> assert false)
bin_pats
and pats_r =
List.map (fun (l, (o, x, r)) ->
(r, (o, l, x)))
and patstop =
List.map (fun (r, (o, l, x)) ->
(Bnr (o, l, r), x))
in
let id_pairs = List.concat_map snd id_ops in
let ids_l = List.map fst id_pairs
and ids_r id_left =
List.filter_map (fun (l, r) ->
if l = id_left then Some r else None)
id_pairs
in
(* match the left arm *)
Action.mk_push ~sym
(gen ids_l pats_l
@@ fun lid pats ->
(* then the right arm, considering
* only the remaining possible
* patterns and knowing that the
* left arm was numbered 'lid' *)
Action.mk_pop
(gen (ids_r lid) (pats_r pats)
@@ fun _rid pats ->
(* continue with the parent *)
k id_top (patstop pats)))
with Stuck ->
let atm_pats =
let seen = states.(id_top).seen in
List.filter (fun (pat, _) ->
pattern_match pat seen) atm_pats
in
if atm_pats = [] then raise Stuck else
let vars =
List.filter_map (function
| (Var (v, _), _) -> Some v
| _ -> None) atm_pats |> setify
in
match vars with
| [] -> k id_top atm_pats
| [v] -> Action.mk_set v (k id_top atm_pats)
| _ -> failwith "ambiguous var match"
in
(* generate a matcher for the rule *)
let ids_top =
Array.to_list states |>
List.filter_map (fun {id; point = p; _} ->
if List.exists ((=) (Top name)) p then
Some id
else None)
in
let rec filter_dups pats =
match pats with
| p :: pats ->
if List.exists (pattern_match p) pats
then filter_dups pats
else p :: filter_dups pats
| [] -> []
in
let pats_top =
List.filter_map (fun r ->
if r.name = name then
Some r.pattern
else None) rules |>
filter_dups |>
List.map (fun p -> (p, ()))
in
gen ids_top pats_top (fun _ pats ->
assert (pats <> []);
Action.stop)
type numberer =
{ atoms: (atomic_pattern * p state) list
; statemap: p state StateMap.t
; states: p state array
; mutable ops: op list
(* memoizes the list of possible operations
* according to the statemap *) }
let make_numberer sa am sm =
{ atoms = am
; states = sa
; statemap = sm
; ops = [] }
let atom_state n atm =
List.assoc atm n.atoms

292
tools/mgen/sexp.ml Normal file
View File

@ -0,0 +1,292 @@
type pstate =
{ data: string
; line: int
; coln: int
; indx: int }
type perror =
{ error: string
; ps: pstate }
exception ParseError of perror
type 'a parser =
{ fn: 'r. pstate -> ('a -> pstate -> 'r) -> 'r }
let update_pos ps beg fin =
let l, c = (ref ps.line, ref ps.coln) in
for i = beg to fin - 1 do
if ps.data.[i] = '\n' then
(incr l; c := 0)
else
incr c
done;
{ ps with line = !l; coln = !c }
let pret (type a) (x: a): a parser =
let fn ps k = k x ps in { fn }
let pfail error: 'a parser =
let fn ps _ = raise (ParseError {error; ps})
in { fn }
let por: 'a parser -> 'a parser -> 'a parser =
fun p1 p2 ->
let fn ps k =
try p1.fn ps k with ParseError e1 ->
try p2.fn ps k with ParseError e2 ->
if e1.ps.indx > e2.ps.indx then
raise (ParseError e1)
else
raise (ParseError e2)
in { fn }
let pbind: 'a parser -> ('a -> 'b parser) -> 'b parser =
fun p1 p2 ->
let fn ps k =
p1.fn ps (fun x ps -> (p2 x).fn ps k)
in { fn }
(* handy for recursive rules *)
let papp p x = pbind (pret x) p
let psnd: 'a parser -> 'b parser -> 'b parser =
fun p1 p2 -> pbind p1 (fun _x -> p2)
let pfst: 'a parser -> 'b parser -> 'a parser =
fun p1 p2 -> pbind p1 (fun x -> psnd p2 (pret x))
module Infix = struct
let ( let* ) = pbind
let ( ||| ) = por
let ( |<< ) = pfst
let ( |>> ) = psnd
end
open Infix
let pre: ?what:string -> string -> string parser =
fun ?what re ->
let what =
match what with
| None -> Printf.sprintf "%S" re
| Some what -> what
and re = Str.regexp re in
let fn ps k =
if not (Str.string_match re ps.data ps.indx) then
(let error =
Printf.sprintf "expected to match %s" what in
raise (ParseError {error; ps}));
let ps =
let indx = Str.match_end () in
{ (update_pos ps ps.indx indx) with indx }
in
k (Str.matched_string ps.data) ps
in { fn }
let peoi: unit parser =
let fn ps k =
if ps.indx <> String.length ps.data then
raise (ParseError
{ error = "expected end of input"; ps });
k () ps
in { fn }
let pws = pre "[ \r\n\t*]*"
let pws1 = pre "[ \r\n\t*]+"
let pthen p1 p2 =
let* x1 = p1 in
let* x2 = p2 in
pret (x1, x2)
let rec plist_tail: 'a parser -> ('a list) parser =
fun pitem ->
(pws |>> pre ")" |>> pret []) |||
(let* itm = pitem in
let* itms = plist_tail pitem in
pret (itm :: itms))
let plist pitem =
pws |>> pre ~what:"a list" "("
|>> plist_tail pitem
let plist1p p1 pitem =
pws |>> pre ~what:"a list" "("
|>> pthen p1 (plist_tail pitem)
let ppair p1 p2 =
pws |>> pre ~what:"a pair" "("
|>> pthen p1 p2 |<< pws |<< pre ")"
let run_parser p s =
let ps =
{data = s; line = 1; coln = 0; indx = 0} in
try `Ok (p.fn ps (fun res _ps -> res))
with ParseError e ->
let rec bol i =
if i = 0 then i else
if i < String.length s && s.[i] = '\n'
then i+1 (* XXX BUG *)
else bol (i-1)
in
let rec eol i =
if i = String.length s then i else
if s.[i] = '\n' then i else
eol (i+1)
in
let bol = bol e.ps.indx in
let eol = eol e.ps.indx in
(*
Printf.eprintf "bol:%d eol:%d indx:%d len:%d\n"
bol eol e.ps.indx (String.length s); (* XXX debug *)
*)
let lines =
String.split_on_char '\n'
(String.sub s bol (eol - bol))
in
let nl = List.length lines in
let caret = ref (e.ps.indx - bol) in
let msg = ref [] in
let pfx = " > " in
lines |> List.iteri (fun ln l ->
if ln <> nl - 1 || l <> "" then begin
let ll = String.length l + 1 in
msg := (pfx ^ l ^ "\n") :: !msg;
if !caret <= ll then begin
let pad = String.make !caret ' ' in
msg := (pfx ^ pad ^ "^\n") :: !msg;
end;
caret := !caret - ll;
end;
);
`Error
( e.ps, e.error
, String.concat "" (List.rev !msg) )
(* ---------------------------------------- *)
(* pattern parsing *)
(* ---------------------------------------- *)
(* Example syntax:
(with-vars (a b c d)
(patterns
(ob (add (tmp a) (con d)))
(bsm (add (tmp b) (mul (tmp m) (con 2 4 8)))) ))
*)
open Match
let pint64 =
let* s = pre "[-]?[0-9_]+" in
pret (Int64.of_string s)
let pid =
pre ~what:"an identifer"
"[a-zA-Z][a-zA-Z0-9_]*"
let pop_base =
let sob, obs = show_op_base, op_bases in
let* s = pre ~what:"an operator"
(String.concat "\\|" (List.map sob obs))
in pret (List.find (fun o -> s = sob o) obs)
let pop = let* ob = pop_base in pret (Kl, ob)
let rec ppat vs =
let pcons_tail =
let* cs = plist_tail (pws1 |>> pint64) in
match cs with
| [] -> pret [AnyCon]
| _ -> pret (List.map (fun c -> Con c) cs)
in
let pvar =
let* id = pid in
if not (List.mem id vs) then
pfail ("unbound variable: " ^ id)
else
pret id
in
pws |>> (
( let* c = pint64 in pret [Atm (Con c)] )
|||
( pre "(con)" |>> pret [Atm AnyCon] ) |||
( let* cs = pre "(con" |>> pcons_tail in
pret (List.map (fun c -> Atm c) cs) ) |||
( let* v = pre "(con" |>> pws1 |>> pvar in
let* cs = pcons_tail in
pret (List.map (fun c -> Var (v, c)) cs) )
|||
( pre "(tmp)" |>> pret [Atm Tmp] ) |||
( let* v = pre "(tmp" |>> pws1 |>> pvar in
pws |>> pre ")" |>> pret [Var (v, Tmp)] )
|||
( let* (op, rands) =
plist1p (pws |>> pop) (papp ppat vs) in
let nrands = List.length rands in
if nrands < 2 then
pfail ( "binary op requires at least"
^ " two arguments" )
else
let mk x y = Bnr (op, x, y) in
pret
(products rands []
(fun rands pats ->
(* construct a left-heavy tree *)
let r0 = List.hd rands in
let rs = List.tl rands in
List.fold_left mk r0 rs :: pats)) )
)
let pwith_vars ?(vs = []) p =
( let* vs =
pws |>> pre "(with-vars" |>> pws |>>
plist (pws |>> pid)
in pws |>> p vs |<< pws |<< pre ")" )
||| p vs
let ppats =
pwith_vars @@ fun vs ->
pre "(patterns" |>> plist_tail
(pwith_vars ~vs @@ fun vs ->
let* n, ps = ppair pid (ppat vs) in
pret (n, vs, ps))
(* ---------------------------------------- *)
(* tests *)
(* ---------------------------------------- *)
let () =
if false then
let show_patterns ps =
"[" ^ String.concat "; "
(List.map show_pattern ps) ^ "]"
in
let pat s =
Printf.printf "parse %s = " s;
let vars =
[ "foobar"; "a"; "b"; "d"
; "m"; "s"; "x" ]
in
match run_parser (ppat vars) s with
| `Ok p ->
Printf.printf "%s\n" (show_patterns p)
| `Error (_, e, _) ->
Printf.printf "ERROR: %s\n" e
in
pat "42";
pat "(tmp)";
pat "(tmp foobar)";
pat "(con)";
pat "(con 1 2 3)";
pat "(con x 1 2 3)";
pat "(add 1 2)";
pat "(add 1 2 3 4)";
pat "(sub 1 2)";
pat "(sub 1 2 3)";
pat "(tmp unbound_var)";
pat "(add 0)";
pat "(add 1 (add 2 3))";
pat "(add (tmp a) (con d))";
pat "(add (tmp b) (mul (tmp m) (con s 2 4 8)))";
pat "(add (con 1 2) (con 3 4))";
()

134
tools/mgen/test.ml Normal file
View File

@ -0,0 +1,134 @@
open Match
open Fuzz
open Cgen
(* unit tests *)
let test_pattern_match =
let pm = pattern_match
and nm = fun x y -> not (pattern_match x y) in
begin
assert (nm (Atm Tmp) (Atm (Con 42L)));
assert (pm (Atm AnyCon) (Atm (Con 42L)));
assert (nm (Atm (Con 42L)) (Atm AnyCon));
assert (nm (Atm (Con 42L)) (Atm Tmp));
end
let test_peel =
let o = Kw, Oadd in
let p = Bnr (o, Bnr (o, Atm Tmp, Atm Tmp),
Atm (Con 42L)) in
let l = peel p () in
let () = assert (List.length l = 3) in
let atomic_p (p, _) =
match p with Atm _ -> true | _ -> false in
let () = assert (List.for_all atomic_p l) in
let l = List.map (fun (p, c) -> fold_cursor c p) l in
let () = assert (List.for_all ((=) p) l) in
()
let test_fold_pairs =
let l = [1; 2; 3; 4; 5] in
let p = fold_pairs l l [] (fun a b -> a :: b) in
let () = assert (List.length p = 25) in
let p = sort_uniq compare p in
let () = assert (List.length p = 25) in
()
(* test pattern & state *)
let print_sm oc =
StateMap.iter (fun k s' ->
match k with
| K (o, sl, sr) ->
let top =
List.fold_left (fun top c ->
match c with
| Top r -> top ^ " " ^ r
| _ -> top) "" s'.point
in
Printf.fprintf oc
" (%s %d %d) -> %d%s\n"
(show_op o)
sl.id sr.id s'.id top)
let rules =
let oa = Kl, Oadd in
let om = Kl, Omul in
let va = Var ("a", Tmp)
and vb = Var ("b", Tmp)
and vc = Var ("c", Tmp)
and vs = Var ("s", Tmp) in
let vars = ["a"; "b"; "c"; "s"] in
let rule name pattern =
List.map
(fun pattern -> {name; vars; pattern})
(ac_equiv pattern)
in
match `X64Addr with
(* ------------------------------- *)
| `X64Addr ->
(* o + b *)
rule "ob" (Bnr (oa, Atm Tmp, Atm AnyCon))
@ (* b + s * m *)
rule "bsm" (Bnr (oa, vb, Bnr (om, Var ("m", Con 2L), vs)))
@
rule "bsm" (Bnr (oa, vb, Bnr (om, Var ("m", Con 4L), vs)))
@
rule "bsm" (Bnr (oa, vb, Bnr (om, Var ("m", Con 8L), vs)))
@ (* b + s *)
rule "bs1" (Bnr (oa, vb, vs))
@ (* o + s * m *)
(* rule "osm" (Bnr (oa, Atm AnyCon, Bnr (om, Atm (Con 4L), Atm Tmp))) *) []
@ (* o + b + s *)
rule "obs1" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb), vs))
@ (* o + b + s * m *)
rule "obsm" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb),
Bnr (om, Var ("m", Con 2L), vs)))
@
rule "obsm" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb),
Bnr (om, Var ("m", Con 4L), vs)))
@
rule "obsm" (Bnr (oa, Bnr (oa, Var ("o", AnyCon), vb),
Bnr (om, Var ("m", Con 8L), vs)))
(* ------------------------------- *)
| `Add3 ->
[ { name = "add"
; vars = []
; pattern = Bnr (oa, va, Bnr (oa, vb, vc)) } ] @
[ { name = "add"
; vars = []
; pattern = Bnr (oa, Bnr (oa, va, vb), vc) } ]
(*
let sa, am, sm = generate_table rules
let () =
Array.iteri (fun i s ->
Format.printf "@[state %d: %s@]@."
i (show_pattern s.seen))
sa
let () = print_sm stdout sm; flush stdout
let matcher = lr_matcher sm sa rules "obsm" (* XXX *)
let () = Format.printf "@[<v>%a@]@." Action.pp matcher
let () = Format.printf "@[matcher size: %d@]@." (Action.size matcher)
let numbr = make_numberer sa am sm
let () =
let opts = { pfx = ""
; static = true
; oc = stdout } in
emit_c opts numbr;
emit_matchers opts
[ ( ["b"; "o"; "s"; "m"]
, "obsm"
, matcher ) ]
(*
let tp = fuzz_numberer rules numbr
let () = test_matchers tp numbr rules
*)
*)

View File

@ -1,8 +1,12 @@
#!/bin/sh
dir=`dirname "$0"`
bin=$dir/../qbe
binref=$dir/../qbe.ref
if [ -z "${bin:-}" ]; then
bin=$dir/../qbe
fi
if [ -z "${binref:-}" ]; then
binref=${bin}.ref
fi
tmp=/tmp/qbe.zzzz
@ -12,55 +16,98 @@ asmref=$tmp.ref.s
exe=$tmp.exe
out=$tmp.out
qemu_not_needed() {
"$@"
}
cc=
find_cc_and_qemu() {
if [ -n "$cc" ]; then
return
fi
target="$1"
candidate_cc="$2"
if $candidate_cc -v >/dev/null 2>&1; then
cc=$candidate_cc
echo "cc: $cc"
if [ "$target" = "$(uname -m)" ]
then
qemu=qemu_not_needed
echo "qemu: not needed, testing native architecture"
else
qemu="$3"
if $qemu -version >/dev/null 2>&1
then
sysroot=$($candidate_cc -print-sysroot)
if [ -n "$sysroot" ]; then
qemu="$qemu -L $sysroot"
fi
echo "qemu: $qemu"
elif $qemu --version >/dev/null 2>&1
then
# wine
:
else
qemu=
echo "qemu: not found"
fi
fi
echo
fi
}
init() {
case "$TARGET" in
arm64)
for p in aarch64-linux-musl aarch64-linux-gnu
do
cc="$p-gcc -no-pie -static"
qemu="qemu-aarch64"
if
$cc -v >/dev/null 2>&1 &&
$qemu -version >/dev/null 2>&1
then
if sysroot=$($cc -print-sysroot) && test -n "$sysroot"
then
qemu="$qemu -L $sysroot"
fi
break
fi
cc=
find_cc_and_qemu aarch64 "$p-gcc -no-pie -static" "qemu-aarch64"
done
if test -z "$cc"
if test -z "$cc" -o -z "$qemu"
then
echo "Cannot find arm64 compiler or qemu."
exit 1
exit 77
fi
bin="$bin -t arm64"
;;
rv64)
for p in riscv64-linux-musl riscv64-linux-gnu
do
cc="$p-gcc -no-pie -static"
qemu="qemu-riscv64"
if
$cc -v >/dev/null 2>&1 &&
$qemu -version >/dev/null 2>&1
then
if sysroot=$($cc -print-sysroot) && test -n "$sysroot"
then
qemu="$qemu -L $sysroot"
fi
break
fi
cc=
find_cc_and_qemu riscv64 "$p-gcc -no-pie -static" "qemu-riscv64"
done
if test -z "$cc" -o -z "$qemu"
then
echo "Cannot find riscv64 compiler or qemu."
exit 77
fi
bin="$bin -t rv64"
;;
x86_64)
for p in x86_64-linux-musl x86_64-linux-gnu
do
find_cc_and_qemu x86_64 "$p-gcc -no-pie -static" "qemu-x86_64"
done
if test -z "$cc" -o -z "$qemu"
then
echo "Cannot find x86_64 compiler or qemu."
exit 77
fi
bin="$bin -t amd64_sysv"
;;
amd64_win)
for p in x86_64-w64-mingw32
do
find_cc_and_qemu x86_64-w64 "$p-gcc -static" "wine"
done
if test -z "$cc"
then
echo "Cannot find riscv64 compiler or qemu."
echo "Cannot find windows compiler or wine."
exit 1
fi
bin="$bin -t rv64"
export WINEDEBUG=-all
bin="$bin -t amd64_win"
;;
"")
case `uname` in
@ -82,7 +129,7 @@ init() {
;;
*)
echo "Unknown target '$TARGET'."
exit 1
exit 77
;;
esac
}
@ -157,7 +204,7 @@ once() {
if test -s $out
then
$qemu $exe a b c | diff -u - $out
$qemu $exe a b c | tr -d '\r' | diff -u - $out
ret=$?
reason="output"
else

196
util.c
View File

@ -154,6 +154,24 @@ vgrow(void *vp, ulong len)
*(Vec **)vp = v1;
}
void
addins(Ins **pvins, uint *pnins, Ins *i)
{
if (i->op == Onop)
return;
vgrow(pvins, ++(*pnins));
(*pvins)[(*pnins)-1] = *i;
}
void
addbins(Ins **pvins, uint *pnins, Blk *b)
{
Ins *i;
for (i=b->ins; i<&b->ins[b->nins]; i++)
addins(pvins, pnins, i);
}
void
strf(char str[NString], char *s, ...)
{
@ -229,6 +247,62 @@ iscmp(int op, int *pk, int *pc)
return 1;
}
void
igroup(Blk *b, Ins *i, Ins **i0, Ins **i1)
{
Ins *ib, *ie;
ib = b->ins;
ie = ib + b->nins;
switch (i->op) {
case Oblit0:
*i0 = i;
*i1 = i + 2;
return;
case Oblit1:
*i0 = i - 1;
*i1 = i + 1;
return;
case_Opar:
for (; i>ib && ispar((i-1)->op); i--)
;
*i0 = i;
for (; i<ie && ispar(i->op); i++)
;
*i1 = i;
return;
case Ocall:
case_Oarg:
for (; i>ib && isarg((i-1)->op); i--)
;
*i0 = i;
for (; i<ie && i->op != Ocall; i++)
;
assert(i < ie);
*i1 = i + 1;
return;
case Osel1:
for (; i>ib && (i-1)->op == Osel1; i--)
;
assert(i->op == Osel0);
/* fall through */
case Osel0:
*i0 = i++;
for (; i<ie && i->op == Osel1; i++)
;
*i1 = i;
return;
default:
if (ispar(i->op))
goto case_Opar;
if (isarg(i->op))
goto case_Oarg;
*i0 = i;
*i1 = i + 1;
return;
}
}
int
argcls(Ins *i, int n)
{
@ -253,18 +327,18 @@ emiti(Ins i)
}
void
idup(Ins **pd, Ins *s, ulong n)
idup(Blk *b, Ins *s, ulong n)
{
*pd = alloc(n * sizeof(Ins));
if (n)
memcpy(*pd, s, n * sizeof(Ins));
vgrow(&b->ins, n);
icpy(b->ins, s, n);
b->nins = n;
}
Ins *
icpy(Ins *d, Ins *s, ulong n)
{
if (n)
memcpy(d, s, n * sizeof(Ins));
memmove(d, s, n * sizeof(Ins));
return d + n;
}
@ -304,6 +378,16 @@ cmpop(int c)
return cmptab[c][1];
}
int
cmpwlneg(int op)
{
if (INRANGE(op, Ocmpw, Ocmpw1))
return cmpneg(op - Ocmpw) + Ocmpw;
if (INRANGE(op, Ocmpl, Ocmpl1))
return cmpneg(op - Ocmpl) + Ocmpl;
die("not a wl comparison");
}
int
clsmerge(short *pk, short k)
{
@ -334,6 +418,28 @@ phicls(int t, Tmp *tmp)
return t1;
}
uint
phiargn(Phi *p, Blk *b)
{
uint n;
if (p)
for (n=0; n<p->narg; n++)
if (p->blk[n] == b)
return n;
return -1;
}
Ref
phiarg(Phi *p, Blk *b)
{
uint n;
n = phiargn(p, b);
assert(n != -1u && "block not found");
return p->arg[n];
}
Ref
newtmp(char *prfx, int k, Fn *fn)
{
@ -398,22 +504,40 @@ getcon(int64_t val, Fn *fn)
}
int
addcon(Con *c0, Con *c1)
addcon(Con *c0, Con *c1, int m)
{
if (c0->type == CUndef)
if (m != 1 && c1->type == CAddr)
return 0;
if (c0->type == CUndef) {
*c0 = *c1;
else {
c0->bits.i *= m;
} else {
if (c1->type == CAddr) {
if (c0->type == CAddr)
return 0;
c0->type = CAddr;
c0->sym = c1->sym;
}
c0->bits.i += c1->bits.i;
c0->bits.i += c1->bits.i * m;
}
return 1;
}
int
isconbits(Fn *fn, Ref r, int64_t *v)
{
Con *c;
if (rtype(r) == RCon) {
c = &fn->con[r.val];
if (c->type == CBits) {
*v = c->bits.i;
return 1;
}
}
return 0;
}
void
salloc(Ref rt, Ref rs, Fn *fn)
{
@ -594,3 +718,57 @@ dumpts(BSet *bs, Tmp *tmp, FILE *f)
fprintf(f, " %s", tmp[t].name);
fprintf(f, " ]\n");
}
void
runmatch(uchar *code, Num *tn, Ref ref, Ref *var)
{
Ref stkbuf[20], *stk;
uchar *s, *pc;
int bc, i;
int n, nl, nr;
assert(rtype(ref) == RTmp);
stk = stkbuf;
pc = code;
while ((bc = *pc))
switch (bc) {
case 1: /* pushsym */
case 2: /* push */
assert(stk < &stkbuf[20]);
assert(rtype(ref) == RTmp);
nl = tn[ref.val].nl;
nr = tn[ref.val].nr;
if (bc == 1 && nl > nr) {
*stk++ = tn[ref.val].l;
ref = tn[ref.val].r;
} else {
*stk++ = tn[ref.val].r;
ref = tn[ref.val].l;
}
pc++;
break;
case 3: /* set */
var[*++pc] = ref;
if (*(pc + 1) == 0)
return;
/* fall through */
case 4: /* pop */
assert(stk > &stkbuf[0]);
ref = *--stk;
pc++;
break;
case 5: /* switch */
assert(rtype(ref) == RTmp);
n = tn[ref.val].n;
s = pc + 1;
for (i=*s++; i>0; i--, s++)
if (n == *s++)
break;
pc += *s;
break;
default: /* jump */
assert(bc >= 10);
pc = code + (bc - 10);
break;
}
}